# Entity intelligence and observation streams

Entity commands answer questions about **functions, methods, classes, and
data-format keys** rather than about files and line ranges. They track an
entity across renames, moves between files, and container changes, so "what is
the history of this function" is answerable even when the file it lives in was
renamed twice.

The entity commands all accept `--format json` (compact by default; the global
`--pretty` flag indents it), keep stdout clean in JSON mode,
and send diagnostics to stderr; the observation streams below (`occ atoms`,
`occ code coordinate-verify`, `occ code graph-export`) are NDJSON-only and take
no `--format` flag.

## Observation substrate and coordinate roles

The first-parent entity index's event stream (`EntityIndex.events`) records
lineage evidence for entity history, hotspots, and entity co-change. `occ atoms`
separately observes every parent edge and builds a run-scoped branch-aware
overlay, then maps its pinned heads back to the first-parent index where the
exact state key resolves uniquely. Both are observed matching hypotheses—not
an intended architecture or boundary hierarchy.

Keep the three coordinate roles distinct:

- `{ oid, path, span, span_digest }` is the native durable identity. The Git
  object is immutable, the path and span locate the evidence, and the digest
  verifies the LF-normalized span. Persist this coordinate.
- `dag1:*` is derived all-parent correlation. It is internally consistent
  within one emitter run, but matcher or policy changes may change it.
- `ent1:*` is the existing first-parent entity lineage. Persist it only for its
  declared entity-index role; a resolved `lineage-head` record relates it to a
  live `dag1:*` hint at one pin. The mapping is answered by the local
  first-parent entity index, so `entity_lineage_id` values (and `resolved`
  versus `not_found`) can vary with that cache's indexed window; only the
  Git-native coordinate is machine-independent.
- `nodeId` belongs to a checkout-specific current graph. Use it for current
  queries and targeted body reads, but never persist it as historical identity.

Pinned blob calls retain unresolved syntactic names; current checkout graphs
connect resolved node ids. Consumers must know which projection they received.
If a stored Git object/span cannot be acquired or its digest cannot be
reproduced, that is unavailable evidence—not a no-op.

Verify persisted coordinates without checking out their revision:

```bash
occ code coordinate-verify --path . --ref <pin> < coordinates.ndjson
```

Each request is `{ "request_id", "coordinate" }`. A result is `verified`,
`mismatch`, or `unavailable`; it includes the observed digest and ref OID when
available, but never the selected source bytes. A mismatch is a complete
negative verification, while unavailable means the check could not be made.

## `occ atoms`

Emit one candidate work episode for every parent-child edge in the bounded Git
DAG. Output is strict NDJSON: header, diagnostics when needed, episodes, pinned
`lineage-head` mappings, and a coverage footer. An episode has both before/after
Git coordinates, change and semantic classes, matcher rung evidence, gross
added/deleted patch-payload token/byte/line measures, stable patch id, exact
revert edge, and changed directory-module pairs. `module_pairs_touched` is not a
static seam. The stream contains no commit message or source body;
`message_ref` points back to Git.

```bash
occ atoms --path . --ref "$(git rev-parse HEAD)" > atoms.ndjson
occ atoms --path . --ref HEAD --max-commits 2057
occ atoms --path . --resume '<last-episode-cursor>' > atoms-resumed.ndjson
```

Every merge produces one episode per parent, in parent order. A bounded first
pass builds a lifetime-aware state overlay across all branch and merge edges:
unchanged matches advance coordinates through unrelated file edits, identical
branch states converge, and delete/re-add lifetimes remain distinct. The
emission pass then assigns deterministic `dag1:*` ids. `--since` and a cursor
filter output only after that warm replay, so correlation at one pin does not
depend on which prefix was emitted. Missing or partial coverage is a reason to
qualify the import, not infer absence. Use `collectAtomsRun()` to reject an
interrupted or internally inconsistent stream. Each resumed invocation is its
own header/footer-framed segment; do not concatenate two framed receipts into
one NDJSON receipt.

Pass `--require-complete` when partial footer coverage must produce a nonzero
exit without changing the NDJSON stream.

The current atoms stream and collector are V2. Fresh streams pin parser@3 and the
evidence-ladder V2. `collectAtomsRunV1()` remains available for stored receipts,
and parser@2 V2 receipts remain decodable, but V1 and parser@2 cursors are
deliberately rejected for resume; begin a fresh V2 run.

For version-zero import, build checkout-derived current/document indexes from a
clean checkout of the same pin. Use body-free indexes plus targeted inspection;
`bundleWorkspace` materializes content and is not the core import path. OCC only
observes and nominates tests; the consumer executes them.

## `occ code graph-export`

Export the whole bounded entity graph for boundary discovery at a pin:

```bash
occ code graph-export --path . --ref "$(git rev-parse HEAD)" > entity-graph.ndjson
```

Nodes are every bounded lineage observed in the run and resolved live nodes may
also carry their first-parent `entity_lineage_id`. Explicit seam records derive
from pinned cross-module imports and calls, including import-only seams;
inheritance remains directed incidence only. Unified entity-pair edges carry
logic co-change count/lift over the full parent-edge denominator, directed
call/inheritance incidence, and membership only in declared seams. Historical
structure and every unresolved or bounded fact fail closed. Use
`collectEntityGraph()` before consuming the receipt. OCC supplies structure;
clustering, relevance, ranking, and boundary learning belong to the consumer.

The current graph receipt is V2 and pins `occ.atoms@2` plus
`occ.git-blob-structure@2`. `collectEntityGraphV1()` remains a read-only path
for persisted V1 receipts.

## When to reach for these

| You want to know                                                               | Command                    |
| ------------------------------------------------------------------------------ | -------------------------- |
| What entities exist, and their durable ids                                     | `occ code entities`        |
| Which current entities changed logic most often                                | `occ code hotspots`        |
| Which current entities historically change logic together                      | `occ code cochange`        |
| Which documentation sections predate proved changes to what they name          | `occ code drift`           |
| Whether externally supplied documents (wikis, specs) predate proved changes    | `occ code drift-documents` |
| Which entities a change touched, and which ones have proved logic drift        | `occ code diff --entities` |
| What breaks if I change this, and which tests cover it                         | `occ code impact`          |
| Who last observably changed each entity without assuming formatting is a no-op | `occ code blame`           |
| How did this function get to its current form                                  | `occ code log`             |
| Give me this function plus its callers, under a token budget                   | `occ code context`         |

`occ code log` is not `occ code history`. `history` shows git line history for
a symbol's current range; `log` follows the **entity** across renames and moves.

## Exit codes

A miss (`not_found`) or an ambiguity (`ambiguous`) is a normal answer and
**exits 0** — an entity that simply is not there should not break a pipeline.
Only a genuine command or input failure exits nonzero. Pass `--strict` to make
a non-resolved result exit 1 for scripts that want that. (`occ code entities`
returns a bare match list rather than a resolution status, so `--strict` has
no effect there — an empty result still exits 0.)

This deliberately differs from `occ code source`, `occ code symbol-hash`, and
`occ code seams --resolve`, which treat non-resolution as failure.

## `occ code entities [query]`

Search entities by substring of their qualified name. Each result carries a
`lineageId` — the id that survives renames and moves.

```bash
occ code entities buildRepoMap --path . --format json
occ code entities buildRepoMap --path . --no-envelope --format json
occ code entities --kind class --limit 20 --path .
occ code entities --file src/code/map.ts --path .
occ code entities config --no-formats --path .     # code only, no JSON/YAML keys
```

Data-format entities (JSON, YAML, TOML, CSV, Markdown) report extraction
honesty: a file that failed to parse but still yielded entities is
`recovered` with `definitions: partial` and a `parse-failed` diagnostic, while
a successfully parsed empty file stays `valid`/`full`. YAML list items carry
ordinal paths such as `jobs.build.steps[0]`, so repeated entries stay distinct.

| Flag            | Meaning                                              | Default |
| --------------- | ---------------------------------------------------- | ------- |
| `--kind <kind>` | Filter by entity kind                                | none    |
| `--file <path>` | A file, or a directory to list every entity beneath  | none    |
| `--limit <n>`   | Maximum results                                      | 50      |
| `--no-tests`    | Exclude entities recognized as tests                 | off     |
| `--envelope`    | Emit measured match, freshness, and evidence honesty | on      |
| `--no-envelope` | Emit the legacy bare JSON entity array               | off     |

JSON emits the envelope by default; pass `--no-envelope` for the legacy bare
array. The library keeps the distinction explicit:
`session.search()` keeps the array, while `await session.searchDetailed()`
returns total matches, omissions, freshness, coverage, ambiguity, and bounded
wide evidence gaps. MCP `occ_entities` always returns the envelope and keeps
`count` as the returned-row count. Gate the envelope on
`health.features?.['code.entities.envelope.v1'] === true`.

## `occ code hotspots`

Rank current code entities by logic-changing lineage events multiplied by source
weight (`tokenCount`, or line span when token counts are unavailable).

```bash
occ code hotspots --limit 20 --format json
occ code hotspots --file src/code/entity --since 2026-01-01 --page-rank
```

`--file` accepts a file or a directory (ranks every entity beneath it);
`--limit` defaults to 20.
`cosmeticChangeCount` remains numeric in JSON for persisted compatibility but
current entity-record evidence cannot newly prove it; text labels the field as
compatibility data rather than whole-diff proof. Pure moves, renames, and reorderings are
neutral; changes whose hash evidence cannot distinguish logic from formatting
remain in `unknownChangeCount`.
`--page-rank` multiplies by `1 + normalizedPageRank/100`. The JSON honesty
block reports measured freshness, truncation reasons and omissions,
commits/events considered, unknown changes, entities born at an indexed-window
boundary, ambiguity, and the first five wide evidence-gap diagnostics plus the
total gap count.
Gate on `health.features?.['code.entity.hotspots'] === true`.

## `occ code cochange [entity]`

Find current code entities whose proved logic changes occurred in the same
commits. This is entity-level temporal coupling: it works inside one file,
requires no import edge, and survives file/symbol renames because grouping uses
durable lineage ids.

```bash
occ code cochange --min-count 3 --min-confidence 0.5 --format json
occ code cochange buildRepoMap --file src/code/map.ts --format json
```

A named query returns partners for that entity. A bare query returns the top
repository pairs. Each row has `a`, `b`, `cochangeCount`, `aChangeCount`,
`confidence` (`cochangeCount / aChangeCount`), and `lastCoChange`. The result's
`windowCommits` states the indexed commit sample. Cosmetic changes, pure
moves/renames, bounded-window seeds, and unknown events do not create coupling;
the honesty block reports those filters, any broad commits skipped by
`--max-commit-entities` (default 500), result-limit omissions, measured
ambiguity, and bounded wide evidence gaps. The pair threshold defaults to
`--min-count 3`; use `--min-confidence 0..1` for directional strength.
`--limit` defaults to 20. `--file` and `--kind` disambiguate the _named_
entity and are an error without one — omit all three for the repository
leaderboard. Gate on
`health.features?.['code.entity.cochange'] === true`.

## `occ code drift`

Join Markdown prose to declarations, then compare each section's last-touch
commit order with later proved logic events.

```bash
occ code drift --format json
occ code drift --dirs docs/,README.md
occ code drift --dirs docs/ --exit-code
```

Fenced examples are excluded. Bare backtick identifiers resolve only to a
unique exported, non-test declaration; use repository-relative
`path#symbol` to name an internal declaration explicitly. Missing or ambiguous
references are refused and counted. Read `honesty.coverage.linksRefused`, its
`linksAmbiguous`/`linksNotFound` split, and `allRefusedDocuments` before
interpreting an empty `rows` array.

Rows contain the document path/span, current entity ref, document last-touch
commit, and subsequent change count with strongest evidence
`cst-proved|body-hash|unknown`. Zero-change rows are `unknown`, and that tier
will usually dominate. `--dirs` defaults to `docs/,README.md`; `--exit-code`
fails only when a row has one or more subsequent `cst-proved` changes. MCP is
deferred. Gate on `health.features?.['code.drift.v1'] === true`.

For generated/private Markdown, use the programmatic
`session.driftForDocuments({ documents })` surface or stream one
`{id, content, baseCommit}` object per line:

```bash
printf '%s\n' '{"id":"wiki://guide","content":"# Guide\nUse `buildRepoMap`.","baseCommit":"HEAD~1"}' |
  occ code drift-documents --path .
```

References resolve against entity state at the supplied commit. Invalid or
out-of-window commits refuse that document; there is no current-HEAD fallback.
Rows carry `entityAtBase`, optional `entityAtHead`, and later proved logic
events. Documents do not need repository paths, duplicate ids remain
positional, and the command does not read document or code bytes from the
working tree. Defaults are 1,000 documents / 16 MiB total content
(`--max-documents`, `--max-total-document-bytes`), with hard ceilings of
10,000 / 256 MiB; exceeding an effective bound rejects the whole batch. Read every document's honesty envelope plus the footer. Gate on
`health.features?.['code.drift.documents.v1'] === true`.

## `occ code anchor` / `occ code verify-anchors`

Capture entities as verifiable memory anchors, then grade stored anchors
against lineage evidence. OCC validates the memory and stores nothing.

```bash
occ code anchor buildRepoMap --file src/code/map.ts --format json
printf '%s\n' '{"id":"m1","lineageId":"ent1:…","baseCommit":"abc123"}' |
  occ code verify-anchors --path . --strict
```

Capture (exactly one of `<entity>`, `--lineage-id`, `--node-id`) emits the
durable `lineageId`, a `symbolKey`, and an `expected` hash tuple projected from
the live record; `baseCommit` appears only when the working-tree bytes equal
the committed head record and the head has not moved past the index — a dirty
entity degrades to an expected-only anchor with `uncommittedContent: true`
rather than stamping a commit that never contained those hashes.

Verification input is NDJSON, one anchor per line, at least one of `lineageId`
or `symbolKey` — capture output verifies verbatim (`{"id":"m1", ...anchor}`),
with the lineage id authoritative and the symbol key a fallback — plus an
optional `baseCommit` (event lane, must be inside the indexed window) and an
optional `expected` (live-hash lane — strictly stronger; the normalized tier
cannot be rebuilt from replay). Verdicts: `fresh`, `normalized-equal`,
`semantic-drift`, `moved`, `renamed`, `deleted`, `unknown` (+`unknownReason`).
`moved`/`renamed` are net base-vs-head state — a rename-and-revert nets to
identity. Proved equality (a stored tuple, or committed endpoint bodyHash
equality) beats event-lane content inference; `deleted` needs a proved end
event and is baseline-independent. A live entity with nothing to verify
against refuses as `unverifiable-anchor`. Both surfaces reconcile the live
index against the working tree before minting working-tree claims; an
unmeasurable borrowed session degrades hash verdicts to
`unknown`/`index-stale`.
`--strict` exits 1 on refusals only; `unknown` is a resolved answer, gated by
`--fail-on-unknown`. Bounds: 1,000 anchors default (hard 10,000), 64 distinct
base commits per batch (memoized replays; tuple-carrying anchors past the
budget degrade to the hash lane). A batch with no tuples opens only the
lineage store. Gate on `health.features?.['code.anchors.verify.v1'] === true`
and `['code.entity.anchor.v1']`.

**The consolidation recipe** (a memory system's outer loop, four receipts each
with its own honesty — deliberately not one flattened envelope):

1. `occ code diff --entities --base <last-session-commit>` — what changed,
   with lineage ids for everything renamed/moved/deleted.
2. `occ code verify-anchors` — which stored anchors went stale, and how.
3. `occ code drift-documents` — which stored prose notes predate proved logic
   changes.
4. `occ code seams --resolve <stored-seam-keys>` — which module-boundary
   handles still resolve.

## `occ code diff --entities`

Entity-level diff plus an authoritative whole-diff proof. Without `--entities`
the command behaves exactly as before, so this is additive.

```bash
occ code diff --entities --base main --format json
occ code diff --entities --staged --path .
occ code diff --entities --file src/code/map.ts --exit-code
occ code diff --obligations --base HEAD~3 --format json
occ code diff --entities --base HEAD~3 --path .
occ code diff --entities --base <tree-a> --head <tree-b> --format json
```

Each change carries a `changeType` — `added`, `modified`, `modified_logic`,
`deleted`, `moved`, `renamed`, `reordered`, or `orphan` — plus
`structuralChange`, the previous and current identity, and the confidence and
reason behind the lineage match. `modified_cosmetic` remains decodable for old
receipts but is not newly emitted.

The legacy `semantic` summary describes emitted entity records only. Normalized
and structural hashes are lineage heuristics, never no-op proof. Comments can
contain compiler/build/type/lint/purity directives, and entity extraction
cannot account for code between declarations, so uncertain changes are
`modified`/unknown rather than newly classified as cosmetic.

Read `wholeDiffSemantic` for the authoritative result:

| Whole-diff evidence                                     | Verdict           | Coverage                |
| ------------------------------------------------------- | ----------------- | ----------------------- |
| Any proved non-trivia CST/topology drift                | `logic-change`    | complete/partial        |
| Safe whitespace-only or ordinary comment-only CST drift | `cosmetic-change` | `complete`              |
| Only cosmetic rows, or exhaustive empty raw diff        | `semantic-no-op`  | `complete`              |
| Directive/BOM/unsafe whitespace/scope gap               | `unknown`         | `partial`               |
| Unsupported, missing, bounded, or classifier error      | `unknown`         | `partial`/`unavailable` |

Every raw changed-file record participates regardless of `--no-formats` or
entity budgets. `gitScope` records the exact resolved physical endpoints,
included Git layers, path filter, and untracked policy. “Logic change” means
proved non-trivia structural source drift, not observational inequivalence.
Relevant `assume-unchanged` or `skip-worktree` paths make a worktree-inclusive
scope partial with `index-visibility-hidden`; committed and staged-only scopes
are unaffected.

Persisted lineage CST receipts are admitted only after the current process
physically re-verifies the grammar WASM named by the checked-in manifest. A
failure remains unknown and reports `grammar-verification-refused`; gate this
behavior on `health.features?.['code.entity.lineage.grammarReverify'] === true`.

`--file <path>` scopes the ledger before proof. Ordinary `--exit-code` exits
nonzero only when `wholeDiffSemantic` reports `logic-change`; `unknown` remains
zero, so zero is not a no-op proof. Under `whole-file-cst-v4`, proved safe
whitespace and ordinary comment rows can aggregate to
`semantic-no-op/complete`. Directive comments block only cosmetic admission —
trivia-only edits beside one stay unknown while proved non-trivia drift is
still `logic-change` — and BOM transitions, invalid encoding, and whitespace
in a grammar outside the pinned safe policy remain unknown. V1-V3 proof names remain decodable, but none satisfies the V4 health
gate or gains V4 classifications retroactively. Strings and template-literal
content remain syntax tokens. These verdicts prove bounded syntax properties,
not observational program equivalence.

Text output keeps the same proof visible without requiring a JSON detour: it
prints the Git comparison/layers/untracked policy, a bounded tally of unknown
evidence bases, and the first five unclassified paths followed by the omitted
count. Use JSON when every per-file receipt is needed.

`--obligations` implies entity diff and adds
`{gaps, seamCrossings, split, coverage, evidenceGaps}`.
Gaps are logic-changed entities for which a complete reverse call BFS reaches
no test; partial graph evidence produces a diagnostic and no false gap.
`seamCrossings` contains current `seamKey`s touched along those reverse impact
paths. `split` retains its numeric logic/cosmetic fields in JSON for
compatibility, but current evidence cannot newly prove the cosmetic bucket and
text omits it. `coverage` is complete
only when no change was skipped and every impact traversal was full;
`evidenceGaps` gives bounded names, paths, reasons, and the total skip count. In
this mode, `--exit-code` fires only when `gaps` is non-empty. Add
`--fail-on-unknown` to fail on an absent/unknown whole-diff verdict or partial
obligation coverage.

For a gap, `nearestTestedAncestor` is `null`: the complete traversal found no
test. Document obligations remain absent until an explicit document-edge list
can be injected; OCC does not return an empty placeholder or infer coverage
from names.

Both endpoints may be arbitrary commits or bare tree object ids. When they have
no merge base, OCC compares the exact endpoint trees and reports
`range.kind: "two-dot"`; callers do not need to manufacture shared ancestry.
`--range-mode exact` forces that endpoint-tree comparison even when a merge
base exists; the default `merge-base` diffs from the common ancestor.

## `occ code impact <entity>`

What a change reaches, including tests found by **reverse call traversal**
rather than filename similarity.

```bash
occ code impact buildRepoMap --scope tests --path . --format json
occ code impact createUser --scope all --depth 3 --path .
occ code impact Cache.get --file src/cache.ts --inline-context 1 --path .
```

| Flag                 | Meaning                                 | Default |
| -------------------- | --------------------------------------- | ------- |
| `--scope <scope>`    | `deps`, `dependents`, `tests`, or `all` | `all`   |
| `--depth <n>`        | Graph hops; `0` is unbounded            | 2       |
| `--max-entities <n>` | Stop after this many                    | 500     |
| `--kind`, `--file`   | Disambiguate the entity                 | none    |
| `--inline-context`   | Verified source lines around each ref   | 0       |

Check `coverage`. `partial` means OCC could not prove an exhaustive traversal:
a data-format key has no graph node, an edge may be unresolved, a resolved
edge's endpoint may be absent from the node/live index (`unreachableEdges`), or
the target or a traversed file has non-full parser call/exact-span provenance.
In all of those cases an empty dependents list means "unknown", not "nothing".

Each entry in `tests` includes `selector.file`, its literal `title`, enclosing
`suites`, and `fullTitle`. When imports identify Vitest, Jest, Mocha,
`node:test`, or pytest unambiguously and the installed runner can be resolved,
it also includes an execFile-ready
`command: { executable, args, cwd }`, for example:

```json
{
  "file": "test/cache.test.ts",
  "title": "rejects stale entries",
  "suites": ["cache"],
  "fullTitle": "cache rejects stale entries",
  "framework": "vitest",
  "command": {
    "executable": "/usr/local/bin/node",
    "args": [
      "/repo/node_modules/vitest/vitest.mjs",
      "run",
      "test/cache.test.ts",
      "-t",
      "^cache rejects stale entries$"
    ],
    "cwd": "/repo"
  }
}
```

Invoke it as
`execFile(command.executable, command.args, { cwd: command.cwd })`; all paths
needed by the process are resolved and no shell parsing is required. When OCC
can identify a framework but cannot find its installed runner, it preserves the
portable title selector and omits `command` instead of emitting a bare name.
The compact text output quotes the descriptor only for human copy/paste.

## `occ code blame <file>`

One entry per entity, attributed to the last change that altered its
**meaning**. Cosmetic edits, pure moves, and reorderings are skipped, so a
reformat does not reassign authorship of an entire file.

```bash
occ code blame src/code/map.ts --path . --format json
occ code blame src/cli.ts --ref HEAD~5 --path .
```

Every entry carries a `state`: `committed`, `working-tree-modified`,
`uncommitted`, `untracked`, `no-history`, or `shallow-history`. Commit metadata
is present only where it is real — never invented for an untracked file or a
shallow clone.

When an entity has no attribution inside the indexed commit window, `blame`
(and `log`) walk further back with one bounded `git log --follow` per file.
Unproved comment or formatting changes are attributed conservatively rather
than skipped as semantic no-ops, so old entities do not acquire unsupported
meaning claims. Pass `--no-walk-back` to disable the walk and stay strictly
inside the indexed window.

## `occ code log <entity>`

Entity history across commits, renames, moves, and container changes, newest
first.

```bash
occ code log buildRepoMap --limit 10 --path . --format json
occ code log handleRequest --file src/server.ts --path .
```

| Flag               | Meaning                                          | Default |
| ------------------ | ------------------------------------------------ | ------- |
| `--limit <n>`      | Maximum entries (values over 100 clamp silently) | 20      |
| `--ref <ref>`      | Start from this ref                              | HEAD    |
| `--kind`, `--file` | Disambiguate the entity                          | none    |
| `--no-walk-back`   | Do not search git past the indexed window        | off     |

Each entry reports `changeType`, `structuralChange`, and — where the entity
moved or was renamed — `previousName` and `previousPath`.

## `occ code context <entity>`

A token-budgeted source bundle: the target first, then dependencies and
dependents, nearest first. Never exceeds the budget, and never truncates an
entity without saying so. The ceiling includes text role/location markers and
diagnostics, or the complete resolved JSON envelope and escaped source when
JSON is selected.

```bash
occ code context buildRepoMap --token-budget 4000 --path .
occ code context createUser --hops 2 --inline-context 2 --tokenizer o200k_base --path . --format json
```

| Flag                 | Meaning                                  | Default     |
| -------------------- | ---------------------------------------- | ----------- |
| `--token-budget <n>` | Hard ceiling                             | 8000        |
| `--hops <n>`         | Graph hops; `0` is unbounded             | 0           |
| `--include <dir>`    | Bundle `deps`, `dependents`, or `both`   | `both`      |
| `--tokenizer <name>` | `heuristic`, `o200k_base`, `cl100k_base` | `heuristic` |
| `--inline-context`   | Verified surrounding lines per entity    | 0           |

Use this instead of reading files when you need a function _and_ its callers:
it returns the reached entities under a budget you set. Check `diagnostics`:
when the target or a related graph file has non-full call/exact-span parser
provenance, context emits one deterministic bounded `evidence-unavailable`
diagnostic instead of presenting the bundle as exhaustive.
Surrounding lines are included in each entry's source and line range and are
charged to the same hard token budget. Full-content indexes use held bytes;
reduced indexes require a size+SHA-256 manifest match or return
`inline-context-stale` without source.

The bundle also reports reference closure over resolved 1-hop edges under the
pinned index as `closed`, `open`, or `unknown`, with edge/omission counts and
bounded handles explaining the verdict.

When even the fixed envelope cannot fit, context returns the shared narrowing
refusal instead of a bare error. Its options translate omitted roles into real
argv such as `--hops 1` (drop transitive callees/callers) or `--include deps`
(drop callers), alongside clearly labeled token projections. Refusal exits
nonzero; fitted `truncated` bundles remain successful.

## Programmatic long-lived sessions

`createEntitySession()` is safe to share among concurrent callers. Queries take
one immutable state snapshot; `refresh()`, `update()`, and `dispose()` are
serialized and publish a complete new generation only after it succeeds.
Construction accepts `typescriptChecker: 'preferred' | 'required' | 'disabled'`
and preserves that policy across refreshes and isolated cache warming.

The construction `signal` applies only until creation resolves. Later
`diff`/`blame`/`history`/`context`/freshness/reconcile calls take their own
`signal`, so cancelling one operation never poisons the session.

Use `session.update(changedFiles, options)` when the host already has the exact
working-tree paths. It updates code, format entities, and untracked state
without a HEAD probe or full manifest sweep. The list does not reconcile
history; call `refresh()` after commits, checkouts, resets, or rewrites.

`session.contextMany(queries, { tokenBudget })` resolves a mixed batch under
one shared budget. All resolved roots are considered before neighbours, shared
neighbours are deduplicated, bodies are admitted whole, and misses,
ambiguities, and omissions remain explicit in the aligned result.

The same session can attach historical lineage to body-free immutable blob
observations at one pinned commit. `inspectGitBlobs` is an async event stream:

```ts
import { collectGitBlobStructureBatch, createOcc } from '@cesarandreslopez/occ';

const occ = createOcc();
const session = await occ.code.createEntitySession({ repoRoot });
const batch = await collectGitBlobStructureBatch(occ.code.inspectGitBlobs(requests, { repoPath: repoRoot }));
const linked = await session.resolveBlobLineage({ ref, ...batch });
```

The ref is resolved once. OCC verifies each path and OID against the pinned
commit's physical tree, ignores replacement refs and grafts, refuses lazy
promisor fetches, and never reads the working tree. Header/footer counts,
ordering, provenance, coverage, declaration/fact/span counts, and all available
historical identity fields are cross-validated. Legacy result-only input stays
accepted but can never claim complete aggregate coverage. Receipts establish
trusted-local-process completeness, not hostile-payload authentication. Gate
the current contract on `health.features?.['code.entity.blobLineage.v2'] === true`;
the V1 key only advertises persisted-receipt compatibility. Authoritative
whole-diff admission requires
`health.features?.['code.entity.diff.wholeDiffSemanticVerdict']` to equal
`['whole-file-cst-v4']` and the result proof to equal `whole-file-cst-v4`.
The MCP receipt checker rejects complete coverage beside unknowns/gaps and
counts below known file-verdict floors, but accepts future verdict/basis arms
and count ceilings.

## Shared flags

These apply to the entity commands (`entities`, `hotspots`, `cochange`, `drift`,
`drift-documents`, `diff --entities`, `impact`, `blame`, `log`, `context`), not
to the NDJSON observation streams (`drift-documents` reads NDJSON input but is
a lineage-store consumer, so the shared flags and entity bounds apply).

| Flag                         | Meaning                                                                                                                                              |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--cache-dir <path>`         | Where the lineage cache lives. Defaults **outside** the repository (`$OCC_CACHE_DIR`, else `$XDG_CACHE_HOME/occ/entity`, else `~/.cache/occ/entity`) |
| `--tests-dir <dirs>`         | Extra comma-separated directories to treat as tests                                                                                                  |
| `--no-formats`               | Exclude data-format entities (JSON, YAML, TOML, CSV, Markdown)                                                                                       |
| `--strict`                   | Exit nonzero when an entity cannot be uniquely resolved (no effect on `entities`, which returns a bare list)                                         |
| `--ref <ref>`                | Inspect as of a git ref — honored by `blame` and `log`; `impact` and `context` take the flag but refuse it with a typed `invalid-ref`                |
| `--no-walk-back`             | Skip the bounded out-of-window `git log --follow` attribution — on `blame` and `log` only                                                            |
| `--max-commits <n>`          | Bound the history window indexed on first contact (default `1000`)                                                                                   |
| `--max-entities-total <n>`   | Bound historical entity extraction (default `200000`)                                                                                                |
| `--max-total-blob-bytes <n>` | Bound unique Git blob bytes read by the build (default `268435456`)                                                                                  |

The first run on a repository walks history and is the slow one. It is cached,
so later runs are incremental — a no-change in-memory refresh returns directly,
and a new commit appends only its lineage tails. Parsed blobs are shared across
compatible option fingerprints and patch releases under an extraction-contract
digest. A metadata/count mismatch after an interrupted append fails closed to a
rebuild. Feature-detect this layout through
`code.entity.store.appendExtend.v1` and
`code.entity.store.sharedBlobCache.v1`.

Cache hygiene: `occ code status --path . --format json` reads entity-cache
freshness and total code-cache bytes/entries plus superseded roots without
opening a session (`--full` opts into the complete build); `occ cache prune
[--max-age <days>] [--max-bytes <n>]` removes old or superseded code-index
entries without deleting one held by a live store. A stderr warning naming a
superseded OCC cache is the prompt to prune. `--no-cache` on graph commands
forces a direct build.

## MCP server

The same capabilities over stdio, for MCP clients:

```bash
occ mcp --root /path/to/repo
```

Tools: `occ_doc`, `occ_pattern`, `occ_entities`, `occ_hotspots`, `occ_cochange`, `occ_diff`, `occ_impact`, `occ_blame`, `occ_log`, `occ_context`, `occ_status`, `occ_source`, and `occ_anchor`.

Each returns both `structuredContent` (the same objects the programmatic API returns) and a compact text rendering.

All thirteen tools accept `response_format: "detailed" | "concise"` (default
`detailed`). Use `concise` after your context has grown: the shared projection
removes blob/span digests and internal parser/lineage provenance while keeping
one expandable handle per element (`nodeId` when available), plus every
coverage, truncation, freshness, verdict, and diagnostic field. The existing
compact text arm remains unchanged. Tool
descriptions contain schema-checked minimal/partial/full argument examples.
Gate this behavior on `health.features?.['mcp.responseFormat.v1'] === true`.

`occ_pattern` accepts a `utils` map of named ast-grep utility rules referenced through `matches`.

`occ_source` reads one exact symbol body by `nodeId`, or by `name` plus optional
`relativePath`. It returns tokens and a freshness/coverage footer and refuses
ambiguous names with candidates instead of choosing one.

`occ_anchor` captures one entity as a verifiable memory anchor (exactly one of
`name`, `lineageId`, `nodeId`). Its payload is nested under an `anchor` key the
concise projection protects, so the hash tuple survives `concise` intact — and
the `nodeId` arm is the bridge from concise refs (which keep only the node
handle) to the durable lineage anchor. Verify stored anchors with the CLI
`occ code verify-anchors`; an MCP `occ_verify` is planned as a fast follow.

For `occ_diff`, read `structuredContent.wholeDiffSemantic` as the authoritative
whole-diff proof. The sibling `semantic` field covers emitted entity records
only. Admit V4 only when health advertises exactly `['whole-file-cst-v4']` and
the result's `proof` is `whole-file-cst-v4`. Safe whitespace and ordinary
comments may then produce `cosmetic-change`; directives, BOM/encoding changes,
`unknown`, or partial/unavailable coverage remain evidence gaps. Persisted
V3 MCP receipts stay decodable but are not V4 evidence and never gain its
trivia classification retroactively; public types retain V1-V3.

`occ_status` answers about the index rather than the code: what it covers,
whether bounds truncated it, and whether it still describes the repository. Ask
it when a result is empty and you need to know whether that is a fact about the
code or a fact about the index. Its additive `codeIndexSchemaVersion`,
`parserContract`, and `parserEvidence` fields expose checker-backed,
checker-fallback, recovered, regex-fallback, degraded, and legacy file counts.

It is non-resident: no daemon, no network, no telemetry, and it exits when the
client disconnects. It answers only about the repository given by `--root`;
paths outside that root are refused. It re-checks freshness before each call
and reindexes when the repository moved; `--no-refresh` pins it. Index bounds
are settable at start for repositories larger than the defaults were sized
for: `--max-commits`, `--max-entities-total`, `--max-live-entities`,
`--max-total-blob-bytes`, and `--max-lineage-events`. The server also accepts
`--cache-dir`, `--exclude-dir` (repeatable), `--tests-dir`, `--no-formats`,
`--tokenizer`, and `--typescript-checker`.

For first contact on a large repository, start with `--max-commits 200`, let
one call complete, then ask `occ_status` whether the bounded index is truncated.
The explicit bound and resulting warm cache prevent timeout/cancel/retry
livelock. Count-based progress notifications make the cold walk visible only
to clients that request and honor them; they are not themselves a timeout
policy.

## Ambiguity is a real answer

If a name matches several entities, the result is `ambiguous` with every
candidate listed — OCC never silently picks the first. Narrow with `--file` or
`--kind`:

```bash
occ code log format --path .                          # ambiguous: 3 candidates
occ code log format --file src/output.ts --path .     # resolved
```
