# Codevira v2.2.0 — Lean (in-repo, no chromadb, token-optimized)

> Active plan. The v2.1.2 plan below (line ~700+) is preserved as history —
> v2.1.2 shipped to PyPI on 2026-05-19.

## Context

v2.1.2 shipped successfully but exposed a structural product problem:
ChromaDB-based semantic code search consumed 64 GB on one project (one
file's HNSW link table exploded due to a write-amplification bug we
shipped in our own indexer code). Even after the fix, the baseline
per-project disk footprint is 40-80 MB — almost entirely the
`codeindex/` vector store.

A market scan confirmed three facts:

1. **The memory-layer race is over.** `agentmemory` (rohitg00) has
   15.5K stars, 95.2% R@5 on LongMemEval, supports 16 IDEs, and
   ships with 0 external DBs. `OMEGA` has 95.4% LongMemEval with
   Apache 2.0 + AES-256. `OpenMemory` (mem0) owns the cloud lane.
   We cannot win the retrieval-accuracy race; competing there is a
   misallocation.

2. **The enforcement-layer space is empty.** Every memory competitor
   is RECALL-only. agentmemory's hooks are explicitly "silent
   observers"; OMEGA "flags" contradictions but doesn't block; mem0
   captures preferences with no enforcement primitive. Codevira's
   `do_not_revert` flag + PreToolUse hook that physically refuses
   Edit/Write calls violating a protected decision is unique in the
   landscape.

3. **2026 AI agents grep + read files iteratively.** Pre-2023 the
   assumption "AI needs vector DB to find code" was correct. In 2026,
   `search_codebase` is functionally redundant with the agent's
   native grep+Read loop. Our usage data confirms it: heavy use of
   `search_decisions` and `get_impact`; near-zero use of
   `search_codebase`. We are maintaining 5000+ LOC of dead-weight code
   that nobody calls.

**The user's explicit directive: ignore migration, ship fresh with the
new approach, fully optimized for token economy.**

## Goals (with hard budgets)

| # | Goal | Measurable target |
|---|---|---|
| G1 | Drop chromadb / sentence-transformers / torch entirely | `pipx install codevira==2.2.0` venv ≤55 MB (was ~200 MB) |
| G2 | MCP server startup with no native deps | `codevira --version` runs in <100 ms (was 1-3 s) |
| G3 | Per-project storage in the repo, human-readable | `.codevira/` ≤2 MB on a 100-decision project; plain JSONL / YAML |
| G4 | AGENTS.md as slim contract, not god-file | AGENTS.md ≤5 KB regardless of decision count; auto-generated |
| G5 | Token-economic injection | Off-topic prompt: 0 tokens injected; on-topic: ≤600 tokens (top-3 digest) |
| G6 | Default search responses are digest-sized | `search_decisions("auth")` returns ≤250 tokens for top-5 |
| G7 | Sub-second cold search | FTS5 `search_decisions` returns in <50 ms on a 1000-decision corpus |
| G8 | The enforcement primitive remains intact | `do_not_revert` still blocks Edit/Write at PreToolUse |
| G9 | Outcome-weighted ranking | Decisions that survived in git rank higher; reverted decisions demoted |
| G10 | Prompt-cache-aligned context blocks | Injected blocks have stable structure (no timestamps, sorted IDs) → cache hit rate measurable |

All 10 goals enforced by hard verification gates (see Testing section).
Any gate failure blocks the release.

## Non-goals (explicit, to prevent scope creep)

- **No migration path from v2.1.x.** Fresh schema; users `codevira init`
  on each project. v2.1.x continues to exist for users who don't upgrade.
- **No multi-language code graph in this release.** Python only.
  TS/Go/Rust deferred to v2.3.0.
- **No semantic search at all.** Not even as opt-in fallback. FTS5 only.
- **No backend adapters** for agentmemory / OMEGA / mem0. Standalone first.
- **No web UI / cloud sync / telemetry.** Git is the sync; CLI is the UX.
- **No code-rule-engine.** Decision conflicts surface to user; we don't
  attempt to "auto-resolve" them.
- **No plugin SDK.** Premature.

## Architecture overview

```
┌─────────────────────────────────────────────────────────────────┐
│  IN THE PROJECT REPO (committed to git)                         │
│                                                                 │
│   AGENTS.md                  ≤5 KB slim contract, generated     │
│      ↑                        from decisions.jsonl              │
│      │                                                          │
│   .codevira/                                                    │
│     decisions.jsonl          full text + metadata (append-only) │
│     digest.jsonl             slim: id+summary+tags+file_path    │
│     outcomes.jsonl           kept/reverted from git observation │
│     manifest.yaml            tag→ids, file→ids index (regen)    │
│     enforcement.yaml         which decisions hard-block         │
│     config.yaml              project settings                   │
│                                                                 │
│   .codevira-cache/           gitignored                         │
│     graph.sqlite             code graph (tree-sitter rebuild)   │
│     fts5.sqlite              FTS5 index over decisions.jsonl    │
│     hash-cache.db            file change detection              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
                              ↑
                              │ MCP / hooks
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  PIPX INSTALL (~/.local/pipx/venvs/codevira)                    │
│                                                                 │
│   codevira (CLI + MCP server)                                   │
│      - pure-python, ~50 MB venv                                 │
│      - sub-100ms startup                                        │
│      - no torch, no chromadb, no sentence-transformers          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
                              ↑
                              │ stdio MCP
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  IDE (Claude Code / Cursor / Antigravity / Codex / Windsurf...) │
│                                                                 │
│   UserPromptSubmit → codevira hook → relevance-gated inject     │
│   Edit/Write → PreToolUse → block if do_not_revert violated     │
│   PostToolUse → record_decision / write_session_log              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

## Complete MCP tool inventory (all 34) and their fate in v2.2.0

Currently registered in `mcp_server/server.py::list_tools()`. Each tool
must have an explicit decision: KEEP / MODIFY / DELETE.

### Decision-layer tools (storage backend changes; surface unchanged)

| Tool | v2.2.0 storage backend |
|---|---|
| `record_decision` | append `.codevira/decisions.jsonl` + update manifest |
| `record_decisions` (batch) | same, batch append |
| `search_decisions` | FTS5 over decisions.jsonl |
| `list_decisions` | paginate over decisions.jsonl with filters |
| `list_tags` | read `.codevira/manifest.yaml` |
| `check_conflict` | FTS5 + Jaccard text similarity (no semantic) |
| `supersede_decision` | append new decision with `supersedes` ptr; update manifest |
| `mark_decision_protected` | rewrite line in decisions.jsonl (rare op) |
| `get_decision_confidence` | aggregate over outcomes.jsonl |
| `get_history` | scan decisions.jsonl + outcomes.jsonl by file_path |

### Session-layer tools (storage backend changes; surface unchanged)

| Tool | v2.2.0 storage backend |
|---|---|
| `get_session_context` | assemble from manifest + recent decisions + roadmap.yaml |
| `write_session_log` | append `.codevira/sessions.jsonl` |
| `write_session_logs` (batch) | same, batch append |

### Roadmap / phases tools (move from `~/.codevira/.../roadmap.yaml` → `.codevira/roadmap.yaml`)

| Tool | v2.2.0 storage backend |
|---|---|
| `get_roadmap` | read `.codevira/roadmap.yaml` |
| `get_phase` | read `.codevira/roadmap.yaml` |
| `add_phase` | append to roadmap.yaml |
| `complete_phase` | mutate roadmap.yaml |
| `defer_phase` | mutate roadmap.yaml |
| `update_phase_status` | mutate roadmap.yaml |
| `update_next_action` | mutate roadmap.yaml |
| `bulk_import_phases` | append to roadmap.yaml |

### Changeset tools (move to `.codevira/changesets.jsonl`)

| Tool | v2.2.0 storage backend |
|---|---|
| `start_changeset` | append `.codevira/changesets.jsonl` |
| `update_changeset_progress` | append a progress event |
| `complete_changeset` | append a done event; emit derived decisions |
| `list_open_changesets` | scan changesets.jsonl, filter by status |

### Rule / preference tools (move to `.codevira/learned_rules.jsonl` + `.codevira/preferences.jsonl`)

| Tool | v2.2.0 storage backend |
|---|---|
| `retire_rule` | rewrite line in learned_rules.jsonl |

### Code-graph tools (UNCHANGED — keep `.codevira-cache/graph.sqlite`)

These all rely on tree-sitter parsing → SQLite graph. ZERO chromadb dependency.
They stay exactly as they are today; only the storage location moves
from `~/.codevira/projects/<key>/graph/graph.db` to
`<repo>/.codevira-cache/graph.sqlite` (gitignored, rebuildable).

| Tool | Behavior |
|---|---|
| `get_node` | unchanged |
| `get_impact` | unchanged |
| `query_graph` | unchanged |
| `update_node` | unchanged |
| `get_signature` | unchanged (tree-sitter extract; multi-language) |
| `get_code` | unchanged (tree-sitter extract; multi-language) |
| `search_codebase` | **DELETED** (the only graph-adjacent tool that used chromadb) |
| `get_playbook` | unchanged (reads `mcp_server/data/rules/*.md`) |

### Total

| Category | Count | Change |
|---|---|---|
| Decision-layer | 10 | storage backend change only |
| Session-layer | 3 | storage backend change only |
| Roadmap | 8 | move location only |
| Changeset | 4 | storage backend change only |
| Rule/pref | 1 | storage backend change only |
| Code-graph | 7 (was 8) | unchanged (1 deleted: search_codebase) |
| **Total in v2.2.0** | **33** | (was 34 in v2.1.2) |

## What STAYS UNCHANGED in v2.2.0

Explicit list to prevent accidental scope creep:

- **All code-graph functionality** (`indexer/sqlite_graph.py`, `indexer/chunker.py`,
  `indexer/graph_generator.py`) — keep tree-sitter parsing + SQLite graph
- **PreToolUse `do_not_revert` enforcement** — `mcp_server/engine/policies/decision_lock.py`
  stays; just reads `.codevira/decisions.jsonl` + `enforcement.yaml` instead of graph.db
- **Anti-regression policy** — `mcp_server/engine/policies/anti_regression.py` keeps
  the fix-history database (unchanged storage)
- **Scope-contract policy** — `mcp_server/engine/policies/scope_contract.py` unchanged
- **TokenBudgetPersist policy** — unchanged
- **PostEditGraphRefresh policy** — unchanged (only updates code graph, not chromadb)
- **Pillar 3 discipline scaffold** — 4 skills, gauntlet, smoke harness, hooks all stay
- **Setup wizard** — `mcp_server/setup_wizard.py` unchanged (still configures 8 IDEs);
  one addition: detect AGENTS.md and warn if it already has non-codevira content
- **Per-IDE hook scripts** — `~/.claude/hooks/codevira-*.sh` unchanged
- **The 8-IDE MCP config writers** — `mcp_server/ide_inject.py` unchanged
- **Watcher** — `mcp_server/watcher/` keeps running BUT only triggers code-graph
  refresh (the chromadb reindex branch goes away)
- **release-gauntlet G1-G4 + G1.5/G1.6/G1.7/G2.5** — all stay; new G2.6 added
- **Cold-install smoke** — script extends with new budget assertions
- **The 5 hardening tests from v2.1.2 hardening release** — all stay

## File format spec (the in-repo schema)

### `.codevira/decisions.jsonl` (full record, append-only)

```jsonl
{"id": "D0001", "ts": "2026-05-19T12:00:00Z", "session_id": "init", "file_path": "auth.py", "decision": "Use bcrypt over argon2 for password hashing because team familiarity + audit history weighs more than argon2's memory-hard advantage for our use case", "context": "Discussed in PR #42; argon2 considered but bcrypt picked", "do_not_revert": true, "tags": ["security", "auth"], "supersedes": null, "superseded_by": null, "outcome": null}
{"id": "D0002", "ts": "2026-05-19T12:05:00Z", "session_id": "init", "file_path": "core.ts", "decision": "Prefer named exports over default exports in TypeScript files", "context": null, "do_not_revert": false, "tags": ["typescript", "style"], "supersedes": null, "superseded_by": null, "outcome": null}
```

- One decision per line; append-only writes (no merge conflicts on parallel commits)
- IDs are stable monotonic strings (`D0001` … `D9999` → `DA000`), NOT auto-increment ints (collisions on parallel commits)
- `ts` is ISO 8601 UTC
- `outcome` is null until git observation detects kept/reverted

### `.codevira/digest.jsonl` (slim, ~50 tokens/entry)

```jsonl
{"id": "D0001", "summary": "Use bcrypt over argon2 for password hashing", "tags": ["security", "auth"], "file": "auth.py", "do_not_revert": true, "weight": 1.0}
{"id": "D0002", "summary": "Prefer named exports over default exports in TS files", "tags": ["typescript", "style"], "file": "core.ts", "do_not_revert": false, "weight": 0.6}
```

- Generated from `decisions.jsonl` (regenerable; can be deleted and rebuilt)
- `summary` is the first 80 chars of `decision`, word-boundary trimmed
- `weight` is the outcome-weighted score (kept = 1.0, modified = 0.6, reverted = 0.2, archived = 0.0)
- This is what gets loaded into the manifest for relevance gating

### `.codevira/manifest.yaml` (tag/file → id index)

```yaml
schema_version: 1
generated_at: 2026-05-19T12:00:00Z
total_decisions: 2
active_decisions: 2
tags:
  security: [D0001]
  auth: [D0001]
  typescript: [D0002]
  style: [D0002]
files:
  "auth.py": [D0001]
  "core.ts": [D0002]
do_not_revert_ids: [D0001]
```

- Rebuilt by `codevira sync` (idempotent) and incrementally on each `record_decision`
- Used by the engine policy to find relevant decisions for a prompt without loading `digest.jsonl`

### `.codevira/outcomes.jsonl` (git-observed kept/reverted)

```jsonl
{"ts": "2026-05-19T14:00:00Z", "decision_id": "D0001", "outcome_type": "kept", "git_ref": "abc1234", "delta_summary": "auth.py unchanged for 30 days"}
{"ts": "2026-05-19T14:05:00Z", "decision_id": "D0002", "outcome_type": "modified", "git_ref": "def5678", "delta_summary": "core.ts: 5 default exports added; partial revert"}
```

### `.codevira/sessions.jsonl` (session events)

```jsonl
{"id": "S0001", "ts": "2026-05-19T10:00:00Z", "session_id": "morning-auth", "summary": "Implemented bcrypt password hashing", "task": "auth flow", "phase": "1", "decision_ids": ["D0001"]}
```

### `.codevira/preferences.jsonl` (extracted preferences)

```jsonl
{"id": "P0001", "ts": "2026-05-19T10:00:00Z", "category": "naming", "signal": "snake_case for Python", "example": "verify_credentials", "frequency": 47, "source": "local"}
```

### `.codevira/learned_rules.jsonl` (rule extractor output)

```jsonl
{"id": "R0001", "ts": "2026-05-19T10:00:00Z", "rule_text": "Files in 'indexer/' should have corresponding tests in 'tests/'", "category": "testing", "confidence": 1.0, "file_pattern": "indexer/*", "source_sessions": ["morning-auth", "evening-refactor"], "retired_at": null, "retired_reason": null}
```

### `.codevira/changesets.jsonl` (changeset events, append-only)

```jsonl
{"id": "C0001", "event": "start", "ts": "2026-05-19T10:00:00Z", "name": "auth-refactor", "scope": ["auth.py", "middleware.py"], "intent": "Switch to bcrypt"}
{"id": "C0001", "event": "progress", "ts": "2026-05-19T11:00:00Z", "files_touched": ["auth.py"], "decisions": ["D0001"]}
{"id": "C0001", "event": "complete", "ts": "2026-05-19T12:00:00Z", "result": "merged", "git_ref": "abc1234"}
```

### `.codevira/roadmap.yaml` (phase tracking — moved from `~/.codevira/projects/<key>/roadmap.yaml`)

Same shape as today's `roadmap.yaml`; just lives in repo now.

### `.codevira/enforcement.yaml` (per-project policy)

```yaml
schema_version: 1
# Default: do_not_revert means PreToolUse hard-blocks the Edit/Write.
# Per-decision overrides go here.
overrides:
  D0001: hard-block   # default for do_not_revert
  D0002: soft-warn    # warn but allow
defaults:
  do_not_revert: hard-block
  protected_age_days: 7        # decisions <7d old can be overridden by author
  override_requires_reason: true
```

### `.codevira/config.yaml` (per-project codevira settings)

```yaml
schema_version: 1
project_name: "my-project"
created_at: 2026-05-19T10:00:00Z
codevira_version: "2.2.0"
agents_md_max_kb: 5
inject_max_decisions: 3
inject_max_tokens: 600
archive_after_days: 90
```

### AGENTS.md (auto-generated, slim contract)

```markdown
# AGENTS.md

<!-- codevira:begin (auto-generated; edit decisions.jsonl directly) -->

## Project: my-project

### Locked decisions (do_not_revert)

- **D0001** Use bcrypt over argon2 for password hashing  ·  `auth.py`  ·  security, auth
- **D0003** All HTTP responses must include `X-Request-ID` header  ·  `middleware.py`  ·  observability

### Active conventions

- Prefer named exports over default exports in TS files  ·  `D0002`
- Always use context.Context as first arg in Go funcs  ·  `D0004`

For the full decision log + outcomes, see `.codevira/decisions.jsonl`
or run `codevira list-decisions`.

<!-- codevira:end -->

<!-- Anything outside the codevira block is preserved on regeneration. -->
```

- Hard size cap: 5 KB. If full content exceeds, oldest non-`do_not_revert` decisions
  get dropped from the rendered file (still present in `decisions.jsonl`).
- Other AI tools that read AGENTS.md (Copilot, Codex, Cursor, Gemini, Factory,
  Amp, Windsurf, Zed, RooCode, Jules) see just the slim contract — cheap.
- Codevira's MCP hook injects the RELEVANT slice for the current prompt
  (see "Relevance-gated injection" below).

## Relevance-gated injection (Goal 5 + 10)

`UserPromptSubmit` engine policy replaces the current cross_session.py
behavior. New flow:

```
1. Read user prompt text.
2. Extract candidate tags (regex match against manifest.yaml's tag list).
3. Extract candidate file paths (regex match against manifest.yaml's file list).
4. If both empty AND no high-weight do_not_revert exists:
     return inject_context=None  (zero tokens)
5. Query FTS5 index for top-N decisions matching prompt keywords.
6. Merge: tag matches (weight 0.4) + file matches (weight 0.4) + FTS5 (weight 0.2).
7. Apply outcome-weight from digest.jsonl.
8. Cap at config.inject_max_decisions (default 3).
9. Render context block with STABLE structure:
     - Sort decisions by ID (deterministic)
     - No timestamps in output (cache-stable)
     - Wrap in <codevira-context cache_key="<sha256(content)>">...</>
10. Return inject_context with token budget enforced (≤config.inject_max_tokens).
```

### Token budget enforcement at injection time

The hook function MUST cut decisions until under the token budget.
Uses a fast tiktoken estimator (or character-based proxy: ~4 chars/token).

```python
budget = config["inject_max_tokens"]  # 600
context_block = ""
for d in ranked_decisions:
    next_block = f"- **{d['id']}** {d['summary']} · `{d['file']}`\n"
    if estimate_tokens(context_block + next_block) > budget:
        break
    context_block += next_block
```

## Deletion inventory (from explore agent — 21 files, ~3800 LOC)

### Files DELETED entirely

| Path | LOC | Reason |
|---|---|---|
| `mcp_server/tools/_decision_embeddings.py` | ~695 | All chromadb + sentence-transformers for decision embedding |
| `mcp_server/cli_calibrate.py` | ~141 | Calibrate subcommand only meaningful for vector thresholds |
| `tests/test_decision_embeddings.py` | 253 | Tests for the deleted module |
| `tests/test_tools_search.py` | 489 | Tests for `search_codebase` + `prewarm` |

### Files MODIFIED (delete code branches; rest stays)

| Path | Lines (approx) | What to delete |
|---|---|---|
| `indexer/index_codebase.py` | 145-282, 539-830, 1254-1278 | `_check_search_deps`, `_get_chroma_client`, `_get_embedding_fn`, `_looks_like_chroma_corruption`, `ChromaCorrupted`, all collection mgmt in `cmd_full_rebuild` / `cmd_incremental` |
| `mcp_server/tools/search.py` | 14-75, 77-193 | `_chroma_cache`, `prewarm_embedding_model`, `search_codebase` (entire function) |
| `mcp_server/server.py` | 78, 314, 1383, 1505-1510, 1868 | `search_codebase` tool registration; prewarm call at startup |
| `mcp_server/http_server.py` | (find prewarm call site) | Delete `prewarm_embedding_model()` call |
| `mcp_server/auto_init.py` | (embedding warmup logic) | Delete |
| `mcp_server/cli.py` | 25, 1502-1532, 1828-1832 | Calibrate subparser + dispatch + import |
| `mcp_server/engine/policies/cross_session.py` | 321-370 (semantic_keep_ids gate) | Delete semantic gate; REWRITE whole policy with relevance-gated injection (see new file plan) |
| `mcp_server/tools/learning.py` | (find `embed_decision` call after record_decision) | Delete embed call |
| `mcp_server/tools/check_conflict.py` | (semantic similarity branch) | Replace with FTS5 + Jaccard text similarity |
| `mcp_server/tools/playbook.py` | (search_decisions calls) | Audit & rewire to new search |
| `mcp_server/cli_reset.py` (or in cli.py) | `--vectors` flag handling | Remove flag; `reset` now means "wipe `.codevira/` + `.codevira-cache/`" |
| `pyproject.toml` | 59-60 | Remove `chromadb>=0.5.0`, `sentence-transformers>=2.7.0`; bump version `2.1.2` → `2.2.0` |

### Tests touched (delete or rewrite)

| Path | Action |
|---|---|
| `tests/test_decision_embeddings.py` | DELETE |
| `tests/test_tools_search.py` | DELETE |
| `tests/test_index_codebase.py` (~1500 LOC chromadb portions) | DELETE chromadb portions; keep graph generation tests |
| `tests/engine/test_cross_session.py` | REWRITE — assert relevance-gated injection, 0-token off-topic, ≤600-token on-topic |
| `tests/integration/test_mcp_roundtrip.py` | DELETE `test_check_conflict_detects_conflict_with_protected` (was semantic); replace with FTS5-keyword conflict detection |
| `tests/conftest.py` | Remove embedding fixtures |
| `tests/test_auto_init.py` | Remove embedding model download simulation |
| `tests/test_doctor.py` | Remove chromadb probe; add `.codevira/` shape probe |
| `tests/test_fork_safety.py` | Remove torch dlopen tests if present |

## New file inventory (~1500 LOC additions)

| Path | Purpose | Est. LOC |
|---|---|---|
| `mcp_server/storage/__init__.py` | Package marker | 5 |
| `mcp_server/storage/jsonl_store.py` | Atomic append/read for `.codevira/*.jsonl` | ~250 |
| `mcp_server/storage/fts5_index.py` | SQLite FTS5 index over decisions; auto-rebuild on JSONL change | ~300 |
| `mcp_server/storage/manifest.py` | Read/write `manifest.yaml`; incremental update on record_decision | ~150 |
| `mcp_server/storage/digest.py` | Generate `digest.jsonl` from `decisions.jsonl` | ~100 |
| `mcp_server/storage/outcomes_writer.py` | Git observer writing `outcomes.jsonl` | ~150 |
| `mcp_server/storage/agents_md_generator.py` | Render AGENTS.md from decisions, enforce 5 KB cap | ~200 |
| `mcp_server/storage/token_estimator.py` | Fast char-based token estimator (no tiktoken dep) | ~50 |
| `mcp_server/cli_sync.py` | `codevira sync` — regenerates AGENTS.md + manifest + digest from decisions.jsonl | ~80 |
| `mcp_server/engine/policies/relevance_inject.py` | NEW UserPromptSubmit policy — replaces cross_session.py's inject logic | ~250 |

Plus 8 new test files mirroring the structure.

## Phase-by-phase implementation order

Single release v2.2.0; phases are internal-only for incremental verification:

### Phase A — Storage skeleton (Days 1-2)

Build the storage layer in isolation; no MCP wiring yet.

- Create `mcp_server/storage/` package
- Implement `jsonl_store.py` (atomic append, line-by-line read, file lock)
- Implement `manifest.py` (read, incremental update)
- Implement `digest.py` (regenerate from jsonl)
- Implement `fts5_index.py` (build, query, incremental update)
- Unit tests for each: 1 file per module under `tests/storage/`
- Verify: write 1000 fake decisions, search 100 queries; total time <2 s

### Phase B — Tool surface (Days 3-4)

Replace the storage backend for existing MCP tools.

- `record_decision`, `record_decisions` — write to `decisions.jsonl`,
  update manifest incrementally
- `search_decisions` — query FTS5; return digest-shape by default
- `list_decisions` — paginate over decisions.jsonl with filters
- `list_tags` — read manifest.yaml
- `check_conflict` — FTS5 text similarity (Jaccard or BM25); no semantic
- `supersede_decision` — append new decision with `supersedes`; update manifest
- `write_session_log`, `write_session_logs` — append session info
- `get_session_context` — assemble digest + signals (token-bounded)
- `bulk_import_phases` — same as v2.1.2
- Unit tests for each new path; reuse `tests/integration/test_mcp_roundtrip.py`
  contracts but pointed at new backend
- Verify: full MCP round-trip suite passes (G1.5 gate from v2.1.2)

### Phase C — Relevance-gated injection (Days 5-6)

Replace `mcp_server/engine/policies/cross_session.py` with new
`mcp_server/engine/policies/relevance_inject.py`.

- Read manifest.yaml + digest.jsonl on init (cached)
- Extract tags + file paths from prompt
- Score: tags (0.4) + files (0.4) + FTS5 (0.2) × outcome-weight
- Cap at `config.inject_max_decisions`
- Token-budget enforcement (cut until under budget)
- Cache-stable output structure
- Hardcoded test: 100-decision project, 5 off-topic prompts → 0 tokens;
  5 on-topic prompts → ≤600 tokens each
- Verify: new gate G2.6 in release-gauntlet (token budget enforcement)

### Phase D — AGENTS.md generation (Days 6-7)

- Implement `agents_md_generator.py` with 5 KB cap
- Preserve user content outside `<!-- codevira:* -->` blocks
- New CLI: `codevira sync` regenerates AGENTS.md + manifest + digest
- Auto-trigger sync after each `record_decision` (debounced 5s)
- Verify: 100-decision project AGENTS.md ≤5 KB; user-edited section
  outside the block preserved across regenerations

### Phase E — Delete chromadb (Day 8)

- Delete the 4 pure-chromadb files
- Strip chromadb branches from refactor files
- Update pyproject.toml; bump version
- Run full test suite; ensure no `chromadb` / `sentence_transformers`
  / `torch` imports anywhere
- Verify: `pipx install` venv size, startup latency, no native deps

### Phase E.5 — Rework operational commands (Day 8)

The current `clean` / `projects` / `doctor` commands assume the
`~/.codevira/projects/<key>/<data dir>` model. After v2.2.0 the data
authority moves to `<repo>/.codevira/` and `~/.codevira/projects/<key>/`
becomes a CACHE only (`.codevira-cache/` content sometimes mirrored here).

Concrete changes:

- `mcp_server/cli_projects.py::cmd_projects` — list projects by walking
  `global.db` (which still tracks every project codevira has seen).
  New "source" column showing where the active data lives: `repo` (has
  `.codevira/` committed), `cache-only` (legacy `~/.codevira/projects/`).
- `mcp_server/cli.py::cmd_clean` — `--orphans` and `--ghosts` keep the
  semantics; both now operate on the cache directories. A new
  `--orphan-cache` mode for cache dirs whose project no longer exists.
- `mcp_server/doctor.py` — replace `graph_db` / `codeindex_freshness`
  / `semantic_search_health` / `ghost_projects` checks with
  `.codevira_present`, `agents_md_size_ok`, `manifest_consistent`,
  `enforcement_policy_present`, `graph_cache_fresh`, `code_graph_ok`.
  Keep `python_version`, `detected_ides`, `nudge_files`, `engine_kill_switch`,
  `claude_mcp_visibility`, `crash_log_size`.
- `mcp_server/global_sync.py` — keep cross-project tracking (`global.db`)
  for discoverability, but stop syncing decisions / preferences across
  projects. Cross-project sharing is a v2.3.0 design pass.

### Phase E.6 — Bootstrap UX (Day 8)

First-install flow on an existing project (no migration; fresh start):

```
$ cd ~/Projects/myrepo
$ codevira init

  Codevira — Init
  ──────────────────
  Project: myrepo (Python detected)

  Found existing AGENTS.md (3.2 KB) — will preserve content outside
    <!-- codevira:begin --> markers.

  Found existing ~/.codevira/projects/<key>/ legacy cache (2 MB).
    → Not migrating decisions. Run `codevira archive-legacy` if you
      want to preserve them as a .codevira/legacy.jsonl reference.

  Creating:
    .codevira/decisions.jsonl     (empty)
    .codevira/manifest.yaml       (empty)
    .codevira/enforcement.yaml    (default policy)
    .codevira/config.yaml         (project settings)
    .codevira/sessions.jsonl      (empty)
    .codevira/changesets.jsonl    (empty)
    .codevira/preferences.jsonl   (empty)
    .codevira/learned_rules.jsonl (empty)
    .codevira/roadmap.yaml        (single-phase stub)
    .codevira/outcomes.jsonl      (empty)
    .codevira-cache/              (gitignored; rebuilt by `codevira index`)

  Updating:
    .gitignore                    + .codevira-cache/
    AGENTS.md                     + codevira-managed block at top

  Configuring detected IDEs (Claude Code, Cursor):
    ~/.claude.json                MCP entry added
    .cursor/mcp.json              MCP entry added

  ✓ Done. Next:
    1. Commit `.codevira/` to git (`git add .codevira/ AGENTS.md .gitignore && git commit`)
    2. Run `codevira index` to build the code graph cache
    3. Open Claude Code / Cursor; codevira is ready
```

`codevira archive-legacy` is the OPT-IN convenience for users who want
to preserve their v2.1.x decisions as a read-only reference (not
imported into the active store). It's not migration; it's a graveyard.

### Phase F — Outcome tracking from git (Day 9)

- Implement `outcomes_writer.py`: scan git log since last observation,
  classify file changes as kept/modified/reverted relative to decisions
- New CLI: `codevira observe-git` (manual trigger; later: post-commit hook)
- Update digest.jsonl `weight` field from outcomes
- Verify: synthetic git history → expected outcome counts

### Phase G — Docs + hardening + release (Day 10)

**Docs deliverables (all in same commit as code; no docs debt):**

- `README.md` — partial rewrite reflecting the lean shape:
  - Hero: "1 MB cross-IDE memory layer with active enforcement, in your repo, no cloud, no vectors, MIT"
  - "What you get" updated: drop semantic-search bullets; add "decisions in your repo as plain JSONL"
  - Disk footprint section: "≤2 MB per project, ≤55 MB pipx install"
  - Daily-use commands table: remove `calibrate`; add `sync` and `init`
  - Comparison table: how we differ from agentmemory / mem0 / momentum
    (focus on enforcement, not retrieval)
- `CHANGELOG.md` `[2.2.0]` section — comprehensive, links to `docs/plans/v2.2.0.md`
- `ROADMAP.md` — add v2.2.0 entry; mark v2.1.x as legacy
- `docs/plans/v2.2.0.md` — copy of this plan (Item 16 v2.1.2 pattern)
- `MIGRATING.md` — v2.1.x → v2.2.0 section that says "no migration; run
  `codevira init` on each project; optional `codevira archive-legacy`"
- `docs/architecture.md` — NEW. The in-repo-storage model explained
  with the directory tree diagram from this plan
- `docs/troubleshooting/antigravity.md` — UPDATE to note v2.2.0 no
  longer carries torch; issue is structurally impossible going forward

**Code cleanup deliverables:**

- Remove the 23-line `# 2026-05-19 v2.1.2 issue #10: prewarm REMOVED`
  comment block in `mcp_server/server.py` — no longer needed because
  the whole prewarm + torch pathway is gone
- Remove the `_semantic_unavailable_reason` cache in
  `mcp_server/tools/_decision_embeddings.py` (the file itself is
  deleted, but verify no other code references the global)
- Clean up `mcp_server/server.py::main()` post-init block — drop the
  embedding-prewarm try/except entirely (already gone in v2.1.2 but
  re-verify)

**Final gauntlet:**

- Run release-gauntlet (G1, G1.5, G1.6, G1.7, G2, G2.5, G2.6 must all PASS)
- Cold-install smoke updated for v2.2.0 expectations
- MCP round-trip suite updated for new shapes
- Help-text consistency linter still passes (new constants)
- Sandboxed-parent test still passes (now redundant — no torch)
- Manual dogfooding: install v2.2.0 in fresh venv, run on a real
  Sachin project, verify cross-IDE handoff (Claude Code → Antigravity)
- Tag v2.2.0; publish to PyPI

Total: 10 working days of focused work.

## Testing strategy

Hard testing taxonomy. Each layer has explicit pass criteria.

### Unit tests (per-module, fast — <30 s total)

| Module | Test file | Key cases |
|---|---|---|
| `storage/jsonl_store.py` | `tests/storage/test_jsonl_store.py` | atomic append; concurrent writers safe; read N lines; file lock contention; UTF-8 round-trip; line ≥10KB handled |
| `storage/fts5_index.py` | `tests/storage/test_fts5_index.py` | build from 1000 decisions; query <50ms; incremental add; rebuild matches full rebuild; stemming works |
| `storage/manifest.py` | `tests/storage/test_manifest.py` | incremental tag add; file add; supersede removes from active; race-safe write |
| `storage/digest.py` | `tests/storage/test_digest.py` | digest summary truncation at word boundary; weight from outcomes |
| `storage/agents_md_generator.py` | `tests/test_agents_md.py` | 5 KB cap enforced; user content outside block preserved; deterministic output across runs |
| `storage/token_estimator.py` | `tests/storage/test_token_estimator.py` | within 10% of tiktoken on 100 sample strings |
| `engine/policies/relevance_inject.py` | `tests/engine/test_relevance_inject.py` | off-topic → 0 tokens; on-topic → ≤600 tokens; deterministic ordering for cache |

### Integration tests (slower — <2 min total)

| Test | What it verifies |
|---|---|
| `tests/integration/test_mcp_roundtrip.py` | All 14 existing tests, repointed at JSONL backend, still pass |
| `tests/integration/test_token_budgets.py` | NEW — for each MCP tool, response token count under the documented budget |
| `tests/integration/test_help_text_consistency.py` | New constants (`AGENTS_MD_MAX_KB`, `INJECT_MAX_TOKENS`) match docstrings |
| `tests/integration/test_sandboxed_parent.py` | Still passes (now trivially — no native deps to fail) |
| `tests/integration/test_in_repo_roundtrip.py` | NEW — record decision → JSONL written → `git diff` clean syntax → `codevira sync` → AGENTS.md regenerated cleanly |

### E2E tests (existing, ported)

| Test | What it verifies |
|---|---|
| `tests/e2e/test_first_contact.py` | 4 fixtures, all 48 tests still pass (no chromadb in any path) |
| `tests/e2e/test_cross_tool_universality.py` | Decision in Tool A → visible in Tool B (via JSONL not chromadb) |

### Contract tests (new — for the JSONL/YAML formats)

| Test | What it verifies |
|---|---|
| `tests/contract/test_jsonl_schema.py` | Every line in decisions.jsonl validates against schema v1; rejects malformed entries cleanly |
| `tests/contract/test_manifest_schema.py` | Round-trip yaml load → save → load produces identical dict |
| `tests/contract/test_agents_md_format.py` | Generated AGENTS.md parses back into the same decisions (round-trip) |
| `tests/contract/test_cache_stability.py` | Same input → same output bytes (deterministic; cache-friendly) |

### Hard verification gates (release-gauntlet G1.5/G1.6/G1.7/G2.5 plus NEW G2.6)

| Gate | Pass criteria | Effort to add |
|---|---|---|
| **G1** Unit tests | All pass | existing |
| **G1.5** MCP round-trip | All 14+ pass against new backend | port |
| **G1.6** Help-text linter | New constants match docs | extend |
| **G1.7** Sandboxed-parent | Still passes (trivial post-chromadb-removal) | existing |
| **G2** First-contact e2e | 48 tests pass | port |
| **G2.5** Cold-install smoke | `.codevira/` ≤2 MB on test fixture; venv ≤55 MB; startup <100ms | extend smoke script |
| **G2.6 NEW** Token-budget gate | Off-topic prompts → 0 tokens; on-topic → ≤600 tokens; search ≤250 tokens digest | new test file |
| **G3** Real-IDE smoke | Still skipped (pre-existing stub) | unchanged |
| **G4** Crash log clean | 0 entries after gauntlet run | existing |

## Critical files reference

### Files to delete entirely

- `mcp_server/tools/_decision_embeddings.py`
- `mcp_server/cli_calibrate.py`
- `tests/test_decision_embeddings.py`
- `tests/test_tools_search.py`

### Files heavily refactored

- `indexer/index_codebase.py` — strip chromadb branches; keep graph + chunker
- `mcp_server/tools/search.py` — delete `search_codebase`; rewrite `search_decisions` over FTS5
- `mcp_server/tools/learning.py` — drop `embed_decision` call; keep everything else
- `mcp_server/tools/check_conflict.py` — replace semantic with FTS5 + Jaccard
- `mcp_server/engine/policies/cross_session.py` — DELETE, replaced by `relevance_inject.py`
- `mcp_server/server.py` — remove `search_codebase` tool, prewarm; add storage initialization
- `mcp_server/cli.py` — remove `calibrate` subparser; add `sync` subparser
- `pyproject.toml` — drop chromadb + sentence-transformers; bump version

### New files to create

- `mcp_server/storage/__init__.py`
- `mcp_server/storage/jsonl_store.py`
- `mcp_server/storage/fts5_index.py`
- `mcp_server/storage/manifest.py`
- `mcp_server/storage/digest.py`
- `mcp_server/storage/outcomes_writer.py`
- `mcp_server/storage/agents_md_generator.py`
- `mcp_server/storage/token_estimator.py`
- `mcp_server/cli_sync.py`
- `mcp_server/engine/policies/relevance_inject.py`
- All matching test files under `tests/storage/`, `tests/engine/`,
  `tests/integration/`, `tests/contract/`

## Existing utilities to reuse (do NOT reinvent)

- `mcp_server/_safe_crash.py::safe_log_crash` — wrap all storage writes
- `mcp_server/paths.py::get_data_dir, get_project_root` — paths resolution
- `mcp_server/_prompts.py::confirm, confirm_typed` — user prompts (no changes needed)
- `mcp_server/_project_inventory.py` — project listing (works with new schema)
- `indexer/sqlite_graph.py::SQLiteGraph` — KEEP for code graph (separate from decisions); just stop using it for decisions
- `mcp_server/engine/wiring/claude_code_hooks.py::_emit` — protocol-compatible hook output (no changes)
- `mcp_server/engine/policies/__init__.py::register_default_policies` — register new policy here
- `indexer/chunker.py::chunk_file` — KEEP for code graph; the chunker is fine
- `mcp_server/cli.py` argparse pattern — copy for new `sync` subparser
- `tests/integration/test_mcp_roundtrip.py::isolated_codevira` fixture — port to new backend

## Verification end-to-end (run before tagging v2.2.0)

```bash
# 1. Unit + integration + e2e
PYTHON=/Users/sachin/.local/pipx/venvs/codevira/bin/python make test
PYTHON=/Users/sachin/.local/pipx/venvs/codevira/bin/python make test-integration
PYTHON=/Users/sachin/.local/pipx/venvs/codevira/bin/python make test-e2e

# 2. Token-budget gate (new G2.6)
PYTHON=/Users/sachin/.local/pipx/venvs/codevira/bin/python -m pytest \
    tests/integration/test_token_budgets.py -v

# 3. Release gauntlet
PYTHON=/Users/sachin/.local/pipx/venvs/codevira/bin/python make release-gauntlet
# Expect: G1, G1.5, G1.6, G1.7, G2, G2.5, G2.6 all PASS;
#         G3 skipped (pre-existing); G4 PASS

# 4. Cold-install smoke (validates G2/G3 budgets from new gates)
PYTHON=/Users/sachin/.local/pipx/venvs/codevira/bin/python make smoke-install

# 5. Trial install in fresh venv (manual budget check)
rm -rf dist build && PYTHON=... make release-build
TMP=$(mktemp -d) && /usr/local/bin/python3.13 -m venv $TMP/venv
$TMP/venv/bin/pip install dist/codevira-2.2.0-py3-none-any.whl
du -sh $TMP/venv          # MUST be ≤55 MB
time $TMP/venv/bin/codevira --version   # MUST be <100 ms
$TMP/venv/bin/codevira --help | grep -E "calibrate|search_codebase"
# MUST return EMPTY (these commands no longer exist)
$TMP/venv/bin/python -c "import torch"     # MUST fail (ModuleNotFoundError)
$TMP/venv/bin/python -c "import chromadb"  # MUST fail

# 6. Real-corpus token verification
# On agent-mcp project (which has 100+ decisions):
cd /Users/sachin/Documents/Projects/LogisticsOS/agent-mcp
codevira init                              # creates .codevira/
codevira migrate-decisions --from-graphdb  # one-time bootstrap from v2.1.2 data
du -sh .codevira                           # MUST be ≤2 MB
du -sh AGENTS.md                           # MUST be ≤5 KB
# Trigger UserPromptSubmit hook with off-topic prompt:
echo '{"hook_event_name":"UserPromptSubmit","prompt":"how do I make a cake"}' \
    | codevira engine handle UserPromptSubmit
# additionalContext MUST be null or empty (≤50 tokens explanatory only)
# Trigger with on-topic prompt:
echo '{"hook_event_name":"UserPromptSubmit","prompt":"implement bcrypt for password hashing"}' \
    | codevira engine handle UserPromptSubmit
# additionalContext MUST contain ≤3 decisions, ≤600 tokens
```

## Open design decisions (resolved in plan)

### AGENTS.md regeneration trigger

**Decision: regenerate on three events, no background daemon.**

1. After every `record_decision` / `record_decisions` / `supersede_decision`
   call returns successfully — synchronous, in the MCP tool path
2. After every `codevira sync` CLI invocation (manual + post-commit hook
   if user installs one)
3. After every `codevira init` (initial creation)

Why synchronous, not debounced: the regeneration is fast (≤20 ms on a
1000-decision corpus per measurement target), the file is small (5 KB
cap), and synchronous makes the contract simpler ("after record_decision
returns OK, AGENTS.md is up to date"). No background thread means no
"why didn't my AGENTS.md update?" debugging.

Failure mode: if regeneration fails (disk full etc.), the decision is
STILL persisted to `decisions.jsonl` (atomic write happened first), the
MCP tool returns success with a `_warning` field noting AGENTS.md stale,
and a `codevira sync` retry fixes it.

### Token estimator

**Decision: char-based proxy (no tiktoken dependency).**

Formula: `tokens ≈ char_count / 4` (validated empirically within 10% of
tiktoken across English + code samples). Faster, no extra dep, no
versioning concerns. Validated by a unit test that compares against
tiktoken on 100 fixtures (tiktoken is a dev dep only, not runtime).

If a user wants exact token counts they can set `CODEVIRA_TOKEN_PRECISION=exact`
which dynamically imports tiktoken at injection time only. Optional.

### `enforcement.yaml` write-time UX

**Decision: codevira init writes the default file with comments
explaining each knob; the file is committable; users edit by hand.**

No MCP tool to mutate enforcement.yaml — that would invite the AI to
relax constraints on itself. User must commit edits explicitly.

### Decision ID generation

**Decision: monotonic base-36 starting `D000001` (zero-padded 6 digits → 36^6 ≈ 2B max).**

Generated by reading the last line of `decisions.jsonl` + incrementing
the suffix. Atomic with the append (single file lock). No UUIDs (too
long for human reading); no sequence number leaks across projects
(each project starts at `D000001`).

Collision risk on parallel commits: vanishingly small if both branches
last-id-incremented from different starting points. JSONL line uniqueness
enforced by a contract test on `git merge`; conflict resolution
documented in `MIGRATING.md` as "re-run `codevira renumber`".

### Append-only constraint (decisions.jsonl, outcomes.jsonl, sessions.jsonl, changesets.jsonl)

**Decision: write-once-then-only-append. Mutations (mark_protected,
supersede) write NEW lines.**

`supersede_decision` appends a new decision with `supersedes: D000123`
pointing at the old. The old line is never modified. List/search code
respects `supersedes` chains and hides the old by default. This keeps
the file format git-friendly (no mid-line edits = clean diffs).

The single exception: `mark_decision_protected` (which flips
`do_not_revert`) appends an "amendment line" with the same ID and a
`_amendment_to_id` field. List code applies amendments in order.
Documented in the JSONL schema.

### Storage of code-graph SQLite file

**Decision: `<repo>/.codevira-cache/graph.sqlite` (gitignored, per-machine).**

Each developer rebuilds via `codevira index` after clone. Not committed
because:

- Binary file, terrible git diffs
- Machine-specific paths inside (we'd have to normalize)
- Rebuilds quickly (a few minutes for the largest projects we've seen)
- Per-developer file-hash cache is more accurate when fresh

### `codevira reset` semantics

**Decision: in v2.2.0, `--vectors` flag is REMOVED entirely (no vectors).**

`reset` now means:

- `codevira reset --cache` (alias: `--rebuildable`) — wipes
  `.codevira-cache/` only; safe; `codevira index` rebuilds
- `codevira reset --all` — wipes `.codevira/` AND `.codevira-cache/`;
  destructive; AUTO-EXPORTS `decisions.jsonl` to
  `<data_dir>/exports/<timestamp>-pre-reset.jsonl` (per v2.1.2 Item 3 pattern)
- `codevira reset --graph` (alias from v2.1.2) — same as `--all`;
  deprecation warning suggesting new flag name

`codevira reset --vectors` prints a friendly explanation that vectors
no longer exist + suggests `--cache` if they wanted to rebuild the
graph cache.

## Risks

| Risk | Likelihood | Mitigation |
|---|---|---|
| FTS5 recall worse than chromadb on concept queries | MEDIUM | Accept the trade-off (decisions are short text; FTS5 with stemming is adequate). Measure on real corpus. If recall drops >20% on labeled test set, add lightweight Jaccard expansion. |
| In-repo JSONL causes merge conflicts | LOW | Append-only with monotonic IDs eliminates most conflicts. Conflicts that DO occur are line-level and trivial to resolve. |
| Users on v2.1.x cannot upgrade | EXPECTED (non-goal) | Documented in README. Optional `codevira migrate-decisions` script for those who want it. v2.1.x stays on PyPI. |
| AGENTS.md generation overwrites user edits | MEDIUM | Strict `<!-- codevira:begin -->` / `<!-- codevira:end -->` boundaries; anything outside is preserved verbatim. Test: round-trip user content. |
| Removing semantic search breaks check_conflict for users who rely on it | LOW | Replace with FTS5 + Jaccard; document the change in CHANGELOG. The exact semantic check_conflict was added in v2.1.2 (1 release lifetime). |
| Engine policy delete breaks existing hook flows | MEDIUM | Keep the same `inject` verdict shape; only change WHAT gets injected. All hook wiring (`claude_code_hooks.py`) untouched. |
| FTS5 index gets out of sync with decisions.jsonl | LOW | Rebuild on startup if file mtime > index mtime; explicit `codevira sync` regenerates. |
| Token estimator inaccurate vs tiktoken | LOW | Use 4 chars/token proxy; validate within 10% of tiktoken on a sample set as a unit test gate. |
| User edits decisions.jsonl by hand and corrupts a line | MEDIUM | Schema-validate on every read; bad lines logged + skipped (not crash); `codevira doctor` flags malformed lines. |
| Two MCP server subprocesses race on append to decisions.jsonl | MEDIUM | File-lock via `fcntl.flock` (posix) / `msvcrt.locking` (Windows); short critical section (just the append). Existing append-only pattern handles concurrency cleanly. |
| User has gigantic AGENTS.md with non-codevira content | LOW | Strict marker preservation (`<!-- codevira:begin --> ... <!-- codevira:end -->`); content outside is byte-for-byte preserved. Unit test: 1000-line user AGENTS.md round-trips identically. |
| Pillar 3 skills reference chromadb / semantic search in their text | LOW | Grep skills dir; update any references; test_help_text_consistency.py extends to skills. |
| Test suite explosion (we add ~30 new tests, delete ~20 old ones) | MEDIUM | Strict naming: tests/storage/, tests/contract/, tests/integration/test_token_budgets.py — separate dirs so it's clear what's new. |
| `codevira doctor` checks reference old `~/.codevira/projects/<key>/` layout | HIGH | Phase E.5 explicitly reworks doctor; new probes for `.codevira/`. Test: empty project (no init yet) → doctor says "run codevira init"; init'd project → all green. |
| Cross-project decision discovery breaks (global.db queries) | MEDIUM | `global.db` keeps cross-project project listing (for `codevira projects`). Cross-project DECISION sharing was a v2.1.2 Item 9 concern; in v2.2.0 it's explicitly per-project (each repo's `.codevira/` is its own scope). Document the change. |
| Watcher running on every save still does work even though chromadb is gone | LOW | Watcher's only remaining job: refresh `.codevira-cache/graph.sqlite` for the changed file. Already lightweight; chromadb removal makes it FASTER. Verify single-file-change incremental finishes in <100ms. |

## What we're NOT including (anti-scope, repeated for emphasis)

- ❌ Multi-language code graph (v2.3.0)
- ❌ Backend adapters for agentmemory / OMEGA / mem0
- ❌ Cloud sync / telemetry / dashboard
- ❌ Enhanced enforcement UX (better conflict messages — v2.3.0)
- ❌ Anything that adds tokens to default session injection
- ❌ Plugin SDK
- ❌ Web UI

If a request doesn't match Goals G1-G10 and doesn't pass the
verification gates, it's out of scope for v2.2.0.

---

