> ## Documentation Index
> Fetch the complete documentation index at: https://hibi.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Anchors and Resolution: How Hibi Pins Claims to Code

> Each side of a claim stores a text quote with context, a position, and on the code side an AST fingerprint and a literal. Resolution is an ordered cascade with fixed constants.

A claim has two sides. The **doc side** is the documented sentence. The **code side** is one or more spans of code the sentence describes. Each side is a **selector bundle**: one file plus a list of selectors that point at the same span in different ways.

```
Anchor = { doc: SelectorBundle, code: SelectorBundle[] }
```

The store holds the anchor, not a copy of the prose. At check time Hibi re-reads the live span from the file.

## Selector kinds

| Selector        | Side | What it stores                                                                                                   |
| --------------- | ---- | ---------------------------------------------------------------------------------------------------------------- |
| `text-quote`    | both | the exact text plus 48 characters of prefix and suffix context                                                   |
| `text-position` | both | the character offsets at record time; a locate bias and the `moved` tiebreaker                                   |
| `ast-node`      | code | the smallest enclosing named tree-sitter node with a structural hash and a semantic hash; used for reason labels |
| `value`         | code | the first literal that lies inside the quoted span, so `5` becoming `50` is caught                               |
| `coarse`        | code | a file or glob pattern (`--glob`); navigation only, never graded as drift                                        |

The `value` selector is stored only when a literal lies inside the quoted span. If the span has no literal, no value selector is stored and no value check runs. Quote the load-bearing token (`MAX_ATTEMPTS = 5`), not the whole line.

<Warning>
  A coarse anchor is never reported as drift. It answers "which claims touch this area" and nothing more. A claim whose only code side is coarse cannot gate; record it with `--suggest`.
</Warning>

Tree-sitter grammars (TypeScript, Python, Rust, Go, Java) load lazily, per language present in the anchors being checked. `--no-ast` skips them.

## The resolution cascade

Each side is resolved by an ordered cascade. There is no weighted score.

<Steps>
  <Step title="Locate the quote">
    Find exact occurrences of the stored quote. If there are several, rank them by how well the stored 48-character prefix and suffix match. If there is no exact occurrence, run a bitap fuzzy match biased toward the stored position. The match is accepted only when its edit distance to the stored quote is within the error budget `min(256, floor(len × 0.4))`, where `len` is the quote length. A candidate over the budget counts as not found.
  </Step>

  <Step title="Not found: orphaned">
    A missing file, or no match within the budget, is `orphaned`.
  </Step>

  <Step title="Several equally good exact matches: ambiguous">
    When more than one exact occurrence ties on context, the side is `ambiguous`. Quotes shorter than 8 characters are exempt from this check; their candidates are picked by position.
  </Step>

  <Step title="Changed">
    The side is `changed` when any of these holds: normalized text similarity to the stored quote is below 0.9; the semantic AST hash differs; the literal inside the quoted span differs from the stored value; on a prose span, a number in the sentence changed.
  </Step>

  <Step title="Unchanged or moved">
    Otherwise the text is the same. If the located start is within 4 characters of the stored position, the side is `unchanged`; further away, it is `moved`.
  </Step>
</Steps>

Position is only a bias during locate and the tiebreaker between `unchanged` and `moved`. It never decides `changed`.

## Reason labels

A `changed` side carries a reason in `notes` and in `evidence.changedEvidence`:

| Condition                                    | Label                              | `changedEvidence.kind` |
| -------------------------------------------- | ---------------------------------- | ---------------------- |
| semantic hash differs, structural hash equal | `identifiers or literals renamed`  | `ast`                  |
| both hashes differ                           | `restructured`                     | `ast`                  |
| in-span literal differs                      | ``value changed (was `5`)``        | `value`                |
| number in a prose sentence differs           | `a number in the sentence changed` | `text`                 |
| similarity below 0.9                         | `text changed (NN% similar)`       | `text`                 |

## The constants

All constants live in `src/algo/params.ts`. There are no hidden weights.

| Constant                       | Value                              | Effect                               |
| ------------------------------ | ---------------------------------- | ------------------------------------ |
| fuzzy error budget             | `min(256, floor(len × 0.4))` edits | a candidate over it is `orphaned`    |
| text-quote context             | 48 characters each side            | ranks exact occurrences              |
| same-text similarity           | 0.9                                | below it, `changed`                  |
| move awareness                 | 4 characters                       | beyond it, same text is `moved`      |
| ambiguity minimum quote length | 8 characters                       | shorter quotes are never `ambiguous` |

## Doc-first resolution

The doc side resolves first. The documented span is the source of truth, and a verifier runs only when the doc side is `unchanged` or `moved` (see [Behavioral claims](/behavioral)). The verdict still reports both sides, so a `doc:changed` verdict also shows what the code did.

## Record-time checks

`record` and `reanchor` refuse a doc quote shorter than 8 characters, a quote that does not occur in its file, and a `--code-file` that does not exist. A doc quote that occurs more than once is refused when the context cannot single out one occurrence, and recorded with a warning when it can; widen the span with `--doc-range` to avoid both.

## Next

<CardGroup cols={2}>
  <Card title="Verdicts" icon="scale-balanced" href="/verdicts">
    How the five states become a verdict, a remediation menu, and an exit code.
  </Card>

  <Card title="Behavioral claims" icon="flask-vial" href="/behavioral">
    Verifiers for sentences that text and AST resolution cannot judge.
  </Card>
</CardGroup>
