Skip to Content

Evidence Graph

@ttsc/evidence makes every requirement you configure demand an explicit acknowledgement from the code, test, or document that claims to satisfy it. A requirement nothing acknowledges fails the build, by name.

It is a rule contributor to @ttsc/lint, not a top-level ttsc plugin, so it needs the lint engine and the compiler that hosts it. Wire Lint & Format first if your build does not run ttsc yet.

Install

npm install -D ttsc @ttsc/lint @ttsc/evidence typescript

The first build after installing links the contributor’s Go rules into the lint binary and can take several minutes. Later builds reuse the linked binary. Pin TTSC_GO_BINARY and TTSC_CACHE_DIR in CI so that step stays deterministic across matrix runs.

The config file

Register the plugin in the lint.config.ts you already have, and pass the graph as the option of evidence/graph:

// lint.config.ts import { evidence, type ITtscEvidenceGraphConfig } from "@ttsc/evidence"; import type { ITtscLintConfig } from "@ttsc/lint"; const graph: ITtscEvidenceGraphConfig = { claims: [ { type: "typescript", files: ["src/components/**/*.tsx"], symbol: "function", reference: { type: "markdown", files: ["docs/requirements/**/*.md"], symbol: ["h2", "h3"], }, }, ], }; export default { plugins: { evidence, }, rules: { "evidence/graph": ["error", graph], }, } satisfies ITtscLintConfig;

That claim reads as one sentence. The components under src claim to implement the requirements, so every H2 and H3 under docs/requirements must be cited by a component.

Read every claim in that direction: files and symbol pick who owes the answers, reference picks the questions they owe.

See it fail

Write a requirement nobody has implemented:

<!-- docs/requirements/discount.md --> # Discount Policy ## Coupon Stacking {#coupon-stacking} At most one seller coupon and one platform coupon may combine on a single order.
$ npx ttsc --noEmit error TS16411: [evidence/graph] Missing acknowledgement for 'docs/requirements/discount.md#coupon-stacking' (Markdown H2 'Coupon Stacking' at docs/requirements/discount.md:3) in Claim 1 reference 1 (markdown, symbols: h2, h3). Cite the artifact that answers for this unit with @evidence on a selected typescript host, building that artifact first when none does, or write @evidenceExclude on an eligible carrier when nothing here owes it. Never leave an untrue tag standing just to pass this check; it removes the error, not the problem. Found 1 error.

The diagnostic names the section, the obligation that owes it, and both repairs. Implement it and cite the section:

// src/components/CouponStackingNotice.tsx /** * @evidence docs/requirements/discount.md#coupon-stacking Renders the combination limit defined by this rule. */ export function CouponStackingNotice() { return <p>One seller coupon and one platform coupon may be combined.</p>; }

Or record that this population deliberately does not serve it, with a reason a reviewer can veto:

// src/components/SCREEN_EVIDENCE_EXCLUDE.tsx /** * @evidenceExclude docs/requirements/discount.md#coupon-stacking Settlement owns coupon combination; reject this exclusion once a screen displays a stacked total. */ export const SCREEN_EVIDENCE_EXCLUDE = true;

An exclusion has to sit on a public export in a file the claim already selects, so this one lives under src/components/ like any other selected file. Gathering them in one named ledger is what evidenceExcludeCarriers is for, and it is worth declaring before the first exclusion is written.

Findings arrive in every ttsc build, every --noEmit check, every ttsx run, and in the editor. The CI step that already runs ttsc --noEmit gates the graph without a second job.

Match on the bracketed rule id rather than on the number. TS16411 is assigned deterministically from the complete set of loaded contributors, so changing that set can renumber a contributor rule while [evidence/graph] stays stable.

What a green build means

Every H2 and H3 under docs/requirements is either implemented by a component that says which one and why, or excluded by a decision someone wrote down.

That guarantee holds only over what the claim selects. A section in a file the globs miss is not covered, and a passing build says nothing about it. Widening a population is how coverage grows; it is never inferred.

Turn on the file rules

evidence/graph is the graph. Three file rules protect the conditions it depends on:

rules: { "evidence/graph": ["error", graph], "evidence/documented": "error", "evidence/singular": "error", "evidence/todo": "error", }
  • evidence/documented requires a JSDoc block on every selected export, because a block is the only place a citation can live.
  • evidence/singular keeps one public identity per file, named after the file.
  • evidence/todo fails the build on every remaining JSDoc @todo, so a stub cannot be mistaken for finished work.

Start with evidence/graph alone and add the others once the graph is green.

Stage the rollout

A claim you are not ready to enforce carries disabled: true. Its shape is still validated, but it contributes no populations, no obligations, no completion hints, and no watched inputs:

{ name: "screens", type: "typescript", files: ["src/components/**/*.tsx"], symbol: "function", reference: { type: "markdown", files: ["docs/requirements/**/*.md"], symbol: ["h2", "h3"], }, // Remove once every required screen and its evidence mapping is complete. disabled: true, }

Declaring the whole graph up front and enabling one claim at a time is the intended adoption path, not a workaround.

Next

Last updated on