@descent-vtt/spec-graph 0.11.1 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Document identity and reference normalisation.\n *\n * Foreign-key validation across a corpus of Markdown only works if `ADR-7`,\n * `ADR-0007`, `adr 7`, `[[0007-sharding]]` and `../adr/0007-sharding.md` are\n * understood to name the same document. Getting that wrong in either direction\n * is fatal: too strict and every real repository drowns in false \"broken\n * reference\" findings, too loose and `RFC 7` silently resolves to `ADR-7`.\n *\n * The resolution here is deliberately asymmetric. **Textual aliases** (`adr-7`)\n * are global, because a prefix already names the family. **Bare numbers** (`7`,\n * `#0007`) are resolved only within the citing document's own family, because a\n * repository with both `adr/0007` and `rfc/0007` is normal and guessing between\n * them would be worse than reporting nothing.\n */\n\n/** Directory names that name a specification family, mapped to a canonical prefix. */\nconst FAMILY_DIRECTORIES: Readonly<Record<string, string>> = {\n adr: 'ADR',\n adrs: 'ADR',\n 'architecture-decisions': 'ADR',\n 'decision-records': 'ADR',\n decisions: 'ADR',\n rfc: 'RFC',\n rfcs: 'RFC',\n kep: 'KEP',\n keps: 'KEP',\n enhancements: 'KEP',\n proposal: 'PROPOSAL',\n proposals: 'PROPOSAL',\n design: 'DESIGN',\n designs: 'DESIGN',\n prd: 'PRD',\n prds: 'PRD',\n spec: 'SPEC',\n specs: 'SPEC',\n};\n\n/** Front-matter keys that may carry an explicit identifier, in preference order. */\nexport const ID_KEYS: readonly string[] = ['id', 'adr', 'adr-id', 'rfc', 'rfc-id', 'kep-number', 'number', 'slug'];\n\n/** `ADR-0007`, `KEP 1234`, `rfc2119`: a family prefix followed by a number. */\nconst PREFIXED_ID = /^([A-Za-z][A-Za-z0-9_]{0,15}?)[\\s._-]*(\\d{1,6})$/;\n/** A bare number, optionally hash-prefixed: `7`, `#0007`. */\nconst BARE_NUMBER = /^#?(\\d{1,6})$/;\n\nexport interface DocumentIdentity {\n /** The canonical id: `ADR-0007`, or a path when nothing better exists. */\n readonly id: string;\n /** Family prefix, upper-cased. `null` when the document is not numbered. */\n readonly family: string | null;\n /** Sequence number without leading zeros. `null` when not numbered. */\n readonly number: number | null;\n /** Every lower-cased spelling that should resolve to this document. */\n readonly aliases: readonly string[];\n}\n\nexport interface IdentityInput {\n /** Repository-relative POSIX path. */\n readonly path: string;\n /** An id declared by a directive or front matter, if any. */\n readonly declaredId: string | null;\n /** Extra aliases declared by a directive or front matter. */\n readonly declaredAliases: readonly string[];\n /** The document H1, if any. */\n readonly heading: string | null;\n /**\n * Whether this identity is the file's own. Default true.\n *\n * A specification that is a *region* of a file must not claim the file's\n * path: the file already answers to it, and two nodes answering to one path\n * would make every link to that file ambiguous. The file's *name* is the same\n * claim spelled differently, so a region does not read a number out of it\n * either: every row of a register kept in `0042-open-issues.md` answered to\n * ADR-0042, and so did the file.\n */\n readonly includePathAliases?: boolean | undefined;\n}\n\n/** An id has to be a name. Punctuation alone is what a bad parse leaves behind. */\nexport const HAS_LETTER_OR_DIGIT = /[\\p{L}\\p{N}]/u;\n\n/**\n * Derives a document's canonical id and every alias that should reach it.\n *\n * Evidence is used in order of how deliberate it is: an explicit declaration,\n * then the file name, then the H1. The first two both *name* the document, so a\n * declaration that carries no number still takes the number from the file name\n * beside it - `slug: sharding-the-write-path` in `0007-sharding.md` is ADR-0007.\n * A title only *describes* it, so it is read as a name only where nothing else\n * has given one. A losing declaration or file name still contributes aliases,\n * so a reference written in either form resolves; a title that lost does not,\n * because the identifier it opens with belongs to the document it is about.\n */\nexport function identify(input: IdentityInput): DocumentIdentity {\n // A declaration carrying no letter or digit is discarded rather than honoured.\n // `<!-- @spec-node id=\\\"ADR-9\\\" -->`, written inside a JavaScript string in a\n // worked example, parses its bare value as a lone backslash - and a document\n // whose id is `\\` collides with every other one that made the same mistake,\n // exports as invalid Mermaid, and names itself in findings a reader cannot act\n // on. There is nothing to gain by taking such a value seriously. The null\n // check is for the type: `test(null)` reads the word \"null\" and passes, and\n // the value it would keep is null anyway.\n const declaredId = input.declaredId !== null && HAS_LETTER_OR_DIGIT.test(input.declaredId) ? input.declaredId : null;\n // What the flag decides, named for what it means: the file's path and its\n // name are one claim, and a region makes neither of them.\n const ownsFile = input.includePathAliases !== false;\n const stem = fileStem(input.path);\n const directoryFamily = familyFromPath(input.path);\n const aliases = new Set<string>();\n\n const add = (value: string | null | undefined): void => {\n if (!value) return;\n const key = normaliseRef(value);\n if (key.length > 0) aliases.add(key);\n };\n\n // Only a spelling that yields a number decides anything below, so only those\n // are kept. `digits` is the number as it was written there.\n const candidates: { family: string | null; number: number; digits: string }[] = [];\n\n const consider = (raw: string | null, allowBare: boolean): void => {\n if (!raw) return;\n const cleaned = raw.trim();\n const prefixed = PREFIXED_ID.exec(cleaned);\n if (prefixed) {\n const digits = prefixed[2] as string;\n candidates.push({ family: (prefixed[1] as string).toUpperCase(), number: Number.parseInt(digits, 10), digits });\n return;\n }\n const bare = BARE_NUMBER.exec(cleaned);\n if (bare && allowBare) {\n const digits = bare[1] as string;\n candidates.push({ family: directoryFamily, number: Number.parseInt(digits, 10), digits });\n }\n };\n\n consider(declaredId, true);\n // `0007-sharding-the-write-path.md` and `kep-1234-foo.md` both start with the\n // identifier; take the leading token rather than the whole stem. Only for the\n // file's own identity: the name names the file, and a region reading its\n // number is the collision ADR-0009 forbids, reached by a second route.\n if (ownsFile) consider(leadingToken(stem), true);\n\n // A title is prose *about* a document, so it names one only where nothing has\n // named it yet, and where it does not it registers no alias either - an alias\n // is the same mistake one step removed, landing on a document that is real.\n // A number disqualifies it, because a different number in a title is about a\n // different document; so does being a region, because a row's id column\n // exists to hold an identifier while its title cell is prose about the rest\n // of the corpus. `| OI-V-05 | ADR-040's enforcement point has no browser\n // test |` became ADR-040, collided with the real one and left the issue with\n // no node at all: 27 of 232 rows of one register, nine inventing a `STAGE-1`\n // nobody had written. A *file* keeps the looser reading deliberately - there\n // the two cases are one shape, `slug:` holds a URL segment rather than an id,\n // and `slug: sharding` under `# ADR-0007` has to stay ADR-0007. See the fifth\n // asymmetry in ADR-0004 for what that costs and why it is not guessed at.\n //\n // Considered last, it names the document only where nothing before it gave\n // a number: only the first candidate is read. A title that does name it\n // supplies the number, and the spellings of that number registered below\n // include the title's own. `false` decides nothing, since an identifier a\n // heading opens with always starts with letters; it says a bare number in a\n // title would name nothing.\n consider(ownsFile ? headingId(input.heading) : null, false);\n\n const numbered = candidates[0];\n const family = numbered?.family ?? directoryFamily;\n const number = numbered?.number ?? null;\n\n let id: string;\n if (numbered !== undefined && family !== null) {\n // Written as the number was written where it was found, which is the local\n // zero-padding convention: an id read from the H1 of `sharding.md` still\n // renders as ADR-0007, not ADR-7, and one declared as `S3-0012` stays that.\n id = `${family}-${numbered.digits}`;\n } else if (declaredId) {\n id = declaredId.trim();\n } else if (number !== null) {\n // Numbered but with no family to prefix - a Rust RFC in `text/0001-foo.md`,\n // say. The file stem is a far better identity than a bare \"1\": it is what\n // people actually type, and it stays unique across the repository.\n //\n // A file's stem is never empty - `.md` is its own - so this fallback, and\n // with it the difference between this branch and the next, is reached only\n // by a caller identifying an empty path.\n id = stem.length > 0 ? stem : String(number);\n } else {\n id = stem.length > 0 ? stem : input.path;\n }\n\n add(id);\n if (ownsFile) {\n add(stem);\n add(input.path);\n add(stripExtension(input.path));\n }\n add(declaredId);\n for (const alias of input.declaredAliases) add(alias);\n\n if (numbered !== undefined) {\n const spellings = numberSpellings(numbered.number, numbered.digits);\n if (family !== null) {\n // Every spelling a human might type. `normaliseRef` folds separators, so\n // `adr-7`, `adr 7` and `adr7` collapse to one key; the padding variants do\n // not, and each needs registering.\n for (const digits of spellings) add(`${family}-${digits}`);\n } else {\n // With no family there is only one number space in the repository, so a\n // bare `0001` is unambiguous and worth resolving.\n for (const digits of spellings) add(digits);\n }\n }\n\n return { id, family, number, aliases: [...aliases] };\n}\n\n/**\n * Folds a reference to its lookup key.\n *\n * Case, separators and surrounding punctuation carry no meaning in a citation,\n * so `ADR-0007`, `adr 0007` and `Adr_0007` all become `adr0007`.\n */\nexport function normaliseRef(value: string): string {\n // The separators go last and take every space with them, so nothing is left\n // to trim. `+` only saves work: each separator is removed either way.\n return value\n .trim()\n .toLowerCase()\n .replace(/^[#<([]+|[)\\]>.,;:]+$/g, '')\n .replace(/[\\s._-]+/g, '');\n}\n\n/** Splits a `target#anchor` reference. */\nexport function splitAnchor(target: string): { target: string; anchor: string | null } {\n const hash = target.indexOf('#');\n if (hash === -1) return { target, anchor: null };\n // A leading `#` leaves the target empty: an anchor in the citing document.\n return { target: target.slice(0, hash), anchor: target.slice(hash + 1) };\n}\n\n/** Parses `ADR-0007` into its family and number, or returns `null`. */\nexport function parsePrefixedRef(value: string): { family: string; number: number } | null {\n const match = PREFIXED_ID.exec(value.trim().replace(/^[#([]+/, ''));\n if (!match) return null;\n return { family: (match[1] as string).toUpperCase(), number: Number.parseInt(match[2] as string, 10) };\n}\n\n/** Parses a bare `7` or `#0007`, or returns `null`. */\nexport function parseBareRef(value: string): number | null {\n const match = BARE_NUMBER.exec(value.trim());\n return match ? Number.parseInt(match[1] as string, 10) : null;\n}\n\n/** True when a reference target names a location rather than an identifier. */\nexport function looksLikePath(target: string): boolean {\n return target.includes('/') || DOCUMENT_EXTENSION.test(target);\n}\n\nconst DOCUMENT_EXTENSION = /\\.(md|markdown|mdx|txt|rst|adoc|asciidoc)$/i;\n\n/**\n * True when a path-like target could name a specification.\n *\n * A specification linking to `../../src/rules.ts` is citing source code, which\n * is a different tool's job. Treating it as a dangling specification reference\n * would report an error on one of the most ordinary things a design document\n * does, so targets carrying a non-document extension are not references at all.\n *\n * A dotfile is such a target too - `../.nvmrc` names a file whose extension is\n * its whole name - and so is a file whose extension is long: `arch.excalidraw`\n * is a drawing, not an identifier with a dot in it (ADR-0006).\n */\nexport function isDocumentTarget(target: string): boolean {\n const withoutAnchor = splitAnchor(target).target;\n const base = withoutAnchor.slice(withoutAnchor.lastIndexOf('/') + 1);\n const dot = base.lastIndexOf('.');\n // No extension: a directory, or an extensionless spec such as `rfcs/0007`.\n // Said outright rather than left to the test below, which would refuse the\n // last character `slice(-1)` handed it and answer the same.\n if (dot === -1) return true;\n const extension = base.slice(dot);\n // Only a file extension disqualifies a target, and one starts with a letter.\n // An identifier like `v1.2` or `ADR-0007.1` keeps its dot and is still\n // resolved as an id. The extension starts at the last dot and holds no\n // other, so the leading anchor decides nothing; it says the dot is where the\n // extension begins.\n if (!/^\\.[A-Za-z][A-Za-z0-9]*$/.test(extension)) return true;\n return DOCUMENT_EXTENSION.test(base);\n}\n\n/** True when a reference target points outside the repository. */\nexport function isExternal(target: string): boolean {\n return /^[a-z][a-z0-9+.-]*:/i.test(target) && !/^[a-z]:[\\\\/]/i.test(target);\n}\n\n/** The family prefix implied by a document's directory, if any. */\nexport function familyFromPath(posixPath: string): string | null {\n const segments = posixPath.toLowerCase().split('/');\n // Nearest enclosing directory wins: `docs/rfcs/adr/0007.md` is an ADR.\n for (let i = segments.length - 2; i >= 0; i -= 1) {\n const found = FAMILY_DIRECTORIES[segments[i] as string];\n if (found) return found;\n }\n return null;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Internals */\n/* -------------------------------------------------------------------------- */\n\nfunction fileStem(posixPath: string): string {\n const base = posixPath.slice(posixPath.lastIndexOf('/') + 1);\n const dot = base.lastIndexOf('.');\n const stem = dot > 0 ? base.slice(0, dot) : base;\n // `docs/adr/0007-sharding/README.md` is identified by its directory - but a\n // README at the repository root has no directory to be named after, and must\n // keep its own name. A repository-relative path never starts with `/`, so\n // `slash > 0` and `slash >= 0` agree on every path this is given.\n const slash = posixPath.lastIndexOf('/');\n if (slash > 0 && /^(readme|index)$/i.test(stem)) {\n const parent = posixPath.slice(0, slash);\n return parent.slice(parent.lastIndexOf('/') + 1);\n }\n return stem;\n}\n\nfunction stripExtension(posixPath: string): string {\n const dot = posixPath.lastIndexOf('.');\n const slash = posixPath.lastIndexOf('/');\n return dot > slash ? posixPath.slice(0, dot) : posixPath;\n}\n\n/**\n * `kep-1234-foo` -> `kep-1234`; `0007-sharding` -> `0007`; `sharding` -> `sharding`.\n *\n * The `$` alternatives decide nothing: a stem that is the identifier alone is\n * handed back whole by the last line, and parses the same. They say that an\n * identifier may end the name as well as open it.\n */\nfunction leadingToken(stem: string): string {\n const prefixed = /^([A-Za-z]{1,15}[\\s._-]?\\d{1,6})(?:[\\s._-]|$)/.exec(stem);\n if (prefixed) return prefixed[1] as string;\n const bare = /^(\\d{1,6})(?:[\\s._-]|$)/.exec(stem);\n if (bare) return bare[1] as string;\n return stem;\n}\n\n/** `# ADR-0007: Sharding the write path` -> `ADR-0007`. */\nfunction headingId(heading: string | null): string | null {\n // Only saves the call: `exec` would read null as the word \"null\", and\n // neither that nor an empty heading holds a number.\n if (!heading) return null;\n const match = /^\\s*([A-Za-z]{1,15}[\\s._-]?\\d{1,6})\\b/.exec(heading);\n return match ? (match[1] as string) : null;\n}\n\n/**\n * Zero-padded spellings a citation might reasonably use: the number as it was\n * written, unpadded, and padded to three and to four digits.\n */\nfunction numberSpellings(number: number, written: string): string[] {\n const unpadded = String(number);\n return [...new Set([written, unpadded, unpadded.padStart(3, '0'), unpadded.padStart(4, '0')])];\n}\n\n/**\n * True when two folded spellings are one edit apart.\n *\n * One edit, not two, and a transposition counts as one: `supercedesby` for\n * `supercededby` is a slip of the finger, `categories` for `dependencies` is a\n * different word. Written out rather than as a distance matrix because the\n * answer is a yes or a no and the bound is one - the general algorithm would be\n * more code, not less.\n */\nexport function withinOneEdit(a: string, b: string): boolean {\n // The loop bounds are not boundaries: one step past the end of equal-length\n // strings compares undefined with undefined and continues, and one past the\n // end of the shorter compares nothing with the last character of the longer\n // and finds the two empty remainders equal - both the answer the loop's exit\n // gives. Nor does `<` against `<=` below matter: equal lengths never get there.\n if (a === b) return false;\n if (a.length === b.length) {\n let first = -1;\n for (let i = 0; i < a.length; i += 1) {\n if (a[i] === b[i]) continue;\n if (first === -1) {\n first = i;\n continue;\n }\n // A second difference is allowed only if the two are a swapped pair, and\n // only if there is no third.\n return first === i - 1 && a[first] === b[i] && a[i] === b[first] && a.slice(i + 1) === b.slice(i + 1);\n }\n return true;\n }\n const [short, long] = a.length < b.length ? [a, b] : [b, a];\n if (long.length - short.length !== 1) return false;\n for (let i = 0; i < short.length; i += 1) {\n if (short[i] === long[i]) continue;\n return short.slice(i) === long.slice(i + 1);\n }\n return true;\n}\n"]}
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Document identity and reference normalisation.\n *\n * Foreign-key validation across a corpus of Markdown only works if `ADR-7`,\n * `ADR-0007`, `adr 7`, `[[0007-sharding]]` and `../adr/0007-sharding.md` are\n * understood to name the same document. Getting that wrong in either direction\n * is fatal: too strict and every real repository drowns in false \"broken\n * reference\" findings, too loose and `RFC 7` silently resolves to `ADR-7`.\n *\n * The resolution here is deliberately asymmetric. **Textual aliases** (`adr-7`)\n * are global, because a prefix already names the family. **Bare numbers** (`7`,\n * `#0007`) are resolved only within the citing document's own family, because a\n * repository with both `adr/0007` and `rfc/0007` is normal and guessing between\n * them would be worse than reporting nothing.\n */\n\n/**\n * Directory names that name a specification family, mapped to a canonical prefix.\n *\n * A map, because a directory is named by whoever made it and an object answers\n * to more names than it was given: `docs/constructor/` was a family, of no\n * prefix, and the run that read a document in it ended there.\n */\nconst FAMILY_DIRECTORIES: ReadonlyMap<string, string> = new Map([\n ['adr', 'ADR'],\n ['adrs', 'ADR'],\n ['architecture-decisions', 'ADR'],\n ['decision-records', 'ADR'],\n ['decisions', 'ADR'],\n ['rfc', 'RFC'],\n ['rfcs', 'RFC'],\n ['kep', 'KEP'],\n ['keps', 'KEP'],\n ['enhancements', 'KEP'],\n ['proposal', 'PROPOSAL'],\n ['proposals', 'PROPOSAL'],\n ['design', 'DESIGN'],\n ['designs', 'DESIGN'],\n ['prd', 'PRD'],\n ['prds', 'PRD'],\n ['spec', 'SPEC'],\n ['specs', 'SPEC'],\n]);\n\n/** Front-matter keys that may carry an explicit identifier, in preference order. */\nexport const ID_KEYS: readonly string[] = ['id', 'adr', 'adr-id', 'rfc', 'rfc-id', 'kep-number', 'number', 'slug'];\n\n/** `ADR-0007`, `KEP 1234`, `rfc2119`: a family prefix followed by a number. */\nconst PREFIXED_ID = /^([A-Za-z][A-Za-z0-9_]{0,15}?)[\\s._-]*(\\d{1,6})$/;\n/** A bare number, optionally hash-prefixed: `7`, `#0007`. */\nconst BARE_NUMBER = /^#?(\\d{1,6})$/;\n\nexport interface DocumentIdentity {\n /** The canonical id: `ADR-0007`, or a path when nothing better exists. */\n readonly id: string;\n /** Family prefix, upper-cased. `null` when the document is not numbered. */\n readonly family: string | null;\n /** Sequence number without leading zeros. `null` when not numbered. */\n readonly number: number | null;\n /** Every lower-cased spelling that should resolve to this document. */\n readonly aliases: readonly string[];\n}\n\nexport interface IdentityInput {\n /** Repository-relative POSIX path. */\n readonly path: string;\n /** An id declared by a directive or front matter, if any. */\n readonly declaredId: string | null;\n /** Extra aliases declared by a directive or front matter. */\n readonly declaredAliases: readonly string[];\n /** The document H1, if any. */\n readonly heading: string | null;\n /**\n * Whether this identity is the file's own. Default true.\n *\n * A specification that is a *region* of a file must not claim the file's\n * path: the file already answers to it, and two nodes answering to one path\n * would make every link to that file ambiguous. The file's *name* is the same\n * claim spelled differently, so a region does not read a number out of it\n * either: every row of a register kept in `0042-open-issues.md` answered to\n * ADR-0042, and so did the file.\n */\n readonly includePathAliases?: boolean | undefined;\n}\n\n/** An id has to be a name. Punctuation alone is what a bad parse leaves behind. */\nexport const HAS_LETTER_OR_DIGIT = /[\\p{L}\\p{N}]/u;\n\n/**\n * Derives a document's canonical id and every alias that should reach it.\n *\n * Evidence is used in order of how deliberate it is: an explicit declaration,\n * then the file name, then the H1. The first two both *name* the document, so a\n * declaration that carries no number still takes the number from the file name\n * beside it - `slug: sharding-the-write-path` in `0007-sharding.md` is ADR-0007.\n * A title only *describes* it, so it is read as a name only where nothing else\n * has given one. A losing declaration or file name still contributes aliases,\n * so a reference written in either form resolves; a title that lost does not,\n * because the identifier it opens with belongs to the document it is about.\n */\nexport function identify(input: IdentityInput): DocumentIdentity {\n // A declaration carrying no letter or digit is discarded rather than honoured.\n // `<!-- @spec-node id=\\\"ADR-9\\\" -->`, written inside a JavaScript string in a\n // worked example, parses its bare value as a lone backslash - and a document\n // whose id is `\\` collides with every other one that made the same mistake,\n // exports as invalid Mermaid, and names itself in findings a reader cannot act\n // on. There is nothing to gain by taking such a value seriously. The null\n // check is for the type: `test(null)` reads the word \"null\" and passes, and\n // the value it would keep is null anyway.\n const declaredId = input.declaredId !== null && HAS_LETTER_OR_DIGIT.test(input.declaredId) ? input.declaredId : null;\n // What the flag decides, named for what it means: the file's path and its\n // name are one claim, and a region makes neither of them.\n const ownsFile = input.includePathAliases !== false;\n const stem = fileStem(input.path);\n const directoryFamily = familyFromPath(input.path);\n const aliases = new Set<string>();\n\n const add = (value: string | null | undefined): void => {\n if (!value) return;\n const key = normaliseRef(value);\n if (key.length > 0) aliases.add(key);\n };\n\n // Only a spelling that yields a number decides anything below, so only those\n // are kept. `digits` is the number as it was written there.\n const candidates: { family: string | null; number: number; digits: string }[] = [];\n\n const consider = (raw: string | null, allowBare: boolean): void => {\n if (!raw) return;\n const cleaned = raw.trim();\n const prefixed = PREFIXED_ID.exec(cleaned);\n if (prefixed) {\n const digits = prefixed[2] as string;\n candidates.push({ family: (prefixed[1] as string).toUpperCase(), number: Number.parseInt(digits, 10), digits });\n return;\n }\n const bare = BARE_NUMBER.exec(cleaned);\n if (bare && allowBare) {\n const digits = bare[1] as string;\n candidates.push({ family: directoryFamily, number: Number.parseInt(digits, 10), digits });\n }\n };\n\n consider(declaredId, true);\n // `0007-sharding-the-write-path.md` and `kep-1234-foo.md` both start with the\n // identifier; take the leading token rather than the whole stem. Only for the\n // file's own identity: the name names the file, and a region reading its\n // number is the collision ADR-0009 forbids, reached by a second route.\n if (ownsFile) consider(leadingToken(stem), true);\n\n // A title is prose *about* a document, so it names one only where nothing has\n // named it yet, and where it does not it registers no alias either - an alias\n // is the same mistake one step removed, landing on a document that is real.\n // A number disqualifies it, because a different number in a title is about a\n // different document; so does being a region, because a row's id column\n // exists to hold an identifier while its title cell is prose about the rest\n // of the corpus. `| OI-V-05 | ADR-040's enforcement point has no browser\n // test |` became ADR-040, collided with the real one and left the issue with\n // no node at all: 27 of 232 rows of one register, nine inventing a `STAGE-1`\n // nobody had written. A *file* keeps the looser reading deliberately - there\n // the two cases are one shape, `slug:` holds a URL segment rather than an id,\n // and `slug: sharding` under `# ADR-0007` has to stay ADR-0007. See the fifth\n // asymmetry in ADR-0004 for what that costs and why it is not guessed at.\n //\n // Considered last, it names the document only where nothing before it gave\n // a number: only the first candidate is read. A title that does name it\n // supplies the number, and the spellings of that number registered below\n // include the title's own. `false` decides nothing, since an identifier a\n // heading opens with always starts with letters; it says a bare number in a\n // title would name nothing.\n consider(ownsFile ? headingId(input.heading) : null, false);\n\n const numbered = candidates[0];\n const family = numbered?.family ?? directoryFamily;\n const number = numbered?.number ?? null;\n\n let id: string;\n if (numbered !== undefined && family !== null) {\n // Written as the number was written where it was found, which is the local\n // zero-padding convention: an id read from the H1 of `sharding.md` still\n // renders as ADR-0007, not ADR-7, and one declared as `S3-0012` stays that.\n id = `${family}-${numbered.digits}`;\n } else if (declaredId) {\n id = declaredId.trim();\n } else if (number !== null) {\n // Numbered but with no family to prefix - a Rust RFC in `text/0001-foo.md`,\n // say. The file stem is a far better identity than a bare \"1\": it is what\n // people actually type, and it stays unique across the repository.\n //\n // A file's stem is never empty - `.md` is its own - so this fallback, and\n // with it the difference between this branch and the next, is reached only\n // by a caller identifying an empty path.\n id = stem.length > 0 ? stem : String(number);\n } else {\n id = stem.length > 0 ? stem : input.path;\n }\n\n add(id);\n if (ownsFile) {\n add(stem);\n add(input.path);\n add(stripExtension(input.path));\n }\n add(declaredId);\n for (const alias of input.declaredAliases) add(alias);\n\n if (numbered !== undefined) {\n const spellings = numberSpellings(numbered.number, numbered.digits);\n if (family !== null) {\n // Every spelling a human might type. `normaliseRef` folds separators, so\n // `adr-7`, `adr 7` and `adr7` collapse to one key; the padding variants do\n // not, and each needs registering.\n for (const digits of spellings) add(`${family}-${digits}`);\n } else {\n // With no family there is only one number space in the repository, so a\n // bare `0001` is unambiguous and worth resolving.\n for (const digits of spellings) add(digits);\n }\n }\n\n return { id, family, number, aliases: [...aliases] };\n}\n\n/**\n * Folds a reference to its lookup key.\n *\n * Case, separators and surrounding punctuation carry no meaning in a citation,\n * so `ADR-0007`, `adr 0007` and `Adr_0007` all become `adr0007`.\n */\nexport function normaliseRef(value: string): string {\n // The separators go last and take every space with them, so nothing is left\n // to trim. `+` only saves work: each separator is removed either way.\n return value\n .trim()\n .toLowerCase()\n .replace(/^[#<([]+|[)\\]>.,;:]+$/g, '')\n .replace(/[\\s._-]+/g, '');\n}\n\n/** Splits a `target#anchor` reference. */\nexport function splitAnchor(target: string): { target: string; anchor: string | null } {\n const hash = target.indexOf('#');\n if (hash === -1) return { target, anchor: null };\n // A leading `#` leaves the target empty: an anchor in the citing document.\n return { target: target.slice(0, hash), anchor: target.slice(hash + 1) };\n}\n\n/** Parses `ADR-0007` into its family and number, or returns `null`. */\nexport function parsePrefixedRef(value: string): { family: string; number: number } | null {\n const match = PREFIXED_ID.exec(value.trim().replace(/^[#([]+/, ''));\n if (!match) return null;\n return { family: (match[1] as string).toUpperCase(), number: Number.parseInt(match[2] as string, 10) };\n}\n\n/** Parses a bare `7` or `#0007`, or returns `null`. */\nexport function parseBareRef(value: string): number | null {\n const match = BARE_NUMBER.exec(value.trim());\n return match ? Number.parseInt(match[1] as string, 10) : null;\n}\n\n/** True when a reference target names a location rather than an identifier. */\nexport function looksLikePath(target: string): boolean {\n return target.includes('/') || DOCUMENT_EXTENSION.test(target);\n}\n\nconst DOCUMENT_EXTENSION = /\\.(md|markdown|mdx|txt|rst|adoc|asciidoc)$/i;\n\n/**\n * True when a path-like target could name a specification.\n *\n * A specification linking to `../../src/rules.ts` is citing source code, which\n * is a different tool's job. Treating it as a dangling specification reference\n * would report an error on one of the most ordinary things a design document\n * does, so targets carrying a non-document extension are not references at all.\n *\n * A dotfile is such a target too - `../.nvmrc` names a file whose extension is\n * its whole name - and so is a file whose extension is long: `arch.excalidraw`\n * is a drawing, not an identifier with a dot in it (ADR-0006).\n */\nexport function isDocumentTarget(target: string): boolean {\n const withoutAnchor = splitAnchor(target).target;\n const base = withoutAnchor.slice(withoutAnchor.lastIndexOf('/') + 1);\n const dot = base.lastIndexOf('.');\n // No extension: a directory, or an extensionless spec such as `rfcs/0007`.\n // Said outright rather than left to the test below, which would refuse the\n // last character `slice(-1)` handed it and answer the same.\n if (dot === -1) return true;\n const extension = base.slice(dot);\n // Only a file extension disqualifies a target, and one starts with a letter.\n // An identifier like `v1.2` or `ADR-0007.1` keeps its dot and is still\n // resolved as an id. The extension starts at the last dot and holds no\n // other, so the leading anchor decides nothing; it says the dot is where the\n // extension begins.\n if (!/^\\.[A-Za-z][A-Za-z0-9]*$/.test(extension)) return true;\n return DOCUMENT_EXTENSION.test(base);\n}\n\n/** True when a reference target points outside the repository. */\nexport function isExternal(target: string): boolean {\n return /^[a-z][a-z0-9+.-]*:/i.test(target) && !/^[a-z]:[\\\\/]/i.test(target);\n}\n\n/** The family prefix implied by a document's directory, if any. */\nexport function familyFromPath(posixPath: string): string | null {\n const segments = posixPath.toLowerCase().split('/');\n // Nearest enclosing directory wins: `docs/rfcs/adr/0007.md` is an ADR.\n for (let i = segments.length - 2; i >= 0; i -= 1) {\n const found = FAMILY_DIRECTORIES.get(segments[i] as string);\n if (found) return found;\n }\n return null;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Internals */\n/* -------------------------------------------------------------------------- */\n\nfunction fileStem(posixPath: string): string {\n const base = posixPath.slice(posixPath.lastIndexOf('/') + 1);\n const dot = base.lastIndexOf('.');\n const stem = dot > 0 ? base.slice(0, dot) : base;\n // `docs/adr/0007-sharding/README.md` is identified by its directory - but a\n // README at the repository root has no directory to be named after, and must\n // keep its own name. A repository-relative path never starts with `/`, so\n // `slash > 0` and `slash >= 0` agree on every path this is given.\n const slash = posixPath.lastIndexOf('/');\n if (slash > 0 && /^(readme|index)$/i.test(stem)) {\n const parent = posixPath.slice(0, slash);\n return parent.slice(parent.lastIndexOf('/') + 1);\n }\n return stem;\n}\n\nfunction stripExtension(posixPath: string): string {\n const dot = posixPath.lastIndexOf('.');\n const slash = posixPath.lastIndexOf('/');\n return dot > slash ? posixPath.slice(0, dot) : posixPath;\n}\n\n/**\n * `kep-1234-foo` -> `kep-1234`; `0007-sharding` -> `0007`; `sharding` -> `sharding`.\n *\n * The `$` alternatives decide nothing: a stem that is the identifier alone is\n * handed back whole by the last line, and parses the same. They say that an\n * identifier may end the name as well as open it.\n */\nfunction leadingToken(stem: string): string {\n const prefixed = /^([A-Za-z]{1,15}[\\s._-]?\\d{1,6})(?:[\\s._-]|$)/.exec(stem);\n if (prefixed) return prefixed[1] as string;\n const bare = /^(\\d{1,6})(?:[\\s._-]|$)/.exec(stem);\n if (bare) return bare[1] as string;\n return stem;\n}\n\n/** `# ADR-0007: Sharding the write path` -> `ADR-0007`. */\nfunction headingId(heading: string | null): string | null {\n // Only saves the call: `exec` would read null as the word \"null\", and\n // neither that nor an empty heading holds a number.\n if (!heading) return null;\n const match = /^\\s*([A-Za-z]{1,15}[\\s._-]?\\d{1,6})\\b/.exec(heading);\n return match ? (match[1] as string) : null;\n}\n\n/**\n * Zero-padded spellings a citation might reasonably use: the number as it was\n * written, unpadded, and padded to three and to four digits.\n */\nfunction numberSpellings(number: number, written: string): string[] {\n const unpadded = String(number);\n return [...new Set([written, unpadded, unpadded.padStart(3, '0'), unpadded.padStart(4, '0')])];\n}\n\n/**\n * True when two folded spellings are one edit apart.\n *\n * One edit, not two, and a transposition counts as one: `supercedesby` for\n * `supercededby` is a slip of the finger, `categories` for `dependencies` is a\n * different word. Written out rather than as a distance matrix because the\n * answer is a yes or a no and the bound is one - the general algorithm would be\n * more code, not less.\n */\nexport function withinOneEdit(a: string, b: string): boolean {\n // The loop bounds are not boundaries: one step past the end of equal-length\n // strings compares undefined with undefined and continues, and one past the\n // end of the shorter compares nothing with the last character of the longer\n // and finds the two empty remainders equal - both the answer the loop's exit\n // gives. Nor does `<` against `<=` below matter: equal lengths never get there.\n if (a === b) return false;\n if (a.length === b.length) {\n let first = -1;\n for (let i = 0; i < a.length; i += 1) {\n if (a[i] === b[i]) continue;\n if (first === -1) {\n first = i;\n continue;\n }\n // A second difference is allowed only if the two are a swapped pair, and\n // only if there is no third.\n return first === i - 1 && a[first] === b[i] && a[i] === b[first] && a.slice(i + 1) === b.slice(i + 1);\n }\n return true;\n }\n const [short, long] = a.length < b.length ? [a, b] : [b, a];\n if (long.length - short.length !== 1) return false;\n for (let i = 0; i < short.length; i += 1) {\n if (short[i] === long[i]) continue;\n return short.slice(i) === long.slice(i + 1);\n }\n return true;\n}\n"]}
package/dist/sections.js CHANGED
@@ -36,30 +36,34 @@ const TITLE_COLUMNS = /^(?:title|name|summary|description|subject|decision|topic
36
36
  * Written the way a header is written - "Depends on", "Superseded by" - rather
37
37
  * than the way a front-matter key is, because that is what an author types at
38
38
  * the top of a column.
39
+ *
40
+ * A map, because a header is whatever an author typed and an object answers to
41
+ * more names than it was given: a column headed `Constructor` was a relation
42
+ * of no kind, and every cell in it an edge to wherever it pointed.
39
43
  */
40
- const RELATION_COLUMNS = {
41
- 'depends on': { kind: 'depends-on', inverted: false },
42
- 'depends-on': { kind: 'depends-on', inverted: false },
43
- dependencies: { kind: 'depends-on', inverted: false },
44
- requires: { kind: 'depends-on', inverted: false },
45
- supersedes: { kind: 'supersedes', inverted: false },
46
- replaces: { kind: 'supersedes', inverted: false },
47
- 'superseded by': { kind: 'supersedes', inverted: true },
48
- 'replaced by': { kind: 'supersedes', inverted: true },
49
- 'blocked by': { kind: 'blocked-by', inverted: false },
50
- 'blocked on': { kind: 'blocked-by', inverted: false },
51
- blocks: { kind: 'blocked-by', inverted: true },
52
- amends: { kind: 'amends', inverted: false },
53
- extends: { kind: 'amends', inverted: false },
54
- assumes: { kind: 'assumes', inverted: false },
55
- 'delegates to': { kind: 'delegates-to', inverted: false },
56
- 'delegated to': { kind: 'delegates-to', inverted: false },
57
- 'tracked in': { kind: 'delegates-to', inverted: false },
58
- related: { kind: 'relates-to', inverted: false },
59
- 'related to': { kind: 'relates-to', inverted: false },
60
- 'see also': { kind: 'relates-to', inverted: false },
61
- references: { kind: 'references', inverted: false },
62
- };
44
+ const RELATION_COLUMNS = new Map([
45
+ ['depends on', { kind: 'depends-on', inverted: false }],
46
+ ['depends-on', { kind: 'depends-on', inverted: false }],
47
+ ['dependencies', { kind: 'depends-on', inverted: false }],
48
+ ['requires', { kind: 'depends-on', inverted: false }],
49
+ ['supersedes', { kind: 'supersedes', inverted: false }],
50
+ ['replaces', { kind: 'supersedes', inverted: false }],
51
+ ['superseded by', { kind: 'supersedes', inverted: true }],
52
+ ['replaced by', { kind: 'supersedes', inverted: true }],
53
+ ['blocked by', { kind: 'blocked-by', inverted: false }],
54
+ ['blocked on', { kind: 'blocked-by', inverted: false }],
55
+ ['blocks', { kind: 'blocked-by', inverted: true }],
56
+ ['amends', { kind: 'amends', inverted: false }],
57
+ ['extends', { kind: 'amends', inverted: false }],
58
+ ['assumes', { kind: 'assumes', inverted: false }],
59
+ ['delegates to', { kind: 'delegates-to', inverted: false }],
60
+ ['delegated to', { kind: 'delegates-to', inverted: false }],
61
+ ['tracked in', { kind: 'delegates-to', inverted: false }],
62
+ ['related', { kind: 'relates-to', inverted: false }],
63
+ ['related to', { kind: 'relates-to', inverted: false }],
64
+ ['see also', { kind: 'relates-to', inverted: false }],
65
+ ['references', { kind: 'references', inverted: false }],
66
+ ]);
63
67
  /** Cell values that mean "nothing here". */
64
68
  const MARKDOWN_LINK = /!?\[([^\]]*)\]\(([^)]*)\)/g;
65
69
  const EMPTY_CELL = /^(?:-+|—|–|n\/?a|none|nil|tbd|\.|_+)$/i;
@@ -239,7 +243,7 @@ function readSchema(table) {
239
243
  status = column;
240
244
  return;
241
245
  }
242
- const relation = RELATION_COLUMNS[name];
246
+ const relation = RELATION_COLUMNS.get(name);
243
247
  if (relation) {
244
248
  relations.push([column, relation]);
245
249
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"sections.js","sourceRoot":"","sources":["../src/sections.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Specification regions: the part of a file that one specification occupies.\n *\n * `spec-graph` began by assuming one specification per file, because that is\n * how MADR, KEPs and Rust RFCs are laid out. Plenty of organisations do not\n * work that way. A register, a whitepaper or a living architecture document\n * holds dozens of decisions in one file, each with its own identifier, its own\n * status and its own dependencies - and to an engine that binds a document to a\n * file, every one of them is invisible.\n *\n * The fix is small once stated: **a specification is a region of a file, not a\n * file.** A file yields at least one region - itself - and may yield more.\n * Everything downstream is unchanged, because a region produces exactly the\n * same `DocumentNode` a whole file produced.\n *\n * What this module decides is where those regions begin and end, and it is\n * deliberately hard to convince. A heading has to carry an identifier *and* the\n * section has to declare a status before it counts. One signal is not enough:\n * `## Q3 2026 Roadmap` parses as family `Q`, number 3, and `## v1.2.0` in a\n * changelog parses as family `v`, number 1. Neither is a specification, and\n * neither declares a status.\n */\n\nimport { attr, type Directive } from './directives.js';\nimport { HAS_LETTER_OR_DIGIT, isExternal, parsePrefixedRef } from './identity.js';\nimport { isStatusHeading } from './lifecycle.js';\nimport {\n isMarkdownLine,\n isOnlyComment,\n type Heading,\n type Range,\n type ScannedDocument,\n type Table,\n type TableCell,\n type TableRow,\n} from './markdown.js';\nimport type { EdgeKind } from './types.js';\n\n/** A status as written, and where it was written. */\nexport interface RegionStatus {\n readonly text: string;\n readonly start: number;\n readonly end: number;\n}\n\n/** A relation read from a table column rather than from prose. */\nexport interface TableRelation {\n readonly kind: EdgeKind;\n readonly inverted: boolean;\n readonly target: string;\n readonly start: number;\n readonly end: number;\n /** The column header that gave it its meaning, for the diagnostic. */\n readonly column: string;\n}\n\nexport interface SpecificationRegion {\n /** The heading that opens the region, or `null` for the file or a table row. */\n readonly heading: Heading | null;\n /** The table row that is the region, when it is one. */\n readonly row: TableRow | null;\n /** Offset the region starts at. */\n readonly start: number;\n /** Offset just past the region. */\n readonly end: number;\n /** Identifier read from the heading or the identifier column. */\n readonly declaredId: string | null;\n readonly title: string | null;\n readonly status: RegionStatus | null;\n /** A `@spec-node` directive inside the region, when there is one. */\n readonly directive: Directive | null;\n /** Relations declared by column, empty for a heading region. */\n readonly relations: readonly TableRelation[];\n /**\n * Cells whose content is already accounted for as a typed relation.\n *\n * Prose scanning skips these, so a link in a `Depends on` column produces one\n * typed edge rather than a typed edge and a neutral citation beside it.\n */\n readonly claimed: readonly Range[];\n}\n\n/** Column headers that name the row's identifier. */\nconst ID_COLUMNS = /^(?:id|ids|ref|reference|key|no|number|#|adr|rfc|kep|oi|decision|spec|specification)$/;\n\n/** Column headers that name the row's status. */\nconst STATUS_COLUMNS = /^(?:status|state|stage|lifecycle|phase)$/;\n\n/** Column headers that name the row's title. */\nconst TITLE_COLUMNS = /^(?:title|name|summary|description|subject|decision|topic)$/;\n\n/**\n * Column headers that declare a relation.\n *\n * Written the way a header is written - \"Depends on\", \"Superseded by\" - rather\n * than the way a front-matter key is, because that is what an author types at\n * the top of a column.\n */\nconst RELATION_COLUMNS: Readonly<Record<string, { kind: EdgeKind; inverted: boolean }>> = {\n 'depends on': { kind: 'depends-on', inverted: false },\n 'depends-on': { kind: 'depends-on', inverted: false },\n dependencies: { kind: 'depends-on', inverted: false },\n requires: { kind: 'depends-on', inverted: false },\n supersedes: { kind: 'supersedes', inverted: false },\n replaces: { kind: 'supersedes', inverted: false },\n 'superseded by': { kind: 'supersedes', inverted: true },\n 'replaced by': { kind: 'supersedes', inverted: true },\n 'blocked by': { kind: 'blocked-by', inverted: false },\n 'blocked on': { kind: 'blocked-by', inverted: false },\n blocks: { kind: 'blocked-by', inverted: true },\n amends: { kind: 'amends', inverted: false },\n extends: { kind: 'amends', inverted: false },\n assumes: { kind: 'assumes', inverted: false },\n 'delegates to': { kind: 'delegates-to', inverted: false },\n 'delegated to': { kind: 'delegates-to', inverted: false },\n 'tracked in': { kind: 'delegates-to', inverted: false },\n related: { kind: 'relates-to', inverted: false },\n 'related to': { kind: 'relates-to', inverted: false },\n 'see also': { kind: 'relates-to', inverted: false },\n references: { kind: 'references', inverted: false },\n};\n\n/** Cell values that mean \"nothing here\". */\nconst MARKDOWN_LINK = /!?\\[([^\\]]*)\\]\\(([^)]*)\\)/g;\nconst EMPTY_CELL = /^(?:-+|—|–|n\\/?a|none|nil|tbd|\\.|_+)$/i;\n\n/** `## ADR-0007: Sharding` - an identifier at the very start of a heading. */\nconst HEADING_ID = /^\\s*([A-Za-z]{1,15}[\\s._-]?\\d{1,6})\\b/;\n\n/** `**Status:** Accepted`, `Status: Accepted`, `*Status* : Accepted`. */\nconst INLINE_STATUS = /^[ \\t>]*[*_]{0,2}\\s*status\\s*[*_]{0,2}\\s*:\\s*(.+?)\\s*$/im;\n\n/**\n * Finds every specification in a file.\n *\n * The first entry is always the file itself, so a caller that ignores the rest\n * behaves exactly as it did before regions existed.\n */\nexport function findSpecificationRegions(\n scanned: ScannedDocument,\n directives: readonly Directive[],\n /** The file's own identifier, so its title heading is not read as a sub-region. */\n fileId: string,\n): SpecificationRegion[] {\n // The file's own relations come from its front matter and prose, never from\n // here, and it claims no cell: nothing reads either list off this region.\n const whole: SpecificationRegion = {\n heading: null,\n row: null,\n start: scanned.bodyStart,\n end: scanned.text.length,\n declaredId: null,\n title: null,\n status: null,\n directive: null,\n relations: [],\n claimed: [],\n };\n\n const regions: SpecificationRegion[] = [];\n const folded = fold(fileId);\n const fileRef = parsePrefixedRef(fileId);\n\n for (let i = 0; i < scanned.headings.length; i += 1) {\n const heading = scanned.headings[i] as Heading;\n const declaredId = HEADING_ID.exec(heading.text)?.[1];\n if (declaredId === undefined) continue;\n // The file's own title heading names the file, not a region inside it.\n if (fold(declaredId) === folded) continue;\n // Padding is not part of an identity anywhere else - `ADR-40`, `ADR-040`\n // and `ADR-0040` all resolve to one document - so a heading that spells the\n // number differently from the file name is still that file's own title.\n // Comparing the text alone made `# ADR-040` inside `0040-enforce.md` a\n // region within the file it names: one decision, two nodes, and a citation\n // arriving at whichever spelling it happened to use.\n // `HEADING_ID` captures only what `PREFIXED_ID` parses, so `ref` is never\n // null here; the check is for the type.\n const ref = parsePrefixedRef(declaredId);\n if (ref !== null && fileRef !== null && ref.family === fileRef.family && ref.number === fileRef.number) continue;\n\n const end = regionEnd(scanned, i);\n const directive = directiveIn(directives, heading.end, end);\n const status = readRegionStatus(scanned, heading.end, end);\n\n // Two independent signals are required. An identifier alone matches\n // `## Q3 2026 Roadmap` and `## v1.2.0`; a status alone matches every\n // ordinary `## Status` section in a normal ADR.\n if (status === null && directive === null) continue;\n\n // A directive's id wins, but only if it is a name. One that is not - the\n // lone backslash `id=\\\"ADR-9\\\"` parses to, written inside a string - would\n // be discarded when the region is identified, and a region with nothing\n // to go by falls back to the file's own name: the file then contained\n // itself, and the section vanished. The heading's id is the one to keep.\n const directiveId = directive ? attr(directive, 'id')?.value : undefined;\n\n regions.push({\n heading,\n row: null,\n start: heading.start,\n end,\n declaredId: directiveId !== undefined && HAS_LETTER_OR_DIGIT.test(directiveId) ? directiveId : declaredId,\n title: heading.text,\n status,\n directive,\n relations: [],\n // Prose inside a section is prose: a heading region claims no cell.\n claimed: [],\n });\n }\n\n return [whole, ...regions, ...findTableRegions(scanned)];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Registers kept as tables */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Reads a table whose columns describe specifications.\n *\n * A table qualifies when it has an identifier column *and* either a status\n * column or at least one relation column. Two signals again, for the same\n * reason: a table of identifiers and prose is a citation list, and promoting\n * every row of it to a specification would invent a register nobody wrote.\n */\nexport function findTableRegions(scanned: ScannedDocument): SpecificationRegion[] {\n const out: SpecificationRegion[] = [];\n\n for (const table of scanned.tables) {\n const schema = readSchema(table);\n if (schema === null) continue;\n\n for (const row of table.rows) {\n const idCell = row.cells[schema.id];\n if (!idCell) continue;\n const id = flatten(idCell.text);\n // A row is named by its id cell or not at all. An empty one, a\n // placeholder, or punctuation alone - `?` for a row nobody has numbered\n // yet - names nothing, and a region with no name fell back to the file's:\n // the register then contained itself, and the row vanished.\n if (!HAS_LETTER_OR_DIGIT.test(id) || EMPTY_CELL.test(id)) continue;\n\n // `row.cells[null]` is undefined too, so the null checks on the optional\n // columns are for the type; and `statusText` is read only beside a cell.\n const statusCell = schema.status === null ? undefined : row.cells[schema.status];\n const statusText = statusCell ? flatten(statusCell.text) : '';\n const status: RegionStatus | null =\n statusCell && statusText.length > 0 && !EMPTY_CELL.test(statusText)\n ? { text: statusText, start: statusCell.start, end: statusCell.end }\n : null;\n\n const titleCell = schema.title === null ? undefined : row.cells[schema.title];\n const title = titleCell ? flatten(titleCell.text) : null;\n\n const relations: TableRelation[] = [];\n // The status cell is left to prose, as a status line under a heading is:\n // `Superseded by ADR-0002` there names the row's successor, and claimed\n // it named nothing at all.\n const claimed: Range[] = [{ start: idCell.start, end: idCell.end }];\n\n for (const [column, relation] of schema.relations) {\n const cell = row.cells[column];\n if (!cell) continue;\n claimed.push({ start: cell.start, end: cell.end });\n for (const target of splitTargets(cell)) {\n relations.push({\n kind: relation.kind,\n inverted: relation.inverted,\n target: target.text,\n start: target.start,\n end: target.end,\n column: (table.headers[column] as TableCell).text,\n });\n }\n }\n\n out.push({\n heading: null,\n row,\n start: row.start,\n end: row.end,\n declaredId: id,\n title: title !== null && title.length > 0 ? title : null,\n status,\n directive: null,\n relations,\n claimed,\n });\n }\n }\n\n return out;\n}\n\n/**\n * Whether spec-graph reads a table as a register: an identifier column, and a\n * status column or a relation column beside it. Such a table's status column\n * describes its rows, never the document holding it.\n */\nexport function isRegisterTable(table: Table): boolean {\n return readSchema(table) !== null;\n}\n\ninterface TableSchema {\n readonly id: number;\n readonly status: number | null;\n readonly title: number | null;\n readonly relations: readonly (readonly [number, { kind: EdgeKind; inverted: boolean }])[];\n}\n\nfunction readSchema(table: Table): TableSchema | null {\n let id: number | null = null;\n let status: number | null = null;\n let title: number | null = null;\n const relations: [number, { kind: EdgeKind; inverted: boolean }][] = [];\n\n table.headers.forEach((header, column) => {\n const name = normaliseHeader(header.text);\n if (id === null && ID_COLUMNS.test(name)) {\n id = column;\n return;\n }\n if (status === null && STATUS_COLUMNS.test(name)) {\n status = column;\n return;\n }\n const relation = RELATION_COLUMNS[name];\n if (relation) {\n relations.push([column, relation]);\n return;\n }\n if (title === null && TITLE_COLUMNS.test(name)) title = column;\n });\n\n // Without an id column every row would be skipped for want of an id cell;\n // this says so before reading any of them.\n if (id === null) return null;\n if (status === null && relations.length === 0) return null;\n return { id, status, title, relations };\n}\n\nfunction normaliseHeader(text: string): string {\n return flatten(text)\n .toLowerCase()\n .replace(/[^a-z0-9/#\\s-]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** Splits a cell into the identifiers it lists, keeping each one's offset. */\nfunction splitTargets(cell: TableCell): { text: string; start: number; end: number }[] {\n const out: { text: string; start: number; end: number }[] = [];\n // Every target is reported at the cell's span. A cell is short, and an offset\n // recovered back through a flattening is an offset that can drift.\n const push = (raw: string): void => {\n const target = raw.trim();\n if (target.length === 0 || EMPTY_CELL.test(target)) return;\n out.push({ text: target, start: cell.start, end: cell.end });\n };\n const prose = (segment: string): void => {\n for (const piece of flatten(segment).split(/\\s*(?:[,;]|\\band\\b)\\s*/i)) push(piece);\n };\n\n // Links are taken out whole rather than flattened with everything else: a\n // path would otherwise be cut in half by its own slash, and its underscores\n // stripped as emphasis.\n // `matchAll` scans a copy of the pattern. `prose` calls `flatten`, which\n // drives the shared one, and one /g regex advanced from inside its own scan\n // resets its cursor and never terminates.\n let last = 0;\n for (const match of cell.text.matchAll(MARKDOWN_LINK)) {\n prose(cell.text.slice(last, match.index));\n // Both groups always take part in a match, so the `?? ''` below and in the\n // label are for the type.\n const destination = (match[2] ?? '').trim();\n // A link states where its target lives; its label states only what the\n // target is called (ADR-0008). In a column the author has already typed,\n // the destination is the claim - `[see](adr/0050.md)` is a dependency on\n // that file, not on the word \"see\". An external or in-page destination\n // names no document here, so the label is what is left to try.\n const local = destination.length > 0 && !destination.startsWith('#') && !isExternal(destination);\n push(local ? destination : (match[1] ?? ''));\n last = match.index + match[0].length;\n }\n prose(cell.text.slice(last));\n return out;\n}\n\n/** Reduces a cell to its text: link labels rather than link syntax. */\nfunction flatten(text: string): string {\n return text\n .replace(MARKDOWN_LINK, '$1')\n .replace(/\\[\\[(?:[^\\]|]*\\|)?([^\\]]*)\\]\\]/g, '$1')\n .replace(/[`*_~]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** Where a heading's section ends: the next heading at the same level or above. */\nfunction regionEnd(scanned: ScannedDocument, index: number): number {\n const heading = scanned.headings[index] as Heading;\n for (let i = index + 1; i < scanned.headings.length; i += 1) {\n const next = scanned.headings[i] as Heading;\n if (next.level <= heading.level) return next.start;\n }\n return scanned.text.length;\n}\n\n/**\n * Reads a status declared inside a region.\n *\n * Two forms, both common: a labelled line (`**Status:** Accepted`) and a\n * sub-heading (`### Status` with the value beneath it). Only the region is\n * searched, so one decision in a register cannot inherit its neighbour's.\n */\nexport function readRegionStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n const labelled = readLabelledStatus(scanned, start, end);\n if (labelled) return labelled;\n return readHeadedStatus(scanned, start, end);\n}\n\nfunction readLabelledStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n for (const line of scanned.lines) {\n if (line.contentStart < start) continue;\n if (line.contentStart >= end) break;\n // A template's commented-out `Status: proposed | accepted` is a hint to\n // whoever fills it in, not a status the region declares, and one in code or\n // a <pre> block is an example.\n if (!isMarkdownLine(line) || line.blank || line.comment) continue;\n const match = INLINE_STATUS.exec(line.content);\n if (!match) continue;\n // `**Status:** Accepted` closes its emphasis *after* the colon, so the\n // closing marker lands in the captured value. Strip it rather than let it\n // reach the report as part of the status a reader is shown.\n const value = (match[1] as string).replace(/^[*_\\s]+/, '').replace(/[*_\\s]+$/, '');\n if (value.length === 0) continue;\n const offset = line.contentStart + line.content.lastIndexOf(value);\n return { text: value, start: offset, end: offset + value.length };\n }\n return null;\n}\n\nfunction readHeadedStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n // The upper bound decides nothing on its own: under a heading at or past\n // `end` there is no line before `end`, and the loop below stops there.\n const heading = scanned.headings.find((h) => h.start >= start && h.start < end && isStatusHeading(h.text));\n if (!heading) return null;\n\n for (const line of scanned.lines) {\n if (line.line <= heading.line) continue;\n if (line.contentStart >= end) break;\n if (line.blank || isOnlyComment(scanned, line)) continue;\n if (!isMarkdownLine(line)) return null;\n if (opensHeading(scanned, line.line)) return null;\n const trimmed = line.content.trim();\n // A bullet list under Status is a status history; the first entry is current.\n const cleaned = trimmed.replace(/^[-*+]\\s+/, '');\n const offset = line.contentStart + line.content.indexOf(trimmed) + (trimmed.length - cleaned.length);\n return { text: cleaned, start: offset, end: offset + cleaned.length };\n }\n return null;\n}\n\n/**\n * Whether a heading begins on this line, which ends a status section's search\n * for its value.\n *\n * Asked of the scanner rather than of the line's first character, because a\n * setext heading's text line starts with no `#`: under `### Status` it read as\n * the status, and a register took the next decision's title for its own.\n */\nexport function opensHeading(scanned: ScannedDocument, line: number): boolean {\n return scanned.headings.some((heading) => heading.line === line);\n}\n\n/**\n * The last `@spec-node` directive inside a region: the one written last, which\n * is the one a reader sees as the final word.\n *\n * Directives arrive in the order they were written, so each candidate starts\n * after the one before it and the comparison below only restates that. And\n * `start` is where a heading's line ends, a comment written on it included,\n * so no directive begins exactly there and `<` against `<=` decides nothing.\n */\nfunction directiveIn(directives: readonly Directive[], start: number, end: number): Directive | null {\n let found: Directive | null = null;\n for (const directive of directives) {\n if (directive.name !== 'spec-node') continue;\n if (directive.start < start || directive.end > end) continue;\n if (found === null || directive.start > found.start) found = directive;\n }\n return found;\n}\n\n/**\n * Case- and separator-insensitive comparison, matching reference folding.\n *\n * Applied to both sides of one comparison, so which case it folds to does not\n * matter; the separators take every space with them, so the trim does not\n * either; and `+` only saves work.\n */\nfunction fold(value: string): string {\n return value.trim().toLowerCase().replace(/[\\s._-]+/g, '');\n}\n\n/** The region containing an offset, innermost first. `null` when only the file does. */\nexport function regionAt(regions: readonly SpecificationRegion[], offset: number): SpecificationRegion | null {\n let best: SpecificationRegion | null = null;\n for (const region of regions) {\n // The whole-file region owns nothing in particular; it is the fallback.\n if (region.heading === null && region.row === null) continue;\n if (offset < region.start || offset >= region.end) continue;\n // No two regions of one file open at the same offset, so `>` and `>=`\n // agree here.\n if (best === null || region.start > best.start) best = region;\n }\n return best;\n}\n"]}
1
+ {"version":3,"file":"sections.js","sourceRoot":"","sources":["../src/sections.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Specification regions: the part of a file that one specification occupies.\n *\n * `spec-graph` began by assuming one specification per file, because that is\n * how MADR, KEPs and Rust RFCs are laid out. Plenty of organisations do not\n * work that way. A register, a whitepaper or a living architecture document\n * holds dozens of decisions in one file, each with its own identifier, its own\n * status and its own dependencies - and to an engine that binds a document to a\n * file, every one of them is invisible.\n *\n * The fix is small once stated: **a specification is a region of a file, not a\n * file.** A file yields at least one region - itself - and may yield more.\n * Everything downstream is unchanged, because a region produces exactly the\n * same `DocumentNode` a whole file produced.\n *\n * What this module decides is where those regions begin and end, and it is\n * deliberately hard to convince. A heading has to carry an identifier *and* the\n * section has to declare a status before it counts. One signal is not enough:\n * `## Q3 2026 Roadmap` parses as family `Q`, number 3, and `## v1.2.0` in a\n * changelog parses as family `v`, number 1. Neither is a specification, and\n * neither declares a status.\n */\n\nimport { attr, type Directive } from './directives.js';\nimport { HAS_LETTER_OR_DIGIT, isExternal, parsePrefixedRef } from './identity.js';\nimport { isStatusHeading } from './lifecycle.js';\nimport {\n isMarkdownLine,\n isOnlyComment,\n type Heading,\n type Range,\n type ScannedDocument,\n type Table,\n type TableCell,\n type TableRow,\n} from './markdown.js';\nimport type { EdgeKind } from './types.js';\n\n/** A status as written, and where it was written. */\nexport interface RegionStatus {\n readonly text: string;\n readonly start: number;\n readonly end: number;\n}\n\n/** A relation read from a table column rather than from prose. */\nexport interface TableRelation {\n readonly kind: EdgeKind;\n readonly inverted: boolean;\n readonly target: string;\n readonly start: number;\n readonly end: number;\n /** The column header that gave it its meaning, for the diagnostic. */\n readonly column: string;\n}\n\nexport interface SpecificationRegion {\n /** The heading that opens the region, or `null` for the file or a table row. */\n readonly heading: Heading | null;\n /** The table row that is the region, when it is one. */\n readonly row: TableRow | null;\n /** Offset the region starts at. */\n readonly start: number;\n /** Offset just past the region. */\n readonly end: number;\n /** Identifier read from the heading or the identifier column. */\n readonly declaredId: string | null;\n readonly title: string | null;\n readonly status: RegionStatus | null;\n /** A `@spec-node` directive inside the region, when there is one. */\n readonly directive: Directive | null;\n /** Relations declared by column, empty for a heading region. */\n readonly relations: readonly TableRelation[];\n /**\n * Cells whose content is already accounted for as a typed relation.\n *\n * Prose scanning skips these, so a link in a `Depends on` column produces one\n * typed edge rather than a typed edge and a neutral citation beside it.\n */\n readonly claimed: readonly Range[];\n}\n\n/** Column headers that name the row's identifier. */\nconst ID_COLUMNS = /^(?:id|ids|ref|reference|key|no|number|#|adr|rfc|kep|oi|decision|spec|specification)$/;\n\n/** Column headers that name the row's status. */\nconst STATUS_COLUMNS = /^(?:status|state|stage|lifecycle|phase)$/;\n\n/** Column headers that name the row's title. */\nconst TITLE_COLUMNS = /^(?:title|name|summary|description|subject|decision|topic)$/;\n\n/**\n * Column headers that declare a relation.\n *\n * Written the way a header is written - \"Depends on\", \"Superseded by\" - rather\n * than the way a front-matter key is, because that is what an author types at\n * the top of a column.\n *\n * A map, because a header is whatever an author typed and an object answers to\n * more names than it was given: a column headed `Constructor` was a relation\n * of no kind, and every cell in it an edge to wherever it pointed.\n */\nconst RELATION_COLUMNS: ReadonlyMap<string, { kind: EdgeKind; inverted: boolean }> = new Map([\n ['depends on', { kind: 'depends-on', inverted: false }],\n ['depends-on', { kind: 'depends-on', inverted: false }],\n ['dependencies', { kind: 'depends-on', inverted: false }],\n ['requires', { kind: 'depends-on', inverted: false }],\n ['supersedes', { kind: 'supersedes', inverted: false }],\n ['replaces', { kind: 'supersedes', inverted: false }],\n ['superseded by', { kind: 'supersedes', inverted: true }],\n ['replaced by', { kind: 'supersedes', inverted: true }],\n ['blocked by', { kind: 'blocked-by', inverted: false }],\n ['blocked on', { kind: 'blocked-by', inverted: false }],\n ['blocks', { kind: 'blocked-by', inverted: true }],\n ['amends', { kind: 'amends', inverted: false }],\n ['extends', { kind: 'amends', inverted: false }],\n ['assumes', { kind: 'assumes', inverted: false }],\n ['delegates to', { kind: 'delegates-to', inverted: false }],\n ['delegated to', { kind: 'delegates-to', inverted: false }],\n ['tracked in', { kind: 'delegates-to', inverted: false }],\n ['related', { kind: 'relates-to', inverted: false }],\n ['related to', { kind: 'relates-to', inverted: false }],\n ['see also', { kind: 'relates-to', inverted: false }],\n ['references', { kind: 'references', inverted: false }],\n]);\n\n/** Cell values that mean \"nothing here\". */\nconst MARKDOWN_LINK = /!?\\[([^\\]]*)\\]\\(([^)]*)\\)/g;\nconst EMPTY_CELL = /^(?:-+|—|–|n\\/?a|none|nil|tbd|\\.|_+)$/i;\n\n/** `## ADR-0007: Sharding` - an identifier at the very start of a heading. */\nconst HEADING_ID = /^\\s*([A-Za-z]{1,15}[\\s._-]?\\d{1,6})\\b/;\n\n/** `**Status:** Accepted`, `Status: Accepted`, `*Status* : Accepted`. */\nconst INLINE_STATUS = /^[ \\t>]*[*_]{0,2}\\s*status\\s*[*_]{0,2}\\s*:\\s*(.+?)\\s*$/im;\n\n/**\n * Finds every specification in a file.\n *\n * The first entry is always the file itself, so a caller that ignores the rest\n * behaves exactly as it did before regions existed.\n */\nexport function findSpecificationRegions(\n scanned: ScannedDocument,\n directives: readonly Directive[],\n /** The file's own identifier, so its title heading is not read as a sub-region. */\n fileId: string,\n): SpecificationRegion[] {\n // The file's own relations come from its front matter and prose, never from\n // here, and it claims no cell: nothing reads either list off this region.\n const whole: SpecificationRegion = {\n heading: null,\n row: null,\n start: scanned.bodyStart,\n end: scanned.text.length,\n declaredId: null,\n title: null,\n status: null,\n directive: null,\n relations: [],\n claimed: [],\n };\n\n const regions: SpecificationRegion[] = [];\n const folded = fold(fileId);\n const fileRef = parsePrefixedRef(fileId);\n\n for (let i = 0; i < scanned.headings.length; i += 1) {\n const heading = scanned.headings[i] as Heading;\n const declaredId = HEADING_ID.exec(heading.text)?.[1];\n if (declaredId === undefined) continue;\n // The file's own title heading names the file, not a region inside it.\n if (fold(declaredId) === folded) continue;\n // Padding is not part of an identity anywhere else - `ADR-40`, `ADR-040`\n // and `ADR-0040` all resolve to one document - so a heading that spells the\n // number differently from the file name is still that file's own title.\n // Comparing the text alone made `# ADR-040` inside `0040-enforce.md` a\n // region within the file it names: one decision, two nodes, and a citation\n // arriving at whichever spelling it happened to use.\n // `HEADING_ID` captures only what `PREFIXED_ID` parses, so `ref` is never\n // null here; the check is for the type.\n const ref = parsePrefixedRef(declaredId);\n if (ref !== null && fileRef !== null && ref.family === fileRef.family && ref.number === fileRef.number) continue;\n\n const end = regionEnd(scanned, i);\n const directive = directiveIn(directives, heading.end, end);\n const status = readRegionStatus(scanned, heading.end, end);\n\n // Two independent signals are required. An identifier alone matches\n // `## Q3 2026 Roadmap` and `## v1.2.0`; a status alone matches every\n // ordinary `## Status` section in a normal ADR.\n if (status === null && directive === null) continue;\n\n // A directive's id wins, but only if it is a name. One that is not - the\n // lone backslash `id=\\\"ADR-9\\\"` parses to, written inside a string - would\n // be discarded when the region is identified, and a region with nothing\n // to go by falls back to the file's own name: the file then contained\n // itself, and the section vanished. The heading's id is the one to keep.\n const directiveId = directive ? attr(directive, 'id')?.value : undefined;\n\n regions.push({\n heading,\n row: null,\n start: heading.start,\n end,\n declaredId: directiveId !== undefined && HAS_LETTER_OR_DIGIT.test(directiveId) ? directiveId : declaredId,\n title: heading.text,\n status,\n directive,\n relations: [],\n // Prose inside a section is prose: a heading region claims no cell.\n claimed: [],\n });\n }\n\n return [whole, ...regions, ...findTableRegions(scanned)];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Registers kept as tables */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Reads a table whose columns describe specifications.\n *\n * A table qualifies when it has an identifier column *and* either a status\n * column or at least one relation column. Two signals again, for the same\n * reason: a table of identifiers and prose is a citation list, and promoting\n * every row of it to a specification would invent a register nobody wrote.\n */\nexport function findTableRegions(scanned: ScannedDocument): SpecificationRegion[] {\n const out: SpecificationRegion[] = [];\n\n for (const table of scanned.tables) {\n const schema = readSchema(table);\n if (schema === null) continue;\n\n for (const row of table.rows) {\n const idCell = row.cells[schema.id];\n if (!idCell) continue;\n const id = flatten(idCell.text);\n // A row is named by its id cell or not at all. An empty one, a\n // placeholder, or punctuation alone - `?` for a row nobody has numbered\n // yet - names nothing, and a region with no name fell back to the file's:\n // the register then contained itself, and the row vanished.\n if (!HAS_LETTER_OR_DIGIT.test(id) || EMPTY_CELL.test(id)) continue;\n\n // `row.cells[null]` is undefined too, so the null checks on the optional\n // columns are for the type; and `statusText` is read only beside a cell.\n const statusCell = schema.status === null ? undefined : row.cells[schema.status];\n const statusText = statusCell ? flatten(statusCell.text) : '';\n const status: RegionStatus | null =\n statusCell && statusText.length > 0 && !EMPTY_CELL.test(statusText)\n ? { text: statusText, start: statusCell.start, end: statusCell.end }\n : null;\n\n const titleCell = schema.title === null ? undefined : row.cells[schema.title];\n const title = titleCell ? flatten(titleCell.text) : null;\n\n const relations: TableRelation[] = [];\n // The status cell is left to prose, as a status line under a heading is:\n // `Superseded by ADR-0002` there names the row's successor, and claimed\n // it named nothing at all.\n const claimed: Range[] = [{ start: idCell.start, end: idCell.end }];\n\n for (const [column, relation] of schema.relations) {\n const cell = row.cells[column];\n if (!cell) continue;\n claimed.push({ start: cell.start, end: cell.end });\n for (const target of splitTargets(cell)) {\n relations.push({\n kind: relation.kind,\n inverted: relation.inverted,\n target: target.text,\n start: target.start,\n end: target.end,\n column: (table.headers[column] as TableCell).text,\n });\n }\n }\n\n out.push({\n heading: null,\n row,\n start: row.start,\n end: row.end,\n declaredId: id,\n title: title !== null && title.length > 0 ? title : null,\n status,\n directive: null,\n relations,\n claimed,\n });\n }\n }\n\n return out;\n}\n\n/**\n * Whether spec-graph reads a table as a register: an identifier column, and a\n * status column or a relation column beside it. Such a table's status column\n * describes its rows, never the document holding it.\n */\nexport function isRegisterTable(table: Table): boolean {\n return readSchema(table) !== null;\n}\n\ninterface TableSchema {\n readonly id: number;\n readonly status: number | null;\n readonly title: number | null;\n readonly relations: readonly (readonly [number, { kind: EdgeKind; inverted: boolean }])[];\n}\n\nfunction readSchema(table: Table): TableSchema | null {\n let id: number | null = null;\n let status: number | null = null;\n let title: number | null = null;\n const relations: [number, { kind: EdgeKind; inverted: boolean }][] = [];\n\n table.headers.forEach((header, column) => {\n const name = normaliseHeader(header.text);\n if (id === null && ID_COLUMNS.test(name)) {\n id = column;\n return;\n }\n if (status === null && STATUS_COLUMNS.test(name)) {\n status = column;\n return;\n }\n const relation = RELATION_COLUMNS.get(name);\n if (relation) {\n relations.push([column, relation]);\n return;\n }\n if (title === null && TITLE_COLUMNS.test(name)) title = column;\n });\n\n // Without an id column every row would be skipped for want of an id cell;\n // this says so before reading any of them.\n if (id === null) return null;\n if (status === null && relations.length === 0) return null;\n return { id, status, title, relations };\n}\n\nfunction normaliseHeader(text: string): string {\n return flatten(text)\n .toLowerCase()\n .replace(/[^a-z0-9/#\\s-]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** Splits a cell into the identifiers it lists, keeping each one's offset. */\nfunction splitTargets(cell: TableCell): { text: string; start: number; end: number }[] {\n const out: { text: string; start: number; end: number }[] = [];\n // Every target is reported at the cell's span. A cell is short, and an offset\n // recovered back through a flattening is an offset that can drift.\n const push = (raw: string): void => {\n const target = raw.trim();\n if (target.length === 0 || EMPTY_CELL.test(target)) return;\n out.push({ text: target, start: cell.start, end: cell.end });\n };\n const prose = (segment: string): void => {\n for (const piece of flatten(segment).split(/\\s*(?:[,;]|\\band\\b)\\s*/i)) push(piece);\n };\n\n // Links are taken out whole rather than flattened with everything else: a\n // path would otherwise be cut in half by its own slash, and its underscores\n // stripped as emphasis.\n // `matchAll` scans a copy of the pattern. `prose` calls `flatten`, which\n // drives the shared one, and one /g regex advanced from inside its own scan\n // resets its cursor and never terminates.\n let last = 0;\n for (const match of cell.text.matchAll(MARKDOWN_LINK)) {\n prose(cell.text.slice(last, match.index));\n // Both groups always take part in a match, so the `?? ''` below and in the\n // label are for the type.\n const destination = (match[2] ?? '').trim();\n // A link states where its target lives; its label states only what the\n // target is called (ADR-0008). In a column the author has already typed,\n // the destination is the claim - `[see](adr/0050.md)` is a dependency on\n // that file, not on the word \"see\". An external or in-page destination\n // names no document here, so the label is what is left to try.\n const local = destination.length > 0 && !destination.startsWith('#') && !isExternal(destination);\n push(local ? destination : (match[1] ?? ''));\n last = match.index + match[0].length;\n }\n prose(cell.text.slice(last));\n return out;\n}\n\n/** Reduces a cell to its text: link labels rather than link syntax. */\nfunction flatten(text: string): string {\n return text\n .replace(MARKDOWN_LINK, '$1')\n .replace(/\\[\\[(?:[^\\]|]*\\|)?([^\\]]*)\\]\\]/g, '$1')\n .replace(/[`*_~]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** Where a heading's section ends: the next heading at the same level or above. */\nfunction regionEnd(scanned: ScannedDocument, index: number): number {\n const heading = scanned.headings[index] as Heading;\n for (let i = index + 1; i < scanned.headings.length; i += 1) {\n const next = scanned.headings[i] as Heading;\n if (next.level <= heading.level) return next.start;\n }\n return scanned.text.length;\n}\n\n/**\n * Reads a status declared inside a region.\n *\n * Two forms, both common: a labelled line (`**Status:** Accepted`) and a\n * sub-heading (`### Status` with the value beneath it). Only the region is\n * searched, so one decision in a register cannot inherit its neighbour's.\n */\nexport function readRegionStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n const labelled = readLabelledStatus(scanned, start, end);\n if (labelled) return labelled;\n return readHeadedStatus(scanned, start, end);\n}\n\nfunction readLabelledStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n for (const line of scanned.lines) {\n if (line.contentStart < start) continue;\n if (line.contentStart >= end) break;\n // A template's commented-out `Status: proposed | accepted` is a hint to\n // whoever fills it in, not a status the region declares, and one in code or\n // a <pre> block is an example.\n if (!isMarkdownLine(line) || line.blank || line.comment) continue;\n const match = INLINE_STATUS.exec(line.content);\n if (!match) continue;\n // `**Status:** Accepted` closes its emphasis *after* the colon, so the\n // closing marker lands in the captured value. Strip it rather than let it\n // reach the report as part of the status a reader is shown.\n const value = (match[1] as string).replace(/^[*_\\s]+/, '').replace(/[*_\\s]+$/, '');\n if (value.length === 0) continue;\n const offset = line.contentStart + line.content.lastIndexOf(value);\n return { text: value, start: offset, end: offset + value.length };\n }\n return null;\n}\n\nfunction readHeadedStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n // The upper bound decides nothing on its own: under a heading at or past\n // `end` there is no line before `end`, and the loop below stops there.\n const heading = scanned.headings.find((h) => h.start >= start && h.start < end && isStatusHeading(h.text));\n if (!heading) return null;\n\n for (const line of scanned.lines) {\n if (line.line <= heading.line) continue;\n if (line.contentStart >= end) break;\n if (line.blank || isOnlyComment(scanned, line)) continue;\n if (!isMarkdownLine(line)) return null;\n if (opensHeading(scanned, line.line)) return null;\n const trimmed = line.content.trim();\n // A bullet list under Status is a status history; the first entry is current.\n const cleaned = trimmed.replace(/^[-*+]\\s+/, '');\n const offset = line.contentStart + line.content.indexOf(trimmed) + (trimmed.length - cleaned.length);\n return { text: cleaned, start: offset, end: offset + cleaned.length };\n }\n return null;\n}\n\n/**\n * Whether a heading begins on this line, which ends a status section's search\n * for its value.\n *\n * Asked of the scanner rather than of the line's first character, because a\n * setext heading's text line starts with no `#`: under `### Status` it read as\n * the status, and a register took the next decision's title for its own.\n */\nexport function opensHeading(scanned: ScannedDocument, line: number): boolean {\n return scanned.headings.some((heading) => heading.line === line);\n}\n\n/**\n * The last `@spec-node` directive inside a region: the one written last, which\n * is the one a reader sees as the final word.\n *\n * Directives arrive in the order they were written, so each candidate starts\n * after the one before it and the comparison below only restates that. And\n * `start` is where a heading's line ends, a comment written on it included,\n * so no directive begins exactly there and `<` against `<=` decides nothing.\n */\nfunction directiveIn(directives: readonly Directive[], start: number, end: number): Directive | null {\n let found: Directive | null = null;\n for (const directive of directives) {\n if (directive.name !== 'spec-node') continue;\n if (directive.start < start || directive.end > end) continue;\n if (found === null || directive.start > found.start) found = directive;\n }\n return found;\n}\n\n/**\n * Case- and separator-insensitive comparison, matching reference folding.\n *\n * Applied to both sides of one comparison, so which case it folds to does not\n * matter; the separators take every space with them, so the trim does not\n * either; and `+` only saves work.\n */\nfunction fold(value: string): string {\n return value.trim().toLowerCase().replace(/[\\s._-]+/g, '');\n}\n\n/** The region containing an offset, innermost first. `null` when only the file does. */\nexport function regionAt(regions: readonly SpecificationRegion[], offset: number): SpecificationRegion | null {\n let best: SpecificationRegion | null = null;\n for (const region of regions) {\n // The whole-file region owns nothing in particular; it is the fallback.\n if (region.heading === null && region.row === null) continue;\n if (offset < region.start || offset >= region.end) continue;\n // No two regions of one file open at the same offset, so `>` and `>=`\n // agree here.\n if (best === null || region.start > best.start) best = region;\n }\n return best;\n}\n"]}
package/dist/select.js CHANGED
@@ -36,18 +36,26 @@ export const DEFAULT_MATCH_LIMIT = 10_000;
36
36
  /* -------------------------------------------------------------------------- */
37
37
  /* Parsing */
38
38
  /* -------------------------------------------------------------------------- */
39
- const NODE_KINDS = {
40
- document: 'document',
41
- documents: 'document',
42
- doc: 'document',
43
- docs: 'document',
44
- item: 'item',
45
- items: 'item',
46
- node: null,
47
- nodes: null,
48
- any: null,
49
- '*': null,
50
- };
39
+ /**
40
+ * The words that name a node type, and `null` for the ones that name any.
41
+ *
42
+ * A map, because a selector is typed by a person and an object answers to
43
+ * more names than it was given: `constructor` was a node type no node has, so
44
+ * `constructor[status=draft]` matched nothing where `construct[status=draft]`
45
+ * was refused.
46
+ */
47
+ const NODE_KINDS = new Map([
48
+ ['document', 'document'],
49
+ ['documents', 'document'],
50
+ ['doc', 'document'],
51
+ ['docs', 'document'],
52
+ ['item', 'item'],
53
+ ['items', 'item'],
54
+ ['node', null],
55
+ ['nodes', null],
56
+ ['any', null],
57
+ ['*', null],
58
+ ]);
51
59
  /** Compiles a selector string into a {@link QuerySpec}. */
52
60
  export function parseQuery(source) {
53
61
  const parser = new Parser(source);
@@ -82,10 +90,10 @@ class Parser {
82
90
  const word = /^[A-Za-z]+|^\*/.exec(this.source.slice(this.position));
83
91
  if (word) {
84
92
  const text = word[0].toLowerCase();
85
- if (!(text in NODE_KINDS)) {
93
+ if (!NODE_KINDS.has(text)) {
86
94
  throw new QueryError(`unknown node type "${word[0]}", expected one of: document, item, * (any)`, this.position);
87
95
  }
88
- kind = NODE_KINDS[text];
96
+ kind = NODE_KINDS.get(text);
89
97
  this.position += word[0].length;
90
98
  }
91
99
  else if (this.source[this.position] !== '[') {
@@ -286,9 +294,12 @@ export function attributesOf(node, key, graph) {
286
294
  return [...node.aliases];
287
295
  }
288
296
  if (key.startsWith('fm.')) {
289
- const value = node.frontMatter[key.slice(3)];
290
- if (value === undefined)
297
+ // Its own keys only: every object answers to `constructor`, and no
298
+ // document's front matter said so.
299
+ const name = key.slice(3);
300
+ if (!Object.hasOwn(node.frontMatter, name))
291
301
  return [];
302
+ const value = node.frontMatter[name];
292
303
  return typeof value === 'string' ? [value] : [...value];
293
304
  }
294
305
  return [];