@descent-vtt/spec-graph 0.2.3 → 0.4.0

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 * Register the file path and stem as aliases. 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.\n */\n readonly includePathAliases?: boolean | undefined;\n}\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. Whatever loses still contributes aliases, so\n * a reference written in any of those forms still resolves.\n */\nexport function identify(input: IdentityInput): DocumentIdentity {\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 const candidates: { family: string | null; number: number | null; label: string }[] = [];\n\n const consider = (raw: string | null, allowBare: boolean): void => {\n if (!raw) return;\n const cleaned = raw.trim();\n if (cleaned.length === 0) return;\n const prefixed = PREFIXED_ID.exec(cleaned);\n if (prefixed) {\n candidates.push({\n family: (prefixed[1] as string).toUpperCase(),\n number: Number.parseInt(prefixed[2] as string, 10),\n label: cleaned,\n });\n return;\n }\n const bare = BARE_NUMBER.exec(cleaned);\n if (bare && allowBare) {\n candidates.push({\n family: directoryFamily,\n number: Number.parseInt(bare[1] as string, 10),\n label: cleaned,\n });\n return;\n }\n candidates.push({ family: null, number: null, label: cleaned });\n };\n\n consider(input.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.\n consider(leadingToken(stem), true);\n consider(headingId(input.heading), false);\n\n const numbered = candidates.find((c) => c.number !== null);\n const family = numbered?.family ?? directoryFamily;\n const number = numbered?.number ?? null;\n\n // Witnesses to the local zero-padding convention, most authoritative first.\n // The spelling that actually produced the number comes first: an id read from\n // the H1 of `sharding.md` must still render as ADR-0007, not ADR-7.\n const witnesses = [numbered?.label, input.declaredId, stem];\n\n let id: string;\n if (number !== null && family !== null) {\n id = `${family}-${padNumber(number, witnesses)}`;\n } else if (input.declaredId) {\n id = input.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 id = stem.length > 0 ? stem : String(number);\n } else {\n id = stem.length > 0 ? stem : input.path;\n }\n\n add(id);\n if (input.includePathAliases !== false) {\n add(stem);\n add(input.path);\n add(stripExtension(input.path));\n }\n add(input.declaredId);\n for (const alias of input.declaredAliases) add(alias);\n if (input.heading) add(headingId(input.heading));\n\n if (number !== null) {\n const spellings = numberSpellings(number, witnesses);\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 return value\n .trim()\n .toLowerCase()\n .replace(/^[#<([]+|[)\\]>.,;:]+$/g, '')\n .replace(/[\\s._-]+/g, '')\n .trim();\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 `#` is an in-document anchor, not a separator.\n if (hash === 0) return { target: '', anchor: target.slice(1) };\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 */\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 if (dot <= 0) return true;\n const extension = base.slice(dot);\n // Only a plausible file extension disqualifies a target. An identifier like\n // `v1.2` or `ADR-0007.1` keeps its dot and is still resolved as an id.\n if (!/^\\.[A-Za-z][A-Za-z0-9]{0,5}$/.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.\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/** `kep-1234-foo` -> `kep-1234`; `0007-sharding` -> `0007`; `sharding` -> `sharding`. */\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 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 * Chooses the digit width of the canonical id.\n *\n * A repository that writes `0007` everywhere should see `ADR-0007` in reports,\n * not `ADR-7`. The file name is the most reliable witness of the local\n * convention, so it decides.\n */\nfunction padNumber(number: number, witnesses: readonly (string | null | undefined)[]): string {\n const width = paddingWidth(number, witnesses) ?? String(number).length;\n return String(number).padStart(width, '0');\n}\n\n/** The digit width of the first witness that actually spells this number. */\nfunction paddingWidth(number: number, witnesses: readonly (string | null | undefined)[]): number | null {\n for (const witness of witnesses) {\n if (!witness) continue;\n const digits = /(\\d{1,6})/.exec(witness)?.[1];\n if (digits && Number.parseInt(digits, 10) === number) return digits.length;\n }\n return null;\n}\n\n/** Zero-padded spellings a citation might reasonably use. */\nfunction numberSpellings(number: number, witnesses: readonly (string | null | undefined)[]): string[] {\n const out = new Set<string>([String(number)]);\n const observed = paddingWidth(number, witnesses);\n if (observed !== null) out.add(String(number).padStart(observed, '0'));\n for (const width of [3, 4]) out.add(String(number).padStart(width, '0'));\n return [...out];\n}\n"]}
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,sFAAsF;AACtF,MAAM,kBAAkB,GAAqC;IAC3D,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,KAAK;IACX,wBAAwB,EAAE,KAAK;IAC/B,kBAAkB,EAAE,KAAK;IACzB,SAAS,EAAE,KAAK;IAChB,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,KAAK;IACX,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,KAAK;IACX,YAAY,EAAE,KAAK;IACnB,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,UAAU;IACrB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,QAAQ;IACjB,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,KAAK;IACX,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,MAAM;CACd,CAAC;AAEF,oFAAoF;AACpF,MAAM,CAAC,MAAM,OAAO,GAAsB,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAEnH,+EAA+E;AAC/E,MAAM,WAAW,GAAG,kDAAkD,CAAC;AACvE,6DAA6D;AAC7D,MAAM,WAAW,GAAG,eAAe,CAAC;AAgCpC,mFAAmF;AACnF,MAAM,mBAAmB,GAAG,eAAe,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAoB;IAC3C,+EAA+E;IAC/E,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,+EAA+E;IAC/E,iEAAiE;IACjE,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,KAAK,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;IACrH,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAElC,MAAM,GAAG,GAAG,CAAC,KAAgC,EAAQ,EAAE;QACrD,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC,CAAC;IAEF,MAAM,UAAU,GAAsE,EAAE,CAAC;IAEzF,MAAM,QAAQ,GAAG,CAAC,GAAkB,EAAE,SAAkB,EAAQ,EAAE;QAChE,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACjC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3C,IAAI,QAAQ,EAAE,CAAC;YACb,UAAU,CAAC,IAAI,CAAC;gBACd,MAAM,EAAG,QAAQ,CAAC,CAAC,CAAY,CAAC,WAAW,EAAE;gBAC7C,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC;gBAClD,KAAK,EAAE,OAAO;aACf,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,IAAI,IAAI,SAAS,EAAE,CAAC;YACtB,UAAU,CAAC,IAAI,CAAC;gBACd,MAAM,EAAE,eAAe;gBACvB,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC;gBAC9C,KAAK,EAAE,OAAO;aACf,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC;IAEF,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAC3B,8EAA8E;IAC9E,iEAAiE;IACjE,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;IAE1C,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,QAAQ,EAAE,MAAM,IAAI,eAAe,CAAC;IACnD,MAAM,MAAM,GAAG,QAAQ,EAAE,MAAM,IAAI,IAAI,CAAC;IAExC,4EAA4E;IAC5E,8EAA8E;IAC9E,oEAAoE;IACpE,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;IAEtD,IAAI,EAAU,CAAC;IACf,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACvC,EAAE,GAAG,GAAG,MAAM,IAAI,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,CAAC;IACnD,CAAC;SAAM,IAAI,UAAU,EAAE,CAAC;QACtB,EAAE,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;SAAM,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAC3B,4EAA4E;QAC5E,0EAA0E;QAC1E,mEAAmE;QACnE,EAAE,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,EAAE,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;IAC3C,CAAC;IAED,GAAG,CAAC,EAAE,CAAC,CAAC;IACR,IAAI,KAAK,CAAC,kBAAkB,KAAK,KAAK,EAAE,CAAC;QACvC,GAAG,CAAC,IAAI,CAAC,CAAC;QACV,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChB,GAAG,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,GAAG,CAAC,UAAU,CAAC,CAAC;IAChB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,eAAe;QAAE,GAAG,CAAC,KAAK,CAAC,CAAC;IACtD,IAAI,KAAK,CAAC,OAAO;QAAE,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAEjD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,SAAS,GAAG,eAAe,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACrD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,yEAAyE;YACzE,2EAA2E;YAC3E,mCAAmC;YACnC,KAAK,MAAM,MAAM,IAAI,SAAS;gBAAE,GAAG,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC;QAC7D,CAAC;aAAM,CAAC;YACN,wEAAwE;YACxE,kDAAkD;YAClD,KAAK,MAAM,MAAM,IAAI,SAAS;gBAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC;AACvD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,KAAK;SACT,IAAI,EAAE;SACN,WAAW,EAAE;SACb,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC;SACrC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;SACxB,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACjD,2DAA2D;IAC3D,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC;AAC3E,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;IACpE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,EAAG,KAAK,CAAC,CAAC,CAAY,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC,EAAE,CAAC;AACzG,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7C,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChE,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,kBAAkB,GAAG,6CAA6C,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,aAAa,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;IACjD,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACrE,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,2EAA2E;IAC3E,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,4EAA4E;IAC5E,uEAAuE;IACvE,IAAI,CAAC,8BAA8B,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IACjE,OAAO,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC9E,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpD,uEAAuE;IACvE,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAW,CAAC,CAAC;QACxD,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF,SAAS,QAAQ,CAAC,SAAiB;IACjC,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjD,4EAA4E;IAC5E,6EAA6E;IAC7E,qBAAqB;IACrB,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,KAAK,GAAG,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACzC,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,SAAiB;IACvC,MAAM,GAAG,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3D,CAAC;AAED,yFAAyF;AACzF,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,QAAQ,GAAG,+CAA+C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5E,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC,CAAC,CAAW,CAAC;IAC3C,MAAM,IAAI,GAAG,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC,CAAC,CAAW,CAAC;IACnC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2DAA2D;AAC3D,SAAS,SAAS,CAAC,OAAsB;IACvC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,KAAK,GAAG,uCAAuC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpE,OAAO,KAAK,CAAC,CAAC,CAAE,KAAK,CAAC,CAAC,CAAY,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,SAAS,SAAS,CAAC,MAAc,EAAE,SAAiD;IAClF,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;IACvE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,6EAA6E;AAC7E,SAAS,YAAY,CAAC,MAAc,EAAE,SAAiD;IACrF,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE,CAAC;QAChC,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9C,IAAI,MAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,MAAM;YAAE,OAAO,MAAM,CAAC,MAAM,CAAC;IAC7E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,6DAA6D;AAC7D,SAAS,eAAe,CAAC,MAAc,EAAE,SAAiD;IACxF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACjD,IAAI,QAAQ,KAAK,IAAI;QAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IACvE,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS,EAAE,CAAS;IAChD,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;QACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAAE,SAAS;YAC5B,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;gBACjB,KAAK,GAAG,CAAC,CAAC;gBACV,SAAS;YACX,CAAC;YACD,yEAAyE;YACzE,6BAA6B;YAC7B,OAAO,KAAK,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACxG,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5D,IAAI,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;YAAE,SAAS;QACnC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC","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 * Register the file path and stem as aliases. 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.\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. */\nconst 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. Whatever loses still contributes aliases, so\n * a reference written in any of those forms still resolves.\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.\n const declaredId = input.declaredId !== null && HAS_LETTER_OR_DIGIT.test(input.declaredId) ? input.declaredId : null;\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 const candidates: { family: string | null; number: number | null; label: string }[] = [];\n\n const consider = (raw: string | null, allowBare: boolean): void => {\n if (!raw) return;\n const cleaned = raw.trim();\n if (cleaned.length === 0) return;\n const prefixed = PREFIXED_ID.exec(cleaned);\n if (prefixed) {\n candidates.push({\n family: (prefixed[1] as string).toUpperCase(),\n number: Number.parseInt(prefixed[2] as string, 10),\n label: cleaned,\n });\n return;\n }\n const bare = BARE_NUMBER.exec(cleaned);\n if (bare && allowBare) {\n candidates.push({\n family: directoryFamily,\n number: Number.parseInt(bare[1] as string, 10),\n label: cleaned,\n });\n return;\n }\n candidates.push({ family: null, number: null, label: cleaned });\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.\n consider(leadingToken(stem), true);\n consider(headingId(input.heading), false);\n\n const numbered = candidates.find((c) => c.number !== null);\n const family = numbered?.family ?? directoryFamily;\n const number = numbered?.number ?? null;\n\n // Witnesses to the local zero-padding convention, most authoritative first.\n // The spelling that actually produced the number comes first: an id read from\n // the H1 of `sharding.md` must still render as ADR-0007, not ADR-7.\n const witnesses = [numbered?.label, declaredId, stem];\n\n let id: string;\n if (number !== null && family !== null) {\n id = `${family}-${padNumber(number, witnesses)}`;\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 id = stem.length > 0 ? stem : String(number);\n } else {\n id = stem.length > 0 ? stem : input.path;\n }\n\n add(id);\n if (input.includePathAliases !== false) {\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 if (input.heading) add(headingId(input.heading));\n\n if (number !== null) {\n const spellings = numberSpellings(number, witnesses);\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 return value\n .trim()\n .toLowerCase()\n .replace(/^[#<([]+|[)\\]>.,;:]+$/g, '')\n .replace(/[\\s._-]+/g, '')\n .trim();\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 `#` is an in-document anchor, not a separator.\n if (hash === 0) return { target: '', anchor: target.slice(1) };\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 */\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 if (dot <= 0) return true;\n const extension = base.slice(dot);\n // Only a plausible file extension disqualifies a target. An identifier like\n // `v1.2` or `ADR-0007.1` keeps its dot and is still resolved as an id.\n if (!/^\\.[A-Za-z][A-Za-z0-9]{0,5}$/.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.\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/** `kep-1234-foo` -> `kep-1234`; `0007-sharding` -> `0007`; `sharding` -> `sharding`. */\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 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 * Chooses the digit width of the canonical id.\n *\n * A repository that writes `0007` everywhere should see `ADR-0007` in reports,\n * not `ADR-7`. The file name is the most reliable witness of the local\n * convention, so it decides.\n */\nfunction padNumber(number: number, witnesses: readonly (string | null | undefined)[]): string {\n const width = paddingWidth(number, witnesses) ?? String(number).length;\n return String(number).padStart(width, '0');\n}\n\n/** The digit width of the first witness that actually spells this number. */\nfunction paddingWidth(number: number, witnesses: readonly (string | null | undefined)[]): number | null {\n for (const witness of witnesses) {\n if (!witness) continue;\n const digits = /(\\d{1,6})/.exec(witness)?.[1];\n if (digits && Number.parseInt(digits, 10) === number) return digits.length;\n }\n return null;\n}\n\n/** Zero-padded spellings a citation might reasonably use. */\nfunction numberSpellings(number: number, witnesses: readonly (string | null | undefined)[]): string[] {\n const out = new Set<string>([String(number)]);\n const observed = paddingWidth(number, witnesses);\n if (observed !== null) out.add(String(number).padStart(observed, '0'));\n for (const width of [3, 4]) out.add(String(number).padStart(width, '0'));\n return [...out];\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 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/index.d.ts CHANGED
@@ -22,7 +22,7 @@
22
22
  * ```
23
23
  */
24
24
  export { applyBaseline, formatBaseline, fingerprintOf, parseBaseline, BASELINE_VERSION, EMPTY_BASELINE, } from './baseline.js';
25
- export type { Baseline, BaselineEntry, BaselineOutcome, ParsedBaseline } from './baseline.js';
25
+ export type { Baseline, BaselineEntry, BaselineOutcome, ParsedBaseline, StaleEntry } from './baseline.js';
26
26
  export { loadConfig, parseConfig, CONFIG_FILES, CONFIG_PACKAGE_KEY } from './config.js';
27
27
  export type { LoadedConfig, SpecGraphConfig } from './config.js';
28
28
  export { main, parseArgs, HELP, UsageError, EXIT_OK, EXIT_FAILED, EXIT_ERROR } from './cli.js';
@@ -31,11 +31,13 @@ export { analyse, analyseSources, createFamilyFilter, createHistoryMatcher, with
31
31
  export type { AnalyseOptions, AnalyseSourcesOptions, AnalysisResult, Source } from './runner.js';
32
32
  export { buildGraph, LOAD_BEARING_EDGES, OBLIGATION_EDGES } from './graph.js';
33
33
  export type { SpecGraph } from './graph.js';
34
- export { attributesOf, execute, matches, parseQuery, query, renderMatch, QueryError, DEFAULT_MATCH_LIMIT, } from './select.js';
34
+ export { attributesOf, execute, matches, parseQuery, query, renderMatch, QueryError, DEFAULT_MATCH_LIMIT, SELECTOR_KEYS, } from './select.js';
35
35
  export type { ExecuteOptions, Match, NodeMatcher, Operator, Predicate, QuerySpec, StepSpec, } from './select.js';
36
36
  export { resolveStrict, runRules, sortDiagnostics, DEFAULT_SEVERITIES, RULE_DESCRIPTIONS, RULE_IDS, RULE_QUERIES, } from './rules.js';
37
37
  export type { RuleOptions } from './rules.js';
38
- export { createPainter, formatGraph, formatJson, formatReport, shouldUseAscii, shouldUseColor, } from './report.js';
38
+ export { compileProjectRules, renderTemplate } from './project-rules.js';
39
+ export type { CompiledProjectRules, ProjectRule } from './project-rules.js';
40
+ export { createPainter, formatGraph, formatJson, formatReport, formatSarif, shouldUseAscii, shouldUseColor, } from './report.js';
39
41
  export type { ColorEnvironment, GraphExportOptions, GraphFormat, Painter, ReporterOptions } from './report.js';
40
42
  export { extractDocument, extractSpecifications, classifyReference, sectionPathAt } from './extract.js';
41
43
  export { findSpecificationRegions, readRegionStatus, regionAt } from './sections.js';
@@ -59,5 +61,5 @@ export type { GlobMatcher, WalkedFile, WalkOptions } from './glob.js';
59
61
  export { compareRefs, createLineIndex, formatRef, refOf, spanOf } from './source.js';
60
62
  export type { LineIndex } from './source.js';
61
63
  export { basenamePosix, dirnamePosix, extnamePosix, joinPosix, normalisePosix, resolveFrom, toPosix, } from './paths.js';
62
- export { DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF } from './types.js';
63
- export type { AnalysisSummary, DanglingReason, DanglingRef, Diagnostic, Disposition, DocumentNode, Edge, EdgeKind, EdgeOrigin, EdgeTraits, ItemNode, NodeKind, Openness, ParseProblem, Phase, Position, Receptivity, RelatedLocation, RuleId, Severity, SourceRef, Span, SpecNode, StateSignal, } from './types.js';
64
+ export { isProjectRule, DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF, PROJECT_RULE_PREFIX } from './types.js';
65
+ export type { AnalysisSummary, AnyRuleId, DanglingReason, DanglingRef, Diagnostic, Disposition, DocumentNode, Edge, EdgeKind, EdgeOrigin, EdgeTraits, ItemNode, MisreadKey, NodeKind, Openness, ParseProblem, Phase, Position, Receptivity, ProjectRuleId, RelatedLocation, RuleId, Severity, SourceRef, Span, SpecNode, StateSignal, SuppressedRef, } from './types.js';
package/dist/index.js CHANGED
@@ -26,9 +26,10 @@ export { loadConfig, parseConfig, CONFIG_FILES, CONFIG_PACKAGE_KEY } from './con
26
26
  export { main, parseArgs, HELP, UsageError, EXIT_OK, EXIT_FAILED, EXIT_ERROR } from './cli.js';
27
27
  export { analyse, analyseSources, createFamilyFilter, createHistoryMatcher, withDiagnostics, DEFAULT_CONCURRENCY, DEFAULT_PATTERNS, } from './runner.js';
28
28
  export { buildGraph, LOAD_BEARING_EDGES, OBLIGATION_EDGES } from './graph.js';
29
- export { attributesOf, execute, matches, parseQuery, query, renderMatch, QueryError, DEFAULT_MATCH_LIMIT, } from './select.js';
29
+ export { attributesOf, execute, matches, parseQuery, query, renderMatch, QueryError, DEFAULT_MATCH_LIMIT, SELECTOR_KEYS, } from './select.js';
30
30
  export { resolveStrict, runRules, sortDiagnostics, DEFAULT_SEVERITIES, RULE_DESCRIPTIONS, RULE_IDS, RULE_QUERIES, } from './rules.js';
31
- export { createPainter, formatGraph, formatJson, formatReport, shouldUseAscii, shouldUseColor, } from './report.js';
31
+ export { compileProjectRules, renderTemplate } from './project-rules.js';
32
+ export { createPainter, formatGraph, formatJson, formatReport, formatSarif, shouldUseAscii, shouldUseColor, } from './report.js';
32
33
  export { extractDocument, extractSpecifications, classifyReference, sectionPathAt } from './extract.js';
33
34
  export { findSpecificationRegions, readRegionStatus, regionAt } from './sections.js';
34
35
  export { resolveCorpus, documentOf } from './resolve.js';
@@ -41,5 +42,5 @@ export { familyFromPath, identify, isDocumentTarget, isExternal, looksLikePath,
41
42
  export { createGlobMatcher, createReferenceFilter, globBase, globToRegExp, isGlob, walkFiles, DEFAULT_IGNORED_DIRECTORIES, MAX_FILE_SIZE, } from './glob.js';
42
43
  export { compareRefs, createLineIndex, formatRef, refOf, spanOf } from './source.js';
43
44
  export { basenamePosix, dirnamePosix, extnamePosix, joinPosix, normalisePosix, resolveFrom, toPosix, } from './paths.js';
44
- export { DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF } from './types.js';
45
+ export { isProjectRule, DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF, PROJECT_RULE_PREFIX } from './types.js';
45
46
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EACL,aAAa,EACb,cAAc,EACd,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,cAAc,GACf,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGxF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG/F,OAAO,EACL,OAAO,EACP,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9E,OAAO,EACL,YAAY,EACZ,OAAO,EACP,OAAO,EACP,UAAU,EACV,KAAK,EACL,WAAW,EACX,UAAU,EACV,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAWrB,OAAO,EACL,aAAa,EACb,QAAQ,EACR,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,aAAa,EACb,WAAW,EACX,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACxG,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIrF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAGzD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAatD,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGhF,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,EACL,SAAS,EACT,eAAe,EACf,eAAe,EACf,aAAa,EACb,OAAO,EACP,aAAa,EACb,qBAAqB,EACrB,WAAW,GACZ,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGjE,OAAO,EACL,cAAc,EACd,QAAQ,EACR,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,OAAO,GACR,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,SAAS,EACT,2BAA2B,EAC3B,aAAa,GACd,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrF,OAAO,EACL,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,cAAc,EACd,WAAW,EACX,OAAO,GACR,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC","sourcesContent":["/**\n * Programmatic API.\n *\n * The CLI is a shell over `analyse`; anything the CLI can do, a build script, a\n * custom reporter or an editor extension can do too.\n *\n * ```ts\n * import { analyse, formatReport } from '@descent-vtt/spec-graph';\n *\n * const result = await analyse({ root: process.cwd(), patterns: ['docs/**\\/*.md'] });\n * if (!result.ok) console.error(formatReport(result, { color: true }));\n * ```\n *\n * For a corpus that is already in memory - a monorepo tool, a language server,\n * a test - skip the filesystem entirely:\n *\n * ```ts\n * import { analyseSources, query } from '@descent-vtt/spec-graph';\n *\n * const { graph } = analyseSources([{ path: 'adr/0001.md', text: '...' }]);\n * const ghosts = query(graph, 'item[openness=open] -delegates-to-> document[phase=retired]');\n * ```\n */\n\nexport {\n applyBaseline,\n formatBaseline,\n fingerprintOf,\n parseBaseline,\n BASELINE_VERSION,\n EMPTY_BASELINE,\n} from './baseline.js';\nexport type { Baseline, BaselineEntry, BaselineOutcome, ParsedBaseline } from './baseline.js';\n\nexport { loadConfig, parseConfig, CONFIG_FILES, CONFIG_PACKAGE_KEY } from './config.js';\nexport type { LoadedConfig, SpecGraphConfig } from './config.js';\n\nexport { main, parseArgs, HELP, UsageError, EXIT_OK, EXIT_FAILED, EXIT_ERROR } from './cli.js';\nexport type { CliIO, CliOptions, Command } from './cli.js';\n\nexport {\n analyse,\n analyseSources,\n createFamilyFilter,\n createHistoryMatcher,\n withDiagnostics,\n DEFAULT_CONCURRENCY,\n DEFAULT_PATTERNS,\n} from './runner.js';\nexport type { AnalyseOptions, AnalyseSourcesOptions, AnalysisResult, Source } from './runner.js';\n\nexport { buildGraph, LOAD_BEARING_EDGES, OBLIGATION_EDGES } from './graph.js';\nexport type { SpecGraph } from './graph.js';\n\nexport {\n attributesOf,\n execute,\n matches,\n parseQuery,\n query,\n renderMatch,\n QueryError,\n DEFAULT_MATCH_LIMIT,\n} from './select.js';\nexport type {\n ExecuteOptions,\n Match,\n NodeMatcher,\n Operator,\n Predicate,\n QuerySpec,\n StepSpec,\n} from './select.js';\n\nexport {\n resolveStrict,\n runRules,\n sortDiagnostics,\n DEFAULT_SEVERITIES,\n RULE_DESCRIPTIONS,\n RULE_IDS,\n RULE_QUERIES,\n} from './rules.js';\nexport type { RuleOptions } from './rules.js';\n\nexport {\n createPainter,\n formatGraph,\n formatJson,\n formatReport,\n shouldUseAscii,\n shouldUseColor,\n} from './report.js';\nexport type { ColorEnvironment, GraphExportOptions, GraphFormat, Painter, ReporterOptions } from './report.js';\n\nexport { extractDocument, extractSpecifications, classifyReference, sectionPathAt } from './extract.js';\nexport { findSpecificationRegions, readRegionStatus, regionAt } from './sections.js';\nexport type { RegionStatus, SpecificationRegion } from './sections.js';\nexport type { Classification, ExtractedDocument, ExtractInput, ReferenceCandidate } from './extract.js';\n\nexport { resolveCorpus, documentOf } from './resolve.js';\nexport type { ResolvedCorpus, ResolveOptions } from './resolve.js';\n\nexport { scanMarkdown, slugify } from './markdown.js';\nexport type {\n FrontMatter,\n Heading,\n HtmlComment,\n Link,\n LinkForm,\n ListItem,\n Range,\n ScannedDocument,\n ScannedLine,\n} from './markdown.js';\n\nexport { parseDirectives, attr, attrList, directiveFor } from './directives.js';\nexport type { Attribute, Directive, DirectiveName } from './directives.js';\n\nexport { parseFrontMatter, toRecord, valuesOf } from './yaml.js';\nexport type { YamlEntry } from './yaml.js';\n\nexport {\n isRetired,\n isStatusHeading,\n normaliseStatus,\n phaseFromPath,\n phaseOf,\n receptivityOf,\n supersessionTargetsIn,\n STATUS_KEYS,\n} from './lifecycle.js';\n\nexport { resolveItemState, KNOWN_STATE_WORDS } from './state.js';\nexport type { ResolvedState, StateInput } from './state.js';\n\nexport {\n familyFromPath,\n identify,\n isDocumentTarget,\n isExternal,\n looksLikePath,\n normaliseRef,\n parseBareRef,\n parsePrefixedRef,\n splitAnchor,\n ID_KEYS,\n} from './identity.js';\nexport type { DocumentIdentity, IdentityInput } from './identity.js';\n\nexport {\n createGlobMatcher,\n createReferenceFilter,\n globBase,\n globToRegExp,\n isGlob,\n walkFiles,\n DEFAULT_IGNORED_DIRECTORIES,\n MAX_FILE_SIZE,\n} from './glob.js';\nexport type { GlobMatcher, WalkedFile, WalkOptions } from './glob.js';\n\nexport { compareRefs, createLineIndex, formatRef, refOf, spanOf } from './source.js';\nexport type { LineIndex } from './source.js';\n\nexport {\n basenamePosix,\n dirnamePosix,\n extnamePosix,\n joinPosix,\n normalisePosix,\n resolveFrom,\n toPosix,\n} from './paths.js';\n\nexport { DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF } from './types.js';\nexport type {\n AnalysisSummary,\n DanglingReason,\n DanglingRef,\n Diagnostic,\n Disposition,\n DocumentNode,\n Edge,\n EdgeKind,\n EdgeOrigin,\n EdgeTraits,\n ItemNode,\n NodeKind,\n Openness,\n ParseProblem,\n Phase,\n Position,\n Receptivity,\n RelatedLocation,\n RuleId,\n Severity,\n SourceRef,\n Span,\n SpecNode,\n StateSignal,\n} from './types.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EACL,aAAa,EACb,cAAc,EACd,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,cAAc,GACf,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGxF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG/F,OAAO,EACL,OAAO,EACP,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9E,OAAO,EACL,YAAY,EACZ,OAAO,EACP,OAAO,EACP,UAAU,EACV,KAAK,EACL,WAAW,EACX,UAAU,EACV,mBAAmB,EACnB,aAAa,GACd,MAAM,aAAa,CAAC;AAWrB,OAAO,EACL,aAAa,EACb,QAAQ,EACR,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzE,OAAO,EACL,aAAa,EACb,WAAW,EACX,UAAU,EACV,YAAY,EACZ,WAAW,EACX,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACxG,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIrF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAGzD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAatD,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGhF,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,EACL,SAAS,EACT,eAAe,EACf,eAAe,EACf,aAAa,EACb,OAAO,EACP,aAAa,EACb,qBAAqB,EACrB,WAAW,GACZ,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGjE,OAAO,EACL,cAAc,EACd,QAAQ,EACR,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,OAAO,GACR,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,SAAS,EACT,2BAA2B,EAC3B,aAAa,GACd,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrF,OAAO,EACL,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,cAAc,EACd,WAAW,EACX,OAAO,GACR,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC","sourcesContent":["/**\n * Programmatic API.\n *\n * The CLI is a shell over `analyse`; anything the CLI can do, a build script, a\n * custom reporter or an editor extension can do too.\n *\n * ```ts\n * import { analyse, formatReport } from '@descent-vtt/spec-graph';\n *\n * const result = await analyse({ root: process.cwd(), patterns: ['docs/**\\/*.md'] });\n * if (!result.ok) console.error(formatReport(result, { color: true }));\n * ```\n *\n * For a corpus that is already in memory - a monorepo tool, a language server,\n * a test - skip the filesystem entirely:\n *\n * ```ts\n * import { analyseSources, query } from '@descent-vtt/spec-graph';\n *\n * const { graph } = analyseSources([{ path: 'adr/0001.md', text: '...' }]);\n * const ghosts = query(graph, 'item[openness=open] -delegates-to-> document[phase=retired]');\n * ```\n */\n\nexport {\n applyBaseline,\n formatBaseline,\n fingerprintOf,\n parseBaseline,\n BASELINE_VERSION,\n EMPTY_BASELINE,\n} from './baseline.js';\nexport type { Baseline, BaselineEntry, BaselineOutcome, ParsedBaseline, StaleEntry } from './baseline.js';\n\nexport { loadConfig, parseConfig, CONFIG_FILES, CONFIG_PACKAGE_KEY } from './config.js';\nexport type { LoadedConfig, SpecGraphConfig } from './config.js';\n\nexport { main, parseArgs, HELP, UsageError, EXIT_OK, EXIT_FAILED, EXIT_ERROR } from './cli.js';\nexport type { CliIO, CliOptions, Command } from './cli.js';\n\nexport {\n analyse,\n analyseSources,\n createFamilyFilter,\n createHistoryMatcher,\n withDiagnostics,\n DEFAULT_CONCURRENCY,\n DEFAULT_PATTERNS,\n} from './runner.js';\nexport type { AnalyseOptions, AnalyseSourcesOptions, AnalysisResult, Source } from './runner.js';\n\nexport { buildGraph, LOAD_BEARING_EDGES, OBLIGATION_EDGES } from './graph.js';\nexport type { SpecGraph } from './graph.js';\n\nexport {\n attributesOf,\n execute,\n matches,\n parseQuery,\n query,\n renderMatch,\n QueryError,\n DEFAULT_MATCH_LIMIT,\n SELECTOR_KEYS,\n} from './select.js';\nexport type {\n ExecuteOptions,\n Match,\n NodeMatcher,\n Operator,\n Predicate,\n QuerySpec,\n StepSpec,\n} from './select.js';\n\nexport {\n resolveStrict,\n runRules,\n sortDiagnostics,\n DEFAULT_SEVERITIES,\n RULE_DESCRIPTIONS,\n RULE_IDS,\n RULE_QUERIES,\n} from './rules.js';\nexport type { RuleOptions } from './rules.js';\n\nexport { compileProjectRules, renderTemplate } from './project-rules.js';\nexport type { CompiledProjectRules, ProjectRule } from './project-rules.js';\n\nexport {\n createPainter,\n formatGraph,\n formatJson,\n formatReport,\n formatSarif,\n shouldUseAscii,\n shouldUseColor,\n} from './report.js';\nexport type { ColorEnvironment, GraphExportOptions, GraphFormat, Painter, ReporterOptions } from './report.js';\n\nexport { extractDocument, extractSpecifications, classifyReference, sectionPathAt } from './extract.js';\nexport { findSpecificationRegions, readRegionStatus, regionAt } from './sections.js';\nexport type { RegionStatus, SpecificationRegion } from './sections.js';\nexport type { Classification, ExtractedDocument, ExtractInput, ReferenceCandidate } from './extract.js';\n\nexport { resolveCorpus, documentOf } from './resolve.js';\nexport type { ResolvedCorpus, ResolveOptions } from './resolve.js';\n\nexport { scanMarkdown, slugify } from './markdown.js';\nexport type {\n FrontMatter,\n Heading,\n HtmlComment,\n Link,\n LinkForm,\n ListItem,\n Range,\n ScannedDocument,\n ScannedLine,\n} from './markdown.js';\n\nexport { parseDirectives, attr, attrList, directiveFor } from './directives.js';\nexport type { Attribute, Directive, DirectiveName } from './directives.js';\n\nexport { parseFrontMatter, toRecord, valuesOf } from './yaml.js';\nexport type { YamlEntry } from './yaml.js';\n\nexport {\n isRetired,\n isStatusHeading,\n normaliseStatus,\n phaseFromPath,\n phaseOf,\n receptivityOf,\n supersessionTargetsIn,\n STATUS_KEYS,\n} from './lifecycle.js';\n\nexport { resolveItemState, KNOWN_STATE_WORDS } from './state.js';\nexport type { ResolvedState, StateInput } from './state.js';\n\nexport {\n familyFromPath,\n identify,\n isDocumentTarget,\n isExternal,\n looksLikePath,\n normaliseRef,\n parseBareRef,\n parsePrefixedRef,\n splitAnchor,\n ID_KEYS,\n} from './identity.js';\nexport type { DocumentIdentity, IdentityInput } from './identity.js';\n\nexport {\n createGlobMatcher,\n createReferenceFilter,\n globBase,\n globToRegExp,\n isGlob,\n walkFiles,\n DEFAULT_IGNORED_DIRECTORIES,\n MAX_FILE_SIZE,\n} from './glob.js';\nexport type { GlobMatcher, WalkedFile, WalkOptions } from './glob.js';\n\nexport { compareRefs, createLineIndex, formatRef, refOf, spanOf } from './source.js';\nexport type { LineIndex } from './source.js';\n\nexport {\n basenamePosix,\n dirnamePosix,\n extnamePosix,\n joinPosix,\n normalisePosix,\n resolveFrom,\n toPosix,\n} from './paths.js';\n\nexport { isProjectRule, DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF, PROJECT_RULE_PREFIX } from './types.js';\nexport type {\n AnalysisSummary,\n AnyRuleId,\n DanglingReason,\n DanglingRef,\n Diagnostic,\n Disposition,\n DocumentNode,\n Edge,\n EdgeKind,\n EdgeOrigin,\n EdgeTraits,\n ItemNode,\n MisreadKey,\n NodeKind,\n Openness,\n ParseProblem,\n Phase,\n Position,\n Receptivity,\n ProjectRuleId,\n RelatedLocation,\n RuleId,\n Severity,\n SourceRef,\n Span,\n SpecNode,\n StateSignal,\n SuppressedRef,\n} from './types.js';\n"]}
@@ -101,13 +101,27 @@ export type LinkForm = 'inline' | 'reference' | 'shortcut' | 'autolink' | 'wiki'
101
101
  export interface Link {
102
102
  /** Visible label. Empty for autolinks and definitions. */
103
103
  readonly text: string;
104
- /** Destination as written: a path, a URL, or a reference label. */
104
+ /** Destination as written: a path or a URL. */
105
105
  readonly target: string;
106
106
  /** Offset of the whole construct. */
107
107
  readonly start: number;
108
108
  readonly end: number;
109
- /** Offset of `target` inside the source, for precise diagnostics. */
109
+ /**
110
+ * Offset of `target` inside the source, for precise diagnostics.
111
+ *
112
+ * For a reference link this is inside the definition, not inside the use:
113
+ * `[design][one]` has no destination of its own, and the line a human edits
114
+ * to fix it is the one that says what `one` points at.
115
+ */
110
116
  readonly targetStart: number;
117
+ /**
118
+ * The reference label, for the two forms written with one.
119
+ *
120
+ * `null` everywhere else. Kept because `target` is the destination the label
121
+ * stands for, and a report quoting `[design][docs/gone.md]` would be quoting
122
+ * something the author never wrote.
123
+ */
124
+ readonly label: string | null;
111
125
  readonly form: LinkForm;
112
126
  readonly line: number;
113
127
  }
package/dist/markdown.js CHANGED
@@ -29,7 +29,10 @@ export function scanMarkdown(source) {
29
29
  const frontMatter = readFrontMatter(text, index);
30
30
  const bodyStart = frontMatter ? frontMatter.bodyStart : 0;
31
31
  const lines = scanLines(text, index, bodyStart);
32
- const codeRanges = collectCodeRanges(lines, bodyStart);
32
+ // Raw-text HTML joins the code ranges rather than sitting beside them: what
33
+ // both have in common is that their content is not Markdown, and every reader
34
+ // downstream - comments, links, tables - already asks that one question.
35
+ const codeRanges = mergeRanges([...collectCodeRanges(lines, bodyStart), ...collectRawTextHtml(lines, bodyStart)]);
33
36
  const comments = scanComments(text, index, bodyStart, codeRanges);
34
37
  const inlineCode = scanInlineCode(text, bodyStart, codeRanges, comments);
35
38
  const maskRanges = [
@@ -205,6 +208,48 @@ function scanLines(text, index, bodyStart) {
205
208
  }
206
209
  return out;
207
210
  }
211
+ /**
212
+ * `<script>`, `<style>`, `<pre>` and `<textarea>` blocks.
213
+ *
214
+ * These four are the only HTML elements whose content is not Markdown - the
215
+ * CommonMark spec calls them out by name for exactly that reason - and the
216
+ * distinction is load-bearing here rather than pedantic. A page explaining how
217
+ * to annotate a document puts `<!-- @spec-node id="..." -->` inside a script
218
+ * sample or a `<pre>` block, and reading that as a directive lets a worked
219
+ * example rename the document it appears in.
220
+ *
221
+ * `<div>` and `<details>` are deliberately not on the list. Their content *is*
222
+ * Markdown, and a decision written inside a collapsed `<details>` section is
223
+ * still a decision.
224
+ */
225
+ const RAW_TEXT_OPEN = /^<(script|pre|style|textarea)(?:[\s>]|$)/i;
226
+ const RAW_TEXT_CLOSE = /<\/(?:script|pre|style|textarea)>/i;
227
+ function collectRawTextHtml(lines, bodyStart) {
228
+ const ranges = [];
229
+ let open = null;
230
+ for (const line of lines) {
231
+ if (line.code)
232
+ continue;
233
+ if (open === null) {
234
+ if (!RAW_TEXT_OPEN.test(line.content.trimStart()))
235
+ continue;
236
+ open = { start: Math.max(line.start, bodyStart), end: line.end };
237
+ }
238
+ else {
239
+ open.end = line.end;
240
+ }
241
+ // The close tag ends the block on the line that carries it, whichever of
242
+ // the four it names. An unclosed block runs to the end of the document,
243
+ // which is what a browser does with it too.
244
+ if (RAW_TEXT_CLOSE.test(line.content)) {
245
+ ranges.push(open);
246
+ open = null;
247
+ }
248
+ }
249
+ if (open)
250
+ ranges.push(open);
251
+ return ranges;
252
+ }
208
253
  function collectCodeRanges(lines, bodyStart) {
209
254
  const ranges = [];
210
255
  let open = null;
@@ -604,9 +649,13 @@ const AUTOLINK = /<((?:https?|ftp|mailto):[^>\s]+)>/g;
604
649
  const MAX_LINK_SPAN = 4096;
605
650
  function scanLinks(masked, text, index) {
606
651
  const out = [];
607
- const definitions = new Set();
652
+ const definitions = new Map();
608
653
  const definitionRanges = [];
609
- // Pass 1: reference definitions, which also make shortcut links resolvable.
654
+ // Pass 1: reference definitions. These are the destination table the other
655
+ // two written-with-a-label forms are read through, which is why the whole
656
+ // document is swept for them before a single use is looked at: a definition
657
+ // is conventionally written at the foot of the file, long after the links
658
+ // that use it.
610
659
  for (let line = 1; line <= index.lineCount; line += 1) {
611
660
  const start = index.lineStart(line);
612
661
  const content = masked.slice(start, index.lineEnd(line));
@@ -614,16 +663,20 @@ function scanLinks(masked, text, index) {
614
663
  if (!match)
615
664
  continue;
616
665
  const label = match[1].trim().toLowerCase();
617
- definitions.add(label);
618
666
  const rawTarget = match[2];
619
667
  const targetStart = start + content.indexOf(rawTarget, match[1].length);
668
+ const target = unwrapAngle(rawTarget);
669
+ // First definition wins, as in CommonMark.
670
+ if (!definitions.has(label))
671
+ definitions.set(label, { target, targetStart });
620
672
  definitionRanges.push({ start, end: start + match[0].length });
621
673
  out.push({
622
674
  text: match[1].trim(),
623
- target: unwrapAngle(rawTarget),
675
+ target,
624
676
  start,
625
677
  end: start + match[0].length,
626
678
  targetStart,
679
+ label: match[1].trim(),
627
680
  form: 'definition',
628
681
  line,
629
682
  });
@@ -638,6 +691,7 @@ function scanLinks(masked, text, index) {
638
691
  start,
639
692
  end: start + m[0].length,
640
693
  targetStart: start + 1,
694
+ label: null,
641
695
  form: 'autolink',
642
696
  line: index.positionAt(start).line,
643
697
  });
@@ -668,6 +722,7 @@ function scanLinks(masked, text, index) {
668
722
  start: i,
669
723
  end: close + 2,
670
724
  targetStart: i + 2,
725
+ label: null,
671
726
  form: 'wiki',
672
727
  line: index.positionAt(i).line,
673
728
  });
@@ -693,6 +748,7 @@ function scanLinks(masked, text, index) {
693
748
  start: isImage ? i - 1 : i,
694
749
  end: dest.close + 1,
695
750
  targetStart: dest.start,
751
+ label: null,
696
752
  form: 'inline',
697
753
  line: index.positionAt(i).line,
698
754
  });
@@ -705,14 +761,20 @@ function scanLinks(masked, text, index) {
705
761
  const refEnd = matchBracket(masked, labelEnd + 1);
706
762
  if (refEnd !== -1) {
707
763
  const ref = text.slice(labelEnd + 2, refEnd).trim();
708
- const resolved = ref.length > 0 ? ref : label.trim();
709
- if (!isImage) {
764
+ const written = ref.length > 0 ? ref : label.trim();
765
+ const defined = definitions.get(written.toLowerCase());
766
+ // A label with no definition is not a link. Every renderer prints
767
+ // `[design][one]` verbatim when nothing says what `one` is, so reading
768
+ // it as a citation invents a reference the author never made - and
769
+ // then reports it as broken.
770
+ if (!isImage && defined) {
710
771
  out.push({
711
772
  text: label.trim(),
712
- target: resolved,
713
- start: isImage ? i - 1 : i,
773
+ target: defined.target,
774
+ start: i,
714
775
  end: refEnd + 1,
715
- targetStart: ref.length > 0 ? labelEnd + 2 : i + 1,
776
+ targetStart: defined.targetStart,
777
+ label: written,
716
778
  form: 'reference',
717
779
  line: index.positionAt(i).line,
718
780
  });
@@ -721,18 +783,23 @@ function scanLinks(masked, text, index) {
721
783
  continue;
722
784
  }
723
785
  }
724
- else if (!isImage && definitions.has(label.trim().toLowerCase())) {
725
- out.push({
726
- text: label.trim(),
727
- target: label.trim(),
728
- start: i,
729
- end: labelEnd + 1,
730
- targetStart: i + 1,
731
- form: 'shortcut',
732
- line: index.positionAt(i).line,
733
- });
734
- i = labelEnd + 1;
735
- continue;
786
+ else if (!isImage) {
787
+ const written = label.trim();
788
+ const defined = definitions.get(written.toLowerCase());
789
+ if (defined) {
790
+ out.push({
791
+ text: written,
792
+ target: defined.target,
793
+ start: i,
794
+ end: labelEnd + 1,
795
+ targetStart: defined.targetStart,
796
+ label: written,
797
+ form: 'shortcut',
798
+ line: index.positionAt(i).line,
799
+ });
800
+ i = labelEnd + 1;
801
+ continue;
802
+ }
736
803
  }
737
804
  i = labelEnd + 1;
738
805
  }