# v1.14.0 — Codebase Graph + Context Composer + CLI Router (2026-05-21)

The closing milestone of the **Complete Context System** roadmap.
With v1.12 (stories), v1.13 (RAG) and v1.14 in place, every
`openlife ask` automatically injects:

1. relevant stories (matched on title/cluster/AC text),
2. relevant code chunks (embedding similarity), and
3. relevant symbols (codebase graph),

— all budgeted within the model's context window. The user picks
which CLI (and which tier inside it) does the answering, via a
zero-config first-run flow.

## What landed

### `src/orchestrator/graph/` — Codebase graph

| File                       | Role                                             |
|----------------------------|--------------------------------------------------|
| `TreeSitterRegistry.ts`    | Lazy grammar loader (ts/tsx/js/py)               |
| `SymbolExtractor.ts`       | Top-level functions/classes/methods/types/consts |
| `DependencyExtractor.ts`   | Import / re-export / require edges (resolved)    |
| `GraphStore.ts`            | SQLite-backed (same `codebase.db` as RAG)        |
| `GraphBuilder.ts`          | Walk → extract → persist                          |

### `openlife graph <verb>` — CLI

```
openlife graph build [--root <p>]
openlife graph status
openlife graph find <name>
openlife graph callers <name>
openlife graph deps <file>
openlife graph usages <name>
```

### `src/orchestrator/context/` — Multi-source composer

| File                | Role                                                     |
|---------------------|----------------------------------------------------------|
| `ContextSource.ts`  | Unified `SourceHit` shape                                |
| `ContextBudget.ts`  | `tiktoken` (cl100k_base) counter + greedy packer         |
| `ContextComposer.ts`| Parallel `story+rag+graph` retrieval, cross-source rerank |

The composer plugs into `Gatekeeper.handleFastPath` at
`src/orchestrator/Gatekeeper.ts:266-282`, inserting a "Contexto do
projeto" block next to the existing "Contexto relevante" slot.
Disabled via `OPENLIFE_CONTEXT_COMPOSER=off`.

### `src/orchestrator/router/` — Per-CLI tier router

| File                            | Role                                          |
|---------------------------------|-----------------------------------------------|
| `ProviderCapabilityCatalog.ts`  | Static roster per CLI (claude/gemini/codex/ollama) |
| `ProviderProbe.ts`              | `--version` probe with 24h-cached availability |
| `ModelTierRouter.ts`            | `pick(task)` honoring mode + preference        |

Inference modes (env: `OPENLIFE_INFERENCE_MODE`):

| Mode                 | Allowed providers                                        |
|----------------------|----------------------------------------------------------|
| `cli-only` (default) | `claude` / `gemini` / `codex` / `ollama` (whichever signed-in) |
| `hybrid`             | CLIs preferred; falls back to API or local                |
| `api-only`           | Only API providers                                       |
| `local-only`         | Only `ollama`                                            |

When no CLI is available under `cli-only`, `openlife ask` prints
a remediation message and exits non-zero — **no silent API
fallback**.

### `openlife provider <verb>` — CLI

```
openlife provider list
openlife provider detect
openlife provider doctor
openlife provider setup
openlife provider use <provider> [--fast <m>] [--smart <m>]
openlife provider models <provider>
openlife provider set-mode <mode>
openlife provider set-preference <list>
```

### Zero-config first run

`openlife ask` calls `ModelTierRouter.ensureConfigured()` once
before invoking the gatekeeper. If `~/.openlife/router.json`
doesn't exist, it auto-detects available CLIs and prints a single
line:

```
[OPENLIFE] Detected: claude (haiku, sonnet, opus) ✓
[OPENLIFE] Using anthropic (sonnet for answers, haiku for retrieval).
[OPENLIFE] Change anytime with: openlife provider setup
```

No interactive prompt, no env vars touched. The user can re-tune
anytime via `openlife provider setup`.

### Tests

| Test                          | What it asserts                                              |
|-------------------------------|--------------------------------------------------------------|
| `test_codebase_graph.ts`      | TS + Python symbol/dependency extraction; GraphStore round-trip |
| `test_context_composer.ts`    | Budget enforcement, cross-source weighting, opt-out, error isolation |
| `test_model_tier_router.ts`   | Catalog/tier/preference/mode behavior, env override, ensureConfigured idempotence |

Wired into `test:all` and the new `test:context` aggregator.

### Dependencies

Added: `tree-sitter`@0.21, `tree-sitter-typescript`,
`tree-sitter-javascript`@0.21, `tree-sitter-python`@0.21,
`tiktoken`.

## Migration notes

- **No breaking changes** to existing CLI/HTTP/Bridge surfaces.
- Composer is opt-out, not opt-in: existing `openlife ask` flows
  start getting richer prompts immediately. Set
  `OPENLIFE_CONTEXT_COMPOSER=off` to keep the v1.13 behavior.
- The router only activates when a CLI is actually signed in.
  Existing API-key chains (`models.json`) still work in
  `hybrid` / `api-only` modes.

## What's next

- **Cross-machine bridge** — daemon-hosted RAG/graph, so a thin
  client can query a remote agent's index without re-indexing locally.
- **LSP-backed graph** as an optional provider for languages where
  tree-sitter coverage is thin.
- **Auto-watch indexing** — file watcher that keeps the index hot
  during a coding session.
