Skip to Content
📖 Guide DocumentsEvidence GraphOverview

Evidence Graph

@ttsc/evidence turns a requirement into a compile error until some declaration cites it by name.

You declare the graph in lint.config.ts: which files owe answers, and which documents, schema models, API operations, or TypeScript symbols they owe answers to. Every pair is one independently complete obligation. A unit nothing acknowledges fails the build, in the same diagnostic stream as a type error.

/** * @evidence docs/discount.md#coupon-stacking * States the per-issuer stacking limit this section defines. */ export function CouponStackingNotice(props: IProps): JSX.Element;

@evidence <target> <reason> names one unit of the spec and why this declaration answers for it. Without it the next build stops:

$ npx ttsc error TS16411: [evidence/graph] Missing acknowledgement for 'docs/discount.md#coupon-stacking' (Markdown H2 'Coupon Stacking' at docs/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.

You do not write these tags. An AI coding agent has to clear the errors to finish, and clearing one means citing the target and writing down why its code answers for it.

What it changes

The omissions it closes, and what closing them measured.

The four omissions nothing else catches

A type error is caught by the compiler. Wrong behavior is caught by a test. Four failures have only ever been caught by a person reading a diff:

  • a requirement the document states and nobody implemented;
  • a feature implemented and never verified;
  • a capability no screen reaches;
  • a document that changed while the code leaning on it did not.

None of them is a search problem, which is why handing the documents to an agent through retrieval does not close them. Coverage needs a denominator. If the requirements hold 73 sections, the denominator is 73, and something has to stop the build on the sections still empty. A retriever answers the query it was given and never returns the sections nobody asked about, which are exactly the ones that went missing.

A review loop does count, badly. Most of what it will ever find arrives in its first rounds, and each round after returns less than the one before, settling on a ceiling rather than on zero. The ceiling drops as the project grows, because one fix touches more surfaces while nothing in the loop is counting either number.

What that is worth, measured

One coding engine built the same four applications twice from the same frozen requirements, once with the graph in the workspace and once without. Coverage is folded from thirteen reference edges, so a codebase scores on how much of its own provenance graph it actually satisfies.

The arm without the graph reached less of its own graph on every larger subject, and it ran the same review loops the arm with the graph did. The arm with it is whole by construction: the build does not finish while an obligation is open, so coverage reaches 100% as the residue of the errors it closed rather than as a target anyone aimed at.

The full benchmark has the thirteen edges behind each score, what both arms spent, and the method.

What the graph enforces

The obligation model, in the order it decides things.

Four artifact kinds

ArtifactUnitHosts a claimCan be citedCites in
Markdownthe file, and every H1 to H4 sectionyesyesan HTML comment
Prismamodel, column, relationyesyesa /// comment
TypeScriptexported type, function, propertyyesyesJSDoc
Swagger / OpenAPIevery operation under pathsnoyesnothing, it cannot host a tag

Swagger is reference-only: an operation grounds an obligation and has nowhere to write a tag.

One pairing is refused outright. Markdown cannot cite a TypeScript symbol, because a symbol citation resolves through the citing module’s imports and a document has no import scope. Wire that relation the other way and let the code cite the document.

Obligations never pool

A backend that honors a pricing rule and a screen that forgot it is not a 67% project. It is a compile error naming the section the screen ignored.

Pooling is how partial use by several consumers reads as complete use by the project, and how two implementations of one rule drift apart with nobody noticing. So each claim-reference pair is counted on its own, and a citation toward one reference never counts toward another.

A test citation is stricter than line coverage for the same reason. Line coverage credits code a test merely passed through. A citation is a statement of responsibility for a named unit.

A recorded no is also an answer

Not every requirement belongs to every layer. @evidenceExclude <target> <reason> discharges a scope with a written decision instead of an implementation.

The reason carries the weight. “Not applicable”, “internal only”, and “later” are conclusions rather than reasons; what belongs in one is the actual owner or the observable alternative, plus the condition that would make the exclusion wrong.

It is the only acknowledgement that settles an obligation with nothing built, so it exists to be vetoed. A claim can name the single file its exclusions must live in, which turns reviewing them from reading a codebase into opening one file.

Documents gain reference integrity

Code has always had it. Rename a function and every caller fails. Documents never did, which is why they rot: nothing complains when a section goes stale, so nobody trusts the spec, so nobody invests in it.

In an evidence graph a document is a set of units other artifacts point at by name, and the link runs both ways. A section nothing implements fails the build, a new export owes a citation to something, and a change on one side alone cannot pass because the citation dies with its target.

Get started

Install, and where to read next.

Install

@ttsc/evidence is a rule contributor to @ttsc/lint, not a top-level ttsc plugin. The compiler never loads it; lint.config.ts does, and its five rules run inside the lint engine’s own binary on the AST and types the type-check pass already built.

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

There is no CI job to add. Violations surface in every ttsc build, every --noEmit check, every ttsx run, and in the editor through ttscserver.

Setup → Evidence Graph is the ten-minute path: one claim, the first failing build, and the two ways to clear it.

Where to next

  1. Claims and References: the whole configuration surface, from symbol selectors to monorepo roots and reference policies.
  2. Evidence Tags: the tag grammar, target forms, and where a tag may live in each artifact kind.
  3. Rules: evidence/graph, evidence/documented, evidence/singular, evidence/todo, and evidence/review.
  4. Full-Stack Wiring: the monorepo graph the benchmark above was measured on, claim by claim.
  5. Adoption: the smallest useful wiring, document-to-document chains, and staged rollout on a legacy codebase.
  • The Evidence Graph benchmark is how this package is measured: one engine builds the same application twice, once with the graph and once without.
  • Rule contributors covers how a contributor package is built and published. @ttsc/evidence is the first-party example.
  • The package README  is the condensed reference for the same surface.
Last updated on