# Migration guide: v0.4 → v1.0

**External-review gate open** (#1390). This guide remains provisional
until at least two external upgraders have run it against their own
pre-v1.0 installs and the reported gaps have either landed here or
been captured as known limitations. The reviewer packet lives at
[`external-review-packet.md`](external-review-packet.md).

The walk from pincherMCP v0.4 (the channel-discipline kickoff) to
v1.0 stable spans ~40 releases. This guide is the **upgrader's
shortcut**: read the section matching your starting release, follow
the actions, skip everything that already shipped before your
current version.

> Full release-by-release detail lives in [CHANGELOG.md](../../CHANGELOG.md).
> This document is for the **what do I have to do** question, not the
> what changed question.

## 30-second walkthrough by starting version

| Starting release | One-line action |
|---|---|
| **v0.4 – v0.5** | Re-install. Schema, tool surface, and data dir all moved. See [Pre-v0.6 starting point](#pre-v06-starting-point). |
| **v0.6 – v0.30** | Re-install + `pincher index <path> --force` once. Many extractor improvements ride on hash-skip; force clears prior runs' confidence-tagged rows. |
| **v0.30 – v0.55** | Re-install + `pincher index <path> --force` once. v0.54 schema v25 ships closure tables (opt-in). |
| **v0.55 – v0.65** | Re-install. CI gate changes don't affect runtime; CLI behavior unchanged. |
| **v0.66 – v0.70** | Re-install. v0.66 silent-cross-project guard tightens `symbol`/`context`/`neighborhood` — set `cross_project=true` on requests that need legacy off-session lookup. v0.69 char/4 token accounting is default; opt back into BPE via `PINCHER_TOKEN_ACCOUNTING=exact`. |
| **v0.70 → v0.71+** | Re-install + `pincher index <path>` against every project once. Schema v32 adds `branch` columns + `projects.current_branch`; the indexer needs one pass per project to populate them. After, `pincher doctor` surfaces a branch-drift advisory when on-disk branch ≠ last-indexed. |
| **v0.71 → v0.72** | Re-install. Schema v33 adds `extraction_failures.binary_version_at_failure` (auto-migrates; no re-index needed). **BREAKING (pre-1.0):** if your host consumes `projects.current_branch` from `list`/`architecture`/`health`/`doctor` responses, the JSON tag is renamed to `last_indexed_branch` (#1388). Update consumer parsing in lockstep with the upgrade. |
| **v0.72 → v0.80** | Re-install. v0.80 closed Phase 3 with a stable-channel promotion; every change is additive. No re-index, no consumer action. |
| **v0.80 → v0.90** | Re-install. v0.90 closed Phase 4 (composite-tool buildout) with a stable promotion. Five new composite MCP tools shipped — `investigate_failure`, `plan_change`, `audit_unused`, `onboard_module`, `why_empty` — all additive; existing tools and arg shapes unchanged. |
| **v0.90 → v0.92** | Re-install. Phase 5 RC cadence. v0.92 adds the `branch_overlap` MCP tool, the `Rationale` symbol kind (Go `// NOTE:` / `// HACK:` / `// WHY:` etc. comments), and the interactive `pincher setup` wizard. Schema unchanged at v34. **BREAKING (pre-1.0):** bare `pincher init` (no `--target`) no longer silently defaults to `claude` — it host-resolves (CLAUDECODE env, then editor markers) and refuses rather than guess; pass `--target=<name>` explicitly if you scripted around the old default ([#1862](https://github.com/kwad77/pincher/issues/1862)). |
| **v0.92 → v0.93** | Re-install. Bug fixes only — no schema change, no surface change, no consumer action. `onboard_module` and `investigate_failure` clarify their edge-case responses; `pincher update --check` test infrastructure tightened (user-invisible); migration guide forward-ported through v0.92. |
| **v0.93 → v0.94** | Re-install. Schema v35 adds covering edge traversal indexes for lower read amplification during graph traversals; no re-index required. Read-only inspection commands and graph-heavy context/composite tools do less SQLite work under concurrent indexing. |
| **v0.94 → v0.99+** | Re-install. Schema v36 adds a crash/OOM recovery marker so a killed index pass is retried with a full re-extract instead of trusting partial file hashes, and schema v37 adds a project-scoped inbound edge grouping index for faster hotspot ranking. No manual re-index required. Watch for the v1.0 stable-promotion notes — Phase 5 ships RC fixes only between here and v1.0, and tool schemas freeze at the v1.0 tag. |

If you're on a `v0.X.Y` patch release, the answer matches the
`v0.X.0` row above.

## Pre-v0.6 starting point

The pre-channel-discipline builds didn't have stable schemas or
data-dir conventions. If you're upgrading from anything before
v0.6, the safest path is:

1. Note your old data directory (where your `pincher.db` lives).
2. Install the new binary fresh (Homebrew, Scoop, or direct download).
3. Re-index your projects from scratch — pincher will create a fresh
   DB at the canonical OS-conventional path. The old DB stays where
   it was; delete it once you're confident the new one's working.

## Schema version map

The DB schema version is independent of pincher's release version.
The migration map below lists the version each schema landed in and
what the migration does for *your* indexed data. **Every migration
is forward-only and runs automatically on the next `db.Open()`** —
you don't trigger migrations manually.

| Schema | First in | Adds | User impact |
|---|---|---|---|
| v1 | Pre-v0.4 baseline | — | No action. |
| v2 | v0.7 | `extraction_confidence` on `symbols` | None on existing rows (defaults to 1.0). |
| v9 | v0.7 | Per-corpus FTS5 split (`symbols_code_fts` / `symbols_config_fts` / `symbols_docs_fts`) | `search` gains a `corpus=` parameter. Existing data stays in legacy `symbols_fts` until first re-index. |
| v12 | v0.9 | Legacy `symbols_fts` removed (#106) | If you queried `symbols_fts` directly via custom SQL, switch to one of the three per-corpus vtabs. Standard `search` callers are unaffected. |
| v15 | v0.10 | `projects.schema_version_at_index` (#236) | `pincher doctor` can now distinguish project rows indexed by old vs current schemas. Pre-v15 rows show "stale (unknown)" until next re-index. |
| v18 | v0.11 | `projects.binary_version` (#304) | Binary-drift warnings start surfacing. Pre-v18 rows opt out. |
| v19 | v0.16 | `pending_edges` (#427) | Watcher-triggered partial reindex stops dropping cross-file edges. Take a single `pincher index <path>` pass for the table to populate. |
| v20 | v0.18 | `edges.source` (#475) | Atomic resolve-pass replacement. Pre-v20 stale resolve-pass edges aren't auto-cleaned — run `pincher index <path> --force` once after upgrading to v0.18 if you have an old, large project DB. |
| v22 | v0.19 | `pending_edges.receiver_type` (#423) | Type-resolver tracking. No user action. |
| v25 | v0.54 | Closure tables (#685) | Opt-in via `PINCHER_CLOSURE_TABLES=1`. Pre-existing trace queries unaffected. |
| v26 | v0.57 | Type-info resolver tables (#760) | Closes the `dead_code` false-positive triangle. `pincher index <path> --force` once gets you the new edges. |
| v27 | v0.64 | `session_tool_calls` per-call event log (#635) | Dashboard panels start surfacing tool-call entropy + payload distribution. No re-index needed. |
| v28 | v0.66 | `symbols` composite PRIMARY KEY (#1231) | **First v28 startup runs the table-rebuild migration** (drops + recreates FTS5 vtabs). Takes seconds on small DBs, minutes on multi-GB ones. Don't interrupt the first `pincher` invocation post-upgrade. |
| v29 | v0.68 | `bench_runs` / `bench_results` (#1162) | `pincher bench --persist` starts writing. No re-index needed. |
| v30 | v0.69 | `closure.via_kind` (#685 phase 2) | Closure trace fast-path produces `Via` identically to CTE. No user action. |
| v31 | v0.71 | `branch` column on `symbols`/`edges`/`files`/`pending_edges` (#1303 Phase 1) | Foundation for multi-branch coexistence; column populated empty until v32 indexer stamps it. |
| v32 | v0.71 | `projects.current_branch` + indexer stamps git branch (#1303 Phase 2a) | **Run `pincher index <path>` against each project once** so `current_branch` and per-row `branch` populate. After that, `pincher doctor` surfaces a branch-drift advisory when your on-disk git branch differs from the last-indexed one. |
| v33 | v0.72 | `extraction_failures.binary_version_at_failure` (#1421) | Doctor's extraction_failures rows now carry the binary version that recorded them — distinguishes fixed-since rows from still-recurring rows without cross-referencing CHANGELOG. Pre-v33 rows show empty string for the field. No re-index required. |
| v34 | v0.85 | `sessions.queries_zero_expected` / `queries_zero_unexpected` (#1632) | Splits the existing `queries_zero_result` counter into audit-shape (queries expected to return zero) vs caller-surprised, so `pincher stats` surfaces the actionable rate. The original sum column is kept for back-compat. Pre-v34 sessions hold zero on both new columns. No re-index required. |
| v35 | v0.94 | `idx_edge_from_project_kind_to` / `idx_edge_to_project_kind_from` | Cover outbound/inbound edge traversals by endpoint, project, kind, and opposite endpoint. Lowers read amplification for trace/pinchQL/composite graph paths in multi-project stores. No re-index required. |
| v36 | v0.96 | `projects.index_state` / `index_started_at` (#1573) | Crash/OOM recovery marker. If an index process dies mid-pass after stamping file hashes, the next index detects the stale `running` state and force-refreshes instead of preserving a half-written project DB. No manual action. |
| v37 | v0.99 | `idx_edge_project_to` | Project-scoped inbound edge grouping index for `hotspot`. Avoids the temp grouping B-tree used by the previous project/kind index plan. No re-index required. |

## Tool-contract changes

The MCP tool list has grown from ~15 in v0.4 to **29 in v0.92**. Some surfaces
remain CLI-only by design: `pincher verify` is a local re-hash audit, `pincher
setup` is an interactive wizard, `pincher completion` configures the shell, and
`pincher export-graph` / `pincher callflow` produce human/export artifacts
rather than agent-callable tool responses.
Net change is additive — no tools have been *removed* since v0.5;
some have been *renamed* or had argument semantics tightened.

### New tools (additive — your existing calls keep working)

- **v0.10 `architecture`** — entry points + hotspots survey.
- **v0.15 `query`** — pinchQL graph queries.
- **v0.20 `trace`** — BFS over CALLS / IMPORTS / READS.
- **v0.30 `dead_code`** — unreached symbols by language.
- **v0.40 `guide`** — task-shaped tool routing.
- **v0.55 `adr`** — runtime ADR storage.
- **v0.60 `health`** — extraction quality / parser tier per language.
- **v0.65 `doctor`** — diagnostic envelope (CLI + MCP).
- **v0.66 `context_for_task`** — composite tool replacing typical 5–10 atomic calls.
- **v0.68 `bench`** — runs-on-your-own-project savings measurement.
- **v0.81 `investigate_failure`** — composite: stack trace → symbol resolution → trace → recent-changes envelope.
- **v0.82 `plan_change`** — pre-edit composite: callers + impact summary + suggested test surface.
- **v0.83 `audit_unused`** — dead-code candidates with deep-trace confirmation; refuses verdicts mid-resolve (#1847).
- **v0.84 `onboard_module`** — new-contributor orientation composite: entry symbols + reading order + landmark callers.
- **v0.85 `why_empty`** — stateless empty-result recovery: tool + args in, suggested rewrites + diagnostics out.
- **v0.92 `branch_overlap`** — diff-symbols intersection between two branches for merge-order risk surfacing.

### Argument semantics tightened

- **v0.66 silent-cross-project guard** (#1232). `symbol` / `context` /
  `neighborhood` now **error** on omitted-project requests whose ID
  lives only off-session. **Action:** pass `cross_project=true` on
  the request if you want the legacy silent-fallback behavior.
- **v0.65 `fetch`** (#1196). `kind="Document"` arg-style syntax now
  documented; the pre-fix recommendation of `search kind:Document`
  (FTS5 operator syntax) silently misled agents — that string never
  worked. **Action:** if you have prompts referencing `kind:Document`
  inside an FTS5 query, switch to the arg-style form.
- **v0.72 scope-to-session-first sweep** (#1408 / #1425 / #1431).
  `symbol` / `context` / `neighborhood` / `trace` now look up the
  session project FIRST when `project=` is omitted, falling back to
  unscoped only when the id isn't in session. Pre-fix the unscoped
  lookup could return a fork's row when the same id lived in both
  the session project and a fork (e.g. an indexed mirror of the
  current repo), and the #1232 strict-cross-project guard then
  rejected the call with "exists only in project X" — even though
  the symbol DID also live in the session project. **Action:**
  none for callers that already specify `project=`. Hosts that
  relied on the unscoped-first behavior should pass
  `project="<session-project-id>"` explicitly.
- **v0.92 `pincher init` no-target host-aware resolution** (#1862).
  Bare `pincher init` (no `--target` flag, no `target=` MCP arg)
  previously defaulted silently to `claude` — running it from Codex
  or VS Code wrote a `CLAUDE.md` and `.claude/settings.json` for a
  host that wasn't installed. v0.92 host-resolves instead: checks
  `CLAUDECODE` env, then editor marker files (`.codex/`, `.vscode/`,
  `.cursor/`, `.gemini/`, etc.), and refuses with a rich error
  ("could not detect a host — pass --target=<name> or run `pincher
  init --target=detect` for a menu") if nothing matches. **Action:**
  if you scripted `pincher init` with no target, add `--target=claude`
  explicitly to preserve the old behavior; otherwise switch to
  `--target=detect` and let pincher pick.

### BREAKING (pre-1.0) — v0.72

- **`projects.current_branch` → `last_indexed_branch` JSON rename
  (#1388).** Every response carrying a project record (`list`,
  `architecture`, `health`, `doctor`'s projects array, `/v1/projects`
  HTTP) emits the new tag. The DB column name and the Go field name
  are unchanged (internal-only); only the wire tag flips. **Action:**
  if your host parses `current_branch` from project records, switch
  to `last_indexed_branch`. The old name read as "what branch IS
  the project on now" but actually meant "what branch was the
  project last indexed against" — every new integrator misread it.

### New pinchQL surface — v0.72

- **`WHERE col IN [a, b, c]`** (#1439) — first-class list-membership
  predicate. Pre-fix required verbose OR chains. `NOT IN` works
  via the `NOT` wrapper. Empty `IN []` and paren-list `IN (...)`
  surface specific errors rather than silent zero-rows.
- **`COUNT(DISTINCT n.property)`** (#1437) — canonical
  "how many unique callers" query. Pre-fix this errored with
  a misleading "unbalanced delimiter".

### New tools — v0.72

- **`pincher verify`** subcommand (#1399) — re-hashes every indexed
  file against the stored `files.hash` column and surfaces drift /
  missing / unreadable classes. Exit code 2 when any class is
  non-empty so CI / hooks can branch. CLI-only; not surfaced over
  MCP.
- **`pincher init --target=antigravity`** (#1368, path corrected
  #1765) — Google Antigravity (Gemini agent IDE) workspace rules at
  `.agents/rules/pincher.md`. Detection fires on the `.agents/`
  directory so `pincher init --target=detect` surfaces Antigravity
  alongside other detected hosts.

### `_meta` envelope (additive across the journey)

Every tool response carries a `_meta` block. Fields added over time
that consumers may want to start reading:

| Field | Added | Use |
|---|---|---|
| `tokens_used` / `tokens_saved` / `tokens_saved_pct` | v0.4 | Per-call savings accounting |
| `capabilities` | v0.34 (#649) | Routers gate on advertised features |
| `complexity_tier` (`lite`/`standard`/`heavy`) | v0.50 | Multi-agent routers assign models per call |
| `X-Request-ID` header + `_meta.request_id` | v0.56 (#835) | Distributed trace correlation |
| `empty_reason` enum | v0.59 (#1252) | Structured empty-result diagnosis |
| `warnings_v2` / `diagnosis_v2` | v0.71 (#1098) | Typed envelope alongside legacy string forms |

**Default behavior unchanged** — every field that existed pre-upgrade
still emits. New fields are pure additions.

Opt-outs added later for heavy-traffic aggregators:

- **v0.69 `PINCHER_META_CAPABILITIES=off`** drops the per-call
  `capabilities` stamp; `GET /v1/capabilities` returns the same list
  in a single fetch.
- **v0.69 `PINCHER_TOOL_DESCRIPTIONS=short`** trims the 5 longest
  tool descriptions to one-sentence variants, ~750 tokens off the
  session-start `tools/list` handshake.

## CLI flag changes

`pincher index`, `pincher doctor`, `pincher stats` have all
extended their flag surface over time. No flag has been **removed**
since v0.5 — extensions only.

Notable additions:

- **v0.18 `pincher stats --json`** — structured JSON output for
  shell pipelines.
- **v0.30 `pincher index --json-summary`** — structured per-pass
  summary for CI / corpus-snapshot pipelines.
- **v0.55 `pincher project prune-stale`** — drops projects untouched
  for N days at an old schema. Replaces the manual `sqlite3
  pincher.db DELETE FROM projects ...` recipe.
- **v0.66 `pincher vacuum`** — 4-step reclaim flow (#1219).
- **v0.69 `pincher doctor --fix`** — auto-resolves the safe-action
  allowlist (currently VACUUM-when-bloated). Destructive
  remediations stay explicit-action.
- **v0.71 `pincher hook-stats --export-7d`** — shareable JSON
  snapshot of 7-day hook conversion metrics for the
  [#640](https://github.com/kwad77/pincher/issues/640) field-data
  thread. Anonymized by default; `--include-host` opts into
  pincher-version / OS / arch attribution.

## Configuration knobs (`PINCHER_*` env vars)

All env vars are **additive** — defaults preserve the behavior every
prior release shipped with. Set them only when you need the non-
default behavior.

| Env var | Since | Effect when set |
|---|---|---|
| `PINCHER_HTTP_ADDR` | v0.30 | Bind HTTP REST + dashboard server |
| `PINCHER_HTTP_KEY` | v0.30 | Required bearer token on all HTTP requests |
| `PINCHER_HTTP_ALLOW_OPEN=1` | v0.45 (#199) | Permit non-loopback HTTP bind without `--http-key` |
| `PINCHER_BASEPATH=/pincher` | v0.45 | URL prefix when behind a reverse proxy |
| `PINCHER_TRUST_PROXY=1` | v0.45 | Honor `X-Forwarded-*` headers |
| `PINCHER_AUTO_RESTART_ON_DRIFT=1` | v0.50 (#371) | MCP child auto-respawns on binary swap (supervised mode does this by default) |
| `PINCHER_CLOSURE_TABLES=1` | v0.54 (#685) | Opt into closure-tables-default-on (2.3–5.6× trace speedup on large graphs) |
| `PINCHER_SLOW_QUERY_MS=<N>` | v0.99 (#1903) | Persist tool calls slower than N ms to `slow_queries`; `0` disables |
| `PINCHER_DEBUG_META=1` | v0.59 (#1152) | Pretty-print `_meta` blocks in error envelopes |
| `PINCHER_SESSION_ID=<uuid>` | v0.16 (#420) | Stable session ID; counters survive supervised respawn |
| `PINCHER_META_CAPABILITIES=off` | v0.69 (#1087) | Drop per-call `_meta.capabilities` stamp |
| `PINCHER_TOOL_DESCRIPTIONS=short` | v0.69 (#1088) | Short-form tool descriptions |
| `PINCHER_TOKEN_ACCOUNTING=exact` | v0.69 (#1320) | Use cl100k_base BPE (slower, exact) instead of char/4 heuristic for `tokens_used` |
| `PINCHER_MCP_HTTP_PATH=/mcp` | v0.66 (#651) | Mount MCP streamable-HTTP transport on existing HTTP server |
| `PINCHER_DATA_DIR=<path>` | v0.6 | Override the platform data directory; used verbatim, no `pincherMCP` suffix appended |
| `PINCHER_DB_READERS=<N>` | v0.50 | Max concurrent SQLite read connections (default 8, cap 32) |
| `PINCHER_MAX_FILE_SIZE_MB=<N>` | v0.45 (#111) | Per-file indexing cap (default 5 MB; 0 disables) |

## Filesystem layout

The default data directory has been stable since v0.6. Confirm via
`pincher --help` or run `pincher stats --json | jq .data_dir`.

| OS | Default | Notes |
|---|---|---|
| Linux | `${XDG_DATA_HOME:-~/.local/share}/pincherMCP/` | `$XDG_DATA_HOME` honored |
| macOS | `~/Library/Application Support/pincherMCP/` | |
| Windows | `%APPDATA%\pincherMCP\` | Falls back to `%USERPROFILE%\AppData\Roaming\pincherMCP\` |

Inside the data dir:

```
pincher.db              ← all symbols / edges / sessions / hook stats
pincher.db-wal          ← SQLite WAL (managed automatically)
pincher.db-shm          ← SQLite shared-memory (managed automatically)
locks/                  ← per-project index lockfiles (v0.51+)
```

**Override** via `--data-dir=<path>` (every subcommand accepts it)
or via the canonical env vars above.

## Common-path walkthrough — "I'm on v0.6.0, what do I do?"

```bash
# 1. Stop the running pincher (if it's serving MCP under an agent
#    client) and back up your DB. Migrations are forward-only and
#    safe, but rolling back to v0.6 after a v0.71 migration would
#    leave you stuck — pincher refuses to open a DB whose schema
#    is newer than the binary understands.
cp "${XDG_DATA_HOME:-$HOME/.local/share}/pincherMCP/pincher.db" \
  ~/pincher.db.backup

# 2. Install the latest pincher.
brew install kwad77/pincher/pincher        # macOS/Linux
# or: scoop install <pincher-manifest-url>  # Windows
# or: download from https://github.com/kwad77/pincher/releases/latest

# 3. First run will migrate the DB through the current schema transparently.
#    The v28 table rebuild (under v0.66) is the one that takes a
#    noticeable amount of time on multi-GB DBs — don't interrupt.
pincher --version
pincher doctor --json | jq .schema_version  # confirm v38 or newer

# 4. For each indexed project, run one force-index pass so the
#    cumulative extractor improvements + the v32 branch stamping
#    take effect on existing data. Pincher will hash-skip files
#    whose content hasn't changed, so this is cheap.
pincher index /path/to/project --force

# 5. Confirm everything's healthy.
pincher doctor       # advisories section calls out any per-project drift
pincher health-check # live MCP liveness probe
```

If `pincher doctor` reports a branch-drift advisory after step 4,
your on-disk git branch differs from the one the indexer just
stamped. Re-run `pincher index <path>` (no `--force` needed) and
the advisory clears.

## Where to file issues

- **Migration failure** (DB won't open, schema migration crashes):
  open an issue with the `pincher doctor --json` output attached.
- **Tool-call behavior regression** between releases: link the
  specific commit in `CHANGELOG.md` so triage can confirm intentional
  vs accidental.
- **Performance regression**: `pincher bench --persist` output from
  the regressing version + baseline version. The `bench_runs` table
  preserves history for re-comparison.

## Next steps

This guide stays provisional until #1390 closes. Acceptance requires
at least two external users to read the guide end-to-end against
their own pre-v1.0 installs, report unclear steps or undocumented
version gotchas, and have each finding either fixed here or captured
as a known limitation / FAQ entry. Use the
[`external-review-packet.md`](external-review-packet.md) checklist for
that review. Until v1.0 ships, the bottom `v0.94 → v0.99+` row stays
provisional and the schema-version map may keep growing.

Cross-refs:
- [`CHANGELOG.md`](../../CHANGELOG.md) — full release history
- [`external-review-packet.md`](external-review-packet.md) — checklist for #1390 reviewers
- [`docs/reference/`](../reference/README.md) — current tool reference
- [`docs/release-channels.md`](../release-channels.md) — stable / pre-release channel discipline
- [`docs/integrations/loop-leverage-layers.md`](../integrations/loop-leverage-layers.md) — the three-layer agent-leverage frame (hook + `_meta` + composites)
- [`docs/integrations/meta-envelope-contract.md`](../integrations/meta-envelope-contract.md) — the per-field `_meta` envelope contract
- [#668](https://github.com/kwad77/pincher/issues/668) — Phase 3 umbrella
