sprig and prose docs classic docs
Reference› ADRs› ADR020 MarkerPosition

ADR020 MarkerPosition

Title: Marker position

  • Status: Accepted
  • Decided: D2026-09
  • Affects: Receipts

Context

A receipt marker is @sprig followed by a dotted identifier, written in a comment. The scanner treated the tag as a marker anywhere it appeared in that comment. The exclusion it did have — a lookahead that skipped @sprigfoo — was never designed. It excluded letters, digits and underscore because those were the case somebody thought of. It did not exclude a hyphen, a backtick, a slash, or a newline.

That gap met a package whose documentation is largely sentences containing the tag. sprig observe stopped working on its own repository. Five comment lines that mentioned @sprig — an npm scope inside a JSDoc import() type, the tag named inside backticks, a sentence that wrapped after the word — became hard errors and exited 1. A sixth, packages/cli/src/receipts.ts:57, said "for @sprig doc-comments" and was silently accepted as a receipt for a concept named doc-comments.

Four of the five errors were inside packages/receipts itself. The scanner could not survive reading its own documentation.

This is not the same question 009 answered. That record rules on how the field guide renders receipts it is given. The connection worth naming is that 009's own evidence is the twenty-four receipts in packages/prose-parser/src/errors.js, and those are the same twenty-four this rule has to leave untouched.

Decision

A marker begins its own line. After comment decoration is stripped, @sprig must be the first thing on the line (leading whitespace allowed), followed by a space or tab and a dotted identifier. Anywhere else in a line, @sprig is prose and is not a marker.

This is the JSDoc block-tag convention that @param and @returns already follow, in exactly the files this scanner reads.

Consequences

What this makes easy. Writing about @sprig is ordinary prose. An import from the @sprig-and-prose scope in a JSDoc type is not a marker. The scanner can read its own comments. sprig observe works on this repository.

What this costs. A tag indented under a Markdown list item inside a doc comment is no longer a marker. A JSDoc block whose example shows a marker at line start still registers it, and there is no way to write that example inertly. The malformed-target error survives, but now fires only on a deliberate line-initial tag, which is narrower than before.

One-time id churn. A block that mentioned @sprig above its real marker used to record the decoy's line and column. Those fields feed the content-addressed receipt id, so those ids change once, to the correct value. Markers that were already valid and already on their own line keep their ids.

What to watch. The identifier grammar inside the target is still hand-duplicated from the prose-parser scanner. That is a consistency problem, and it is a different problem.

Alternatives considered

Widen the TAG_RE lookahead to exclude -, backtick, /, : and newline. Rejected. It fixes five of six cases and leaves the sixth — the one that writes a wrong receipt rather than an error — completely untouched, because that line is lexically a valid marker. It also grows a list that has to be guessed at correctly forever, and every future punctuation mark somebody writes next to the word is a new bug of the same kind.

An escape syntax, \@sprig or @@sprig. Rejected. It puts the burden on every person who writes a sentence containing the word, in a package whose documentation is largely sentences containing the word, and it would have required editing five existing comments in this repo to appease the tool. The line rule needs no escape because prose already never starts a line with the bare tag.

Ignore any tag that sits inside backticks or an import() type. Rejected. It means teaching the comment scanner about Markdown inline code and TypeScript type syntax, inside comments, per language. That is a parser for the host language, which the comment scanner is explicit about not being, and it still misses the unquoted doc-comments case.

Evidence

Across 325 scannable files in this repository there were 32 lines containing an @sprig tag. The line rule classified all 32 correctly, with no exceptions: the six prose lines above became invisible, and the 26 real markers — 24 in packages/prose-parser/src/errors.js plus the two canonical fixtures — were untouched.

The result that contradicted the obvious approach is the doc-comments case. Widening the lookahead cannot fix it, because "for @sprig doc-comments" is lexically a valid marker. The two strings are identical. Something other than the characters around the tag has to decide.

Receipt ids were measured before and after, keyed by file:line:column:target. 26 of 27 came back byte-identical. The 27th was the doc-comments receipt, and it disappeared.