Evidence Tags
One target, one reason, on the declaration that owes the answer.
@evidence <target> <reason>
@link <file.ts>#<Accessor> <reason>
@evidenceExclude <target> <reason>
@evidenceReview <target> <description>
@evidenceReview <target> #<fingerprint> <description>
@evidenceExcludeReview <target> <description>
@evidenceExcludeReview <target> #<fingerprint> <description>The target is one whitespace-delimited token, except that an inline link runs to its closing brace and a file accessor’s quoted bracket segment can contain spaces. A declaration may carry any number of tags, and each is validated on its own.
The four tags
@evidence is a positive citation. It says this declaration implements, verifies, or documents the target, and the reason says why.
@link file.ts#Accessor <reason> is the file-qualified spelling of a positive citation, available in Markdown HTML comments and TypeScript JSDoc. It has the same coverage and review obligations as @evidence. The same file-qualified target can also follow @evidence, @evidenceExclude, or their review tags.
@evidenceExclude is the opposite decision, recorded rather than assumed. It says nothing here owes the target, and the reason is what a reviewer reads to agree or disagree. A reference that declares noEvidenceExclude refuses this answer outright, and a claim that declares evidenceExcludeCarriers accepts it only from the files it names.
@evidenceReview and @evidenceExcludeReview annotate a citation and an exclusion respectively. They are not a third kind of acknowledgement: a review discharges no coverage, contributes no host, and never answers a unit on its own.
The two review forms stay separate because they answer different questions. Whether a screen truly renders a limit and whether it truly owes nothing are not the same verification, and one tag for both would let the easier one discharge the harder.
A review is therefore paired by acknowledgement kind together with target, which also lets one host cite a target for one claim and exclude it for another.
Writing the reason
The reason is the only part a reviewer can disagree with, so it has to say what this declaration does about the target rather than what the target says.
// unfalsifiable: true of anything in the same folder
/** @evidence docs/requirements/discount.md#coupon-stacking Handles coupon stacking. */
// checkable: names what a reader can look for and fail to find
/** @evidence docs/requirements/discount.md#coupon-stacking Renders the per-issuer limit this section defines, in the buyer's words. */A restated heading is worse than no citation. It passes every check, spends the reviewer’s attention, and returns a sentence that cannot be wrong.
An exclusion’s reason carries more, because it records a decision rather than describing work. Where another population owns the target, name it and the condition that would bring the target back: “Settlement owns coupon combination; reject this exclusion once a screen displays a stacked total.”
Where the target does not apply at all, say what makes it inapplicable rather than asserting that it is. A principle about fixing root causes does not reach a component that only renders, and “No failure to fix.” is the whole reason, because a reviewer can check it against the declaration in one look.
Nothing scores any of this, and that is deliberate. A graded reason becomes a reason written for the grader, and the sentence stops being worth a reviewer’s time.
Targets
/** @evidence docs/requirements/pricing.md#sale-price Sale price derives from this section. */
/** @evidence prisma:Sale.price The stored column this figure is read from. */
/** @evidence POST:/members Member creation follows this API operation. */
/** @evidence {@link ISale} The complete sale contract is mirrored here. */| Kind | Address |
|---|---|
| Markdown | <path> for a file, <path>#<anchor> for an H1-H4 section |
| Prisma | prisma:<Model>, prisma:<Model>.<member> |
| Swagger | <UPPERCASE_METHOD>:<path>, such as GET:/members/{id} |
| TypeScript | {@link QualifiedName}, resolved through your imports |
Those are two resolvers, and the token itself says which. A path address resolves against the configured populations, while an inline link resolves through the citing module’s imports, so a symbol citation is a real reference rather than a string that happens to spell a name.
The braces are load-bearing rather than decoration. TypeScript resolves a name inside an inline link and counts it as a use, so an import that exists only to support a citation survives noUnusedLocals; an unbraced name leaves that import unreferenced and raises TS6133. Prefer import type for a citation-only import, since it is erased at emit and creates no runtime dependency or cycle.
Keeping the discrimination inside the token is also why a path target must stay one token. POST /members cannot be read as a two-token target without reinterpreting a TypeScript target named POST whose reason begins with /members.
Markdown anchors
A file target is its path inside the reference root, written with / or \. A heading target appends its anchor.
An explicit {#anchor} suffix on the heading wins, and it must start with an ASCII letter or digit and may then contain letters, digits, ., _, :, and -. Without one, the heading becomes a lowercase slug: letters, numbers, and _ survive, whitespace and - collapse to -, and other punctuation is dropped.
Two selected headings that slug to the same target are ambiguous and need distinct explicit anchors. A source path containing whitespace cannot be addressed at all, so the rule reports it with a rename diagnostic rather than creating an impossible obligation.
TypeScript identities
A top-level identity uses its public export name, so export { Local as Public } is addressed as Public. A namespace member prepends its namespace, as in Orders.create. A static class member is ClassName.member and an instance member is ClassName.prototype.member, which is also how a constructor parameter property is addressed.
File-qualified targets distinguish modules by path. Existing inline links distinguish them through imports. A re-export adds a public address and creates no second unit in the barrel file.
File-qualified code links
## Existing behavior
<!--
@link ../src/example.ts#execute Reviews this operation.
@link ../src/example.ts#Target.property Reviews this static field.
@link ../src/example.ts#Namespace.property Reviews this namespace value.
-->The same tags work in a TypeScript /** ... */ block without importing the target. Existing @evidence {@link ImportedName} <reason> citations keep using imports. A file link does not count as use of an otherwise unused import.
Paths resolve relative to the file containing the citation. Use / or \ as file separators, %20 for a space, %23 for a literal #, and %25 for a literal percent sign. Use the file’s exact casing for portable links. The accessor is separate: its names are case-sensitive, and its slashes and backslashes are literal name characters.
| Target | Declaration |
|---|---|
example.ts#execute | Exported function |
example.ts#Target | Class and its selected descendants |
example.ts#Target.property | Static class field |
example.ts#Target.prototype.property | Instance class field |
example.ts#Namespace.property | Public namespace member |
example.ts#Target["a.b"] | Static member whose literal name contains a dot |
example.ts#default | Default-exported class/function or named local declaration |
Use public aliases at the module that exports them. Interfaces and object-shaped type aliases expose their declared members. Type-only re-exports keep their existing type/value restrictions. Private and protected members, constructors, accessors, computed names, inherited-only members, and unsupported expression exports do not become evidence units. Ambiguous declarations in one module require an unambiguous accessor or a narrower symbol selector.
An ordinary export * does not forward a default export. Name it explicitly or reach it through a namespace re-export. The default’s private local binding is not a public address. A type-only default export exposes the type and withholds its value members.
reference.symbol selects obligations; their structural ancestors remain addressable. For example, #Target can acknowledge all selected properties of the class even when the selector is "property". A failed link satisfies no obligation.
Pair a positive link with @evidenceReview file.ts#Accessor <description>. When the reference requires review, add the fingerprint from the diagnostic after the target. The review belongs to the same host, and changes to the cited code expire it. Neither a review annotation nor a fenced example supplies coverage.
Upgrading to file-qualified links changes existing TypeScript review fingerprints once. They now include the declaring unit’s file identity and complete declaration scope, independently of barrel aliases, selected entry modules, and type-only export paths. Re-read existing TypeScript citations and update their review fingerprints. Markdown, Prisma, and Swagger fingerprints keep their existing format. Default-only declarations and whitespace-containing literal members now join the supported code population, so existing selectors can acquire new obligations.
Where a tag may live
A tag is read only from a comment the parser attached to a declaration. One written anywhere else is reported rather than silently ignored, because a tag that does nothing is the exact failure this plugin exists to remove.
| Artifact | Host | Not a host |
|---|---|---|
| TypeScript | A JSDoc block on the declaration | // lines, comments inside a binding pattern |
| Prisma | A /// run or block comment above the declaration | // lines, comments above a closing brace |
| Markdown | An HTML comment | Rendered prose |
/// @evidence docs/requirements/pricing.md#sale-price Sale price derives from this section.
model Sale {
/// @evidence docs/requirements/discount.md#coupon-stacking The stacking limit is stored here.
coupon_limit Int
}<!-- @evidence docs/ideas/discount.md#discount-policy Carries over the per-issuer limit recorded in the idea notes. -->Markdown uses an HTML comment so the citation renders invisibly and the document a reader sees stays clean. The tag must be the first content on its line, though a bullet or quote marker in front of it is read through, so a sentence merely mentioning a tag goes on describing it. Anything that renders as code stays silent, which is what lets this page print tags in fenced blocks without answering for them.
Prisma follows Prisma’s own rule that a comment documents the declaration immediately below it. A blank line before a top-level block detaches the comment, and a comment above a block attribute or a closing brace documents nothing. One exception exists on the exclusion side: an unattached top-level /// @evidenceExclude run is a file-level carrier, which supports a lint-only .schema ledger outside Prisma generation.
Editor completions
evidence/graph publishes the configured targets as editor completions through ttscserver, so a citation can be picked from the real address list instead of typed from memory.
The host withholds a rule’s completions on any cycle where that rule reports something, which is deliberate rather than a limitation: a target list computed from a graph that failed to load would suggest addresses that do not exist. Clear the diagnostics and the completions come back.
Scope
Units contain other units. A Markdown file contains its outline and a heading contains the lower headings until the next one of equal or higher level. A Prisma model contains its columns and relations. An interface or object-shaped type alias contains its members, a class contains its selected members, and a namespace contains everything public nested below it.
An @evidence target acknowledges the unit it names and every selected descendant, and @evidenceExclude does the same unless its reference refuses exclusions. That is what lets one citation of an H2 answer for the H3s beneath it when the whole section is what the declaration implements.
The reference’s symbol selector defines the denominator, not the set of addressable targets. Every structural ancestor of a selected unit stays resolvable as an aggregate scope even when its own kind is not selected.
A checklist reference is the one place this cascade is cut back, because the whole point of a checklist is that each item was answered on purpose.
Withdrawing a declaration
A declaration whose documentation carries @internal, @hidden, or @ignore materializes no unit, and neither does anything nested inside it. The three are equivalent statements that the declaration is not API, and the graph honors the decision the source already made.
Withdrawal applies to both sides. A withdrawn declaration is neither a selected reference unit nor a selected claim host, and it cannot serve as an exclusion carrier either. TypeScript JSDoc and Prisma /// comments behave identically, and a withdrawn Prisma model takes its columns and relations with it.
The tag has to open its own line, so prose that mentions one is describing it rather than declaring it. A citation naming a withdrawn declaration is answered with the tag as the cause, rather than as an unresolved target that would send you hunting for a typo that is not there.
Note that stripInternal deletes a withdrawn declaration from emitted declaration files. Writing one of these tags inside a published doc comment removes the API it documents from what consumers see.
Reviews and fingerprints
/**
* @evidence docs/requirements/discount.md#coupon-stacking States the per-issuer limit.
* @evidenceReview docs/requirements/discount.md#coupon-stacking #a1b2c3d Verified against policy section 3.
* @evidenceExclude .agents/skills/principles/SKILL.md#fix-root-causes-not-symptoms No failure to fix.
* @evidenceExcludeReview .agents/skills/principles/SKILL.md#fix-root-causes-not-symptoms #f0e1d2c Confirmed the component only renders.
*/
export function CouponStackingNotice(props: IProps): JSX.Element;A review matches the same declaration and the same target as the acknowledgement it annotates, and the two kinds pair separately: the citation above is answered by @evidenceReview, the exclusion beneath it by @evidenceExcludeReview. A review filed against a target nothing on that declaration cites is its own finding, because the author did the work and an orphan message would send them looking for a typo instead.
The #-prefixed fingerprint is seven lowercase hex characters, optional in the grammar and required by a reference that declares requireReview. It digests the cited scope’s content, so editing that content expires the review and the diagnostic hands you the value that replaces it. Write the new one only after re-reading what moved; copying it from the diagnostic is how a review becomes a formality.
The # is doing the same work the braces do around an inline link. The token discriminates itself rather than being guessed at, which matters because a requirement anchor such as #req-search-policies opens a description in exactly that shape.
What a review proves is narrow and worth stating plainly. It proves that a separately addressed statement exists and was written against the cited content as it now stands. It does not prove anyone read that content, and no rule judges whether the prose is sincere.
When a tag is wrong
A tag is data the compiler reads, so every way of getting one wrong has its own diagnostic rather than silence. A target that resolves nowhere, a tag with no reason, a review naming a target its declaration never cites, an exclusion written outside the declared carriers, and a tag sitting where nothing can attach it are each reported where they sit, with the repair named in the message.
What the tags cannot do
An import-scoped inline link still requires a TypeScript claim. Markdown uses a file-qualified code link instead. Prisma-to-TypeScript citations are unsupported.
A tag also cannot make a false statement true. It removes the error, not the problem, which is why evidence/review exists and why the overview draws the line where it does: the compiler catches omissions, and review catches falsehoods.