For AI agents: the documentation index is at /taste-lint/docs/llms.txt. Append .md to any page URL, or send Accept: text/markdown, for the Markdown version of that page.
For AI agents: the documentation index is at llms.txt. Markdown versions are available by appending .md to any page URL, including this page's markdown.
Design
Rule, unit, request, and finding contracts, plus the pipeline map.
Design
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 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.
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.tsUnit). 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.
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 for configuration and current limits.