> ## Documentation Index
> [HTML page](https://blode.co/taste-lint/docs/design)
> [Documentation index](https://blode.co/taste-lint/docs/llms.txt)
> Use the index to discover all available pages before exploring further.

# Design

Rule, unit, request, and finding contracts, plus the pipeline map.

## Contracts

### Rule

One YAML file per rule at `data/rules/<domain>/<id>.yaml`. `id` equals the filename. Fields and their meaning live in `src/types.ts` (`Rule`) and are validated fail-closed by `src/rules/validate.ts`. An invalid rule aborts before any request is made. A rule writes only what cannot be derived. The loader fills in `domain` (from the category), `tier` (from which of `mechanical` and `question` are present) and, for every unit kind but `source`, `scope.include` (from the unit kinds: paragraph and heading come from Markdown, jsx-text and class lists from TSX and JSX, attribute strings from both). Every rule skips tests, stories, and changelogs. `scope.exclude` adds to that list.

- A mechanical section alone runs in code (`tier: mechanical`). Raw-source regex rules only nominate candidates; their matches do not prove the source skill's conclusion.
- A question alone sends one question per matching unit (`tier: jev`).
- Both together make the mechanical part a candidate filter (`tier: both`). Only units it fires on are sent to Jev, and the mechanical hit never surfaces alone.
- A rule is YAML data (`data/rules/<domain>/<id>.yaml`: regex, phrases, `absent`, a Jev question) or a code object (`src/rules/code/*.ts`: the same fields with `check(unit)` in place of `mechanical`). Anything that counts, compares, or measures is a code rule. The loader returns both kinds in one list.
- `mechanical.absent` pairs with `regex`: the rule fires only when `regex` matches and `absent` matches nowhere in the unit (a file with a `<form>` and no focus call). It is how rg's `--files-without-match` pipelines port.
- `thresholds.act` and `thresholds.review` are per rule. Findings at or above `act` fail the run, between the two print as review notes, below are silent.
- `severity` (`major` or `minor`) says how bad a finding is if real. It is independent of probability.
- `source` points at the exact file and line the rule was harvested from. `handWritten` lists the keys `scripts/port-rules.ts` must not overwrite.
- Raw-source candidates require `review.applicability`, `review.exceptions`, `review.evidence`, and `review.verification`. They remain review-only, including under tuning overlays. Reports mark them `assessment: candidate`; their probability describes a pattern match, not confidence in a defect. `eval` treats them as unknown until an evidence-based detector replaces the search. Agent handoffs omit defect confidence for candidates.
- `review.sourceHash` records the reviewed skill document. `port-rules --check` detects changes to that document, including exceptions and verification guidance, and requires a procedure review.
- `data/rules/tuning.json` overlays `thresholds.act` and `status` per rule and is written only by `tune --write`.

### Unit

`src/types.ts` (`Unit`). One extracted piece of text, one class list, or (kind `source`) one whole TSX, JSX, or CSS file for mechanical rules that pattern-match raw markup. A raw-source regex is a candidate search. A source rule with a question gives Jev bounded complete-file context; oversized input stays unknown. Each has `file`, 1-based `line`/`column`, UTF-16 source offsets, `fixRanges` (the prose inside that slice a fix may rewrite: JSX text pieces, string literal insides, Markdown text nodes; absent when nothing can be rewritten safely), `inCode`, `context` (heading above, doc type, element, role) and, for class lists, resolved typography plus text-bearing neighbours. `id` is a stable hash used for cache keys and SARIF fingerprints.

### Jev request

`src/map/jev.ts` speaks `POST /v1/systemone` with `\{ model, state, questions \}`, or the same request through Vercel AI Gateway's evaluation route (model in a header, `boolean` for `noul`) when only `AI_GATEWAY_API_KEY` is set. State is a labelled string built from the unit and only the context keys the batched questions asked for. Questions use the noul primitive with `criteria.true` and `criteria.false` as `\{ what, examples \}` objects, which is the structured form the API accepts. The response is validated fail-closed. Bodies are never logged.

### Finding

`src/types.ts` (`Finding`): rule id, category, domain, severity, band, probability, unit provenance, message, evidence, fix hint.

## Pipeline

`src/cli.ts` (Commander) calls `src/lint.ts`, which runs extract, judge, reduce, report.

- `src/rules/`: fail-closed loader and validator, taxonomy copied from taste-training `content/categories.ts`, question builder for the wire format. `src/rules/code/`: the rules that count, compare, or measure, as `Rule` objects with a `check` attached (`typography.ts`, `classes.ts`). `loadRules` returns them beside the YAML rules under the same tuning overlay.
- `src/extract/`: units from Markdown and MDX (mdast), TSX (oxc-parser), Tailwind class lists, style-capture output, and one `source` unit per TSX, JSX, or CSS file.
- `src/map/`: `plan.ts` (which rules apply to which unit, mechanical checks), `judge.ts` (the one stage lint and eval share: plan, answer questions from cache or live, report abstentions), one request per unit with every matching question, sha256 cache under `results/cache`, token bucket limiter, the fetch client with its two transports.
- `src/reduce/`: bands, dedupe, scorecard, deterministic fixes, `mechanical.ts` (regex and phrase matching plus the helpers code rules share).
- `src/report/`: tty, JSON, SARIF. `src/eval/`: corpus loader, precision and recall with Wilson intervals, calibration, threshold tuning, McNemar A/B.
- `data/rules/<domain>/<id>.yaml` shipped rules. `data/rules/tuning.json` written only by `tune --write`. `data/corpus/*.jsonl` labelled units. `data/rule-drafts/` never loaded. `scripts/port-rules.ts` writes draft scaffolds, including static searches; a regex alone cannot ship a reviewed procedure. `--check` verifies every shipped rule against its source.

## Glossary

One name per concept. Substituting a synonym splits the concept across the code.

- **Unit**: one extracted piece of text or one class list (`src/types.ts` `Unit`). A corpus row is an **item** until `unitFromItem` turns it into a unit.
- **Unresolved**: a value the extractor could not resolve (a Tailwind theme token, a missing class list). Lives on `ResolvedTypography.unresolved` and in `UnresolvedError`.
- **Unknown**: the finding-level outcome when a rule cannot decide for a unit, usually because a value was unresolved or a precondition was unmet. Reported, never counted as pass or fail.
- **Band**: how sure a finding is: `act`, `review`, or `silent`. The eval reports the share of labelled items in the review band as the review rate.
- **Severity**: how bad a finding is if real: `major` or `minor`. Set by the rule, independent of the band.
- **Mechanical / Jev / both**: the rule tiers. `both` means the mechanical part filters candidates and Jev decides.

## fix

Every rule carries `fix.hint`, printed under the finding for a person or an agent. Nothing in this package calls a text model. `fix.function` names a function in `src/reduce/fixes.ts` that `--fix` applies to each of the unit's `fixRanges`, never to quotes, braces, expressions, or inline code around them. A unit without ranges is reported and left for a hand fix. Each fix function matches exactly what its rule's `mechanical.regex` flags.

## Non-goals for v1

- No `choice` or `score` rules. The schema reserves the field.
- No Tailwind `@theme` parsing. Unknown tokens are `unresolved`, never guessed.
- No composite taste score. The scorecard is counts by category and domain.

## Run evidence and coverage

Preparation in `src/map/judge.ts` is shared by lint preview, execution, and eval. A prepared judgement retains eligibility, mechanical negatives, and skipped reasons. Execution owns answers and unknowns. Renderers own grouping and display limits. The canonical policy result is `LintResult.summary`, computed from all rule findings before presentation grouping.

- **Skipped**: a rule does not apply to the unit. Excluded from evaluated negatives.
- **Negative**: an applicable mechanical check or candidate filter evaluated without a violation.
- **Request**: one logical provider evaluation. Several source units may share it when questions and state are identical.
- **Attempt**: one HTTP try, including retries owned by the provider adapter.
- **Cached answer**: one rule probability reused from persisted successful work.

JSON v1 retains its grouped fields and adds complete `ruleFindings`, `ruleScorecard`, `summary`, `scope`, and `coverage`. The compatibility scorecard's old unit totals remain. Use coverage for eligible denominators. TypeSafe source references, provider distinctions, and contract checks are in [TypeSafe](/typesafe).

## Repository evidence and skill packs

A run owns one repository-facts service. Source units carry non-enumerable parsed facts and root-contained file access. Extraction JSON excludes those capabilities. Markdown structure and actual JavaScript/TypeScript imports drive structural code rules. Missing evidence produces unknowns. README, skill, plan, and explicit personal-writing profiles keep artifact-specific checks scoped.

Personal comparison rules request named context fields supplied by the caller. Missing or oversized context prevents scheduling that comparison. Preparation determines request work once. Execution reuses it. Recorder failures remain local failures rather than provider errors.

See [skill packs](/skill-packs) for configuration and current limits.