file.c:12, the search file.c::'grep' and the petrified file.c:12:d8A link is an in-prose reference to a file and a position in it.
Links are used in code comments, tickets, chats and docs.
A link differs from a URI in its minimal syntax.
It merely needs a file name and enough of a path to disambiguate.
A link is resolved by an index, not by a filesystem.
For that reason, absolute or relative paths are unnecessary.
A link is three slots, file:line:proof; an author writes a seat or a search, wordy if need be, and the commit hook petrifies it short (petrify).
The shape is Pike's sam address, file:address, as the Plan 9 plumber reads it off plain prose; the :: search is Org-mode's inspired.
The grammar accepts ticket | relfile | rooted | authed | scope; a nav target is a ticket code, or a name carrying evidence — a known extension, or an anchor past a slash.
Examples, one per accepted shape:
main.js — a known extension is evidence by itselfdog/tok/LINK.rl:52 — dirs, known ext, a lineabc/Makefile:20 — ext-less name vouched for by an anchor past a slashsrc/weird.xyz:12 — unknown extension, same vouching/README:10, ./view/pager.js:452 — rooted, the anchor is mandatory//libdog/tok/LINK.rl:88 — the repo named up front, as in Rep:; a path and an anchor are mandatoryLITE-029, DOG-034:64 — ticket code, optionally anchoredA target may stand alone, naming the whole file (main.js, LITE-029). Past the first colon come a LINE slot and a PROOF slot, file:line:proof, at least one of them filled: main.js: and main.js:: are nothing. A line alone is a SEAT, transient; an empty line slot is a SEARCH (::); a line with a proof is petrified. The proof kind is told by its first character. Ruled 2026-09-22; the single-colon search forms of BEE-073 retire once the grammar lands.
min_addr.c:30 — a seat: honest at the moment of writing, the hook petrifies itmin_addr.c:30:24 — line:col, transient too; an all-digit proof is a column, not a proofmin_addr.c:30:LjJ — the petrified form: line + hashlet, 2..10 ron64 chars carrying a non-digit; a BLOB hashlet (a prefix of the blob id) or a TOKEN hashlet (one char per token off the line), told apart by resolution, not by shapemin_addr.c:30:'Earliest wins' — raw line + grep proof, any length up to the cap: the line if it carries the text, else the nearest line that doesmin_addr.c:30:"earliest($1)" — line + spot proof: $1 binds one token, $a a block or nothing, $A a block; two spaces are a gapmin_addr.c:30:/best = c/ — line + regex proofCAPO.c::'Compose URI' — a search, substring; verbatim, as in a shell single quote; first match winsCAPO.c::"ok64 $1 = OK;" — a search, spot snippetGREP.c::/CAPO\w+Grep/ — a search, regexHUNK.c::HUNKu8sMakeURI — a search, symbol: its definition, found by per-file-type snippets such as $0($a){$b}file::24 and file::d8 are nothing.\ ('it\'s', /a\/b/), nothing else is.x.c:12,20), no sam chains (x.c:1599/re/), no second proof (x.c:12:'a':'b').abc/Makefile:20, abc/Makefile::'ragel'); a symbol vouches for nothing, either/or::pick stays prose.* spells */ and closes the comment: use a // comment or end it in {0,}.Raw forms are for writing and may be wordy: a seat f.c:12, a search f.c::'Earliest wins the tie', a proofed seat f.c:12:"earliest($1)". The hook (same repo, at commit) and bee mint (any repo, any time) turn each into the short form f.c:12:d8, line plus hashlet. Stability is the LINE's, not the file's: a line whose content stands in a trunk-reachable blob gets a BLOB hashlet, of the newest such blob, with the line's number in that blob (LITE-025), so the follow can diff since then and show the original line. A line in doubt gets a TOKEN hashlet, one ron64 char per token: uncommitted, or committed on a topic branch only. Tokens survive re-indent, re-wrap, reformat, rebases, squashes and rewrites; the line tells repeated content apart by proximity, so 2..4 chars do (BEE-073).
f.c:12 → f.c:9:d8 (the line as numbered in its stable blob) or f.c:12:LjJ — same-repo the staged line is the one the author saw; a sibling may have moved since, so tickets cite siblings as searches, and bee cite prints links petrified.f.c::'grep', f.c::"spot", f.c::/re/ → the same, on exactly one hit, minted off the hit line.f.c:12:'grep', f.c:12:"spot", f.c:12:/re/ → the same, if the proof holds at the seat; else the proof is followed first, then the line refreshed.f.c:12:24 → the same, a token hashlet starting at the token under column 24, the column dropped.}, blank) takes the blob form or stays and is reported.bee mint --upgrade swaps a token hashlet for the blob one once the line's content is on the trunk, and the line may be refreshed on a same-repo commit when the follow says "moved".::name), scopes and bare targets never petrify: a definition is meant to move, a query is a query.:12:d8 is DOG-050's permalink shape as it stands; petrifying changes the minter and the follower, not the grammar. Only :: is new.A target may be a scope, not one file; the link is then a query. A scope link opens the search view on all the matches, has no line slot ever, and is never petrified.
.c::/u8\w+Feed/ — an ext family at any depth, since a target is never rooted unless it says soabc/::'TODO' — a subtreeabc/.c::"malloc($1*$A);" — a subtree and a family at once.c::HUNKu8sMakeURI — the definition, wherever it lives//libdog/.h::'ok64' — a scope in a named repo**/*.c would spell */ inside a comment, and * is emphasis in prose.