@descent-vtt/spec-graph 0.9.1 → 0.9.3

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +162 -15
  3. package/dist/baseline.js +6 -0
  4. package/dist/baseline.js.map +1 -1
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.js +50 -17
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config.d.ts +3 -3
  9. package/dist/config.js +16 -8
  10. package/dist/config.js.map +1 -1
  11. package/dist/diff.js +14 -0
  12. package/dist/diff.js.map +1 -1
  13. package/dist/directives.js +9 -6
  14. package/dist/directives.js.map +1 -1
  15. package/dist/extract.js +120 -57
  16. package/dist/extract.js.map +1 -1
  17. package/dist/glob.js +28 -16
  18. package/dist/glob.js.map +1 -1
  19. package/dist/graph.d.ts +2 -1
  20. package/dist/graph.js +10 -1
  21. package/dist/graph.js.map +1 -1
  22. package/dist/identity.d.ts +6 -0
  23. package/dist/identity.js +68 -68
  24. package/dist/identity.js.map +1 -1
  25. package/dist/lifecycle.js +17 -11
  26. package/dist/lifecycle.js.map +1 -1
  27. package/dist/report.js +34 -5
  28. package/dist/report.js.map +1 -1
  29. package/dist/resolve.js +40 -14
  30. package/dist/resolve.js.map +1 -1
  31. package/dist/rules.d.ts +8 -0
  32. package/dist/rules.js +83 -16
  33. package/dist/rules.js.map +1 -1
  34. package/dist/runner.js +22 -4
  35. package/dist/runner.js.map +1 -1
  36. package/dist/sections.d.ts +9 -0
  37. package/dist/sections.js +60 -9
  38. package/dist/sections.js.map +1 -1
  39. package/dist/select.d.ts +18 -2
  40. package/dist/select.js +46 -16
  41. package/dist/select.js.map +1 -1
  42. package/dist/source.js +2 -0
  43. package/dist/source.js.map +1 -1
  44. package/dist/state.js +46 -12
  45. package/dist/state.js.map +1 -1
  46. package/dist/yaml.js +11 -3
  47. package/dist/yaml.js.map +1 -1
  48. package/package.json +6 -3
  49. package/src/vendor/spec-core/README.md +0 -8
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Corpus-level reference resolution - the foreign-key check.\n *\n * Extraction produces relations whose targets are still just text. This module\n * turns each into an edge or into an explicit, reportable failure. The whole\n * value of the exercise is in refusing to do either job carelessly:\n *\n * - **A deliberate reference that does not resolve is an error.** A link, a\n * front-matter field or a directive naming a document that does not exist is\n * a broken foreign key, and silence about it is how reference graphs rot.\n * - **An opportunistic reference that does not resolve is nothing.** `ADR-0007`\n * written in prose is a citation; `SHA-256` is not. Since the two are\n * indistinguishable in isolation, bare identifiers are only reported when\n * their family already exists in the corpus - so `ADR-0099` in a repository\n * full of ADRs is a finding, and `T-1000` in the same repository is not.\n * - **Two candidates is worse than none.** A reference that matches several\n * documents is reported as ambiguous rather than silently bound to whichever\n * one happened to be indexed first.\n */\n\nimport type { ExtractedDocument, ReferenceCandidate } from './extract.js';\nimport {\n isDocumentTarget,\n isExternal,\n looksLikePath,\n normaliseRef,\n parseBareRef,\n parsePrefixedRef,\n splitAnchor,\n withinOneEdit,\n} from './identity.js';\nimport { basenamePosix, dirnamePosix, extnamePosix, resolveFrom } from './paths.js';\nimport type {\n DanglingRef,\n DocumentNode,\n Edge,\n ItemNode,\n MisreadKey,\n ParseProblem,\n SpecNode,\n SuppressedRef,\n} from './types.js';\n\nexport interface ResolveOptions {\n /**\n * Whether a repository-relative path exists on disk.\n *\n * Used only to tell \"you linked to a file that is not a specification\" apart\n * from \"you linked to nothing at all\", which are different mistakes with\n * different fixes.\n */\n readonly fileExists?: ((path: string) => boolean) | undefined;\n /**\n * Targets whose failure to resolve should not be reported.\n *\n * This is a *suppression* filter, not a resolution one: it is consulted only\n * when a reference has already failed to resolve. A reference that resolves\n * still becomes an edge no matter what this says, so no amount of\n * configuration can silently delete a relation from the graph.\n */\n readonly isIgnoredReference?: ((target: string) => boolean) | undefined;\n /**\n * Families that are never citations in this repository.\n *\n * `RFC 2119` is the canonical case: every specification cites it, and a\n * repository that keeps its own `RFC-*` documents will otherwise read that\n * sentence as a dangling reference to a local RFC 2119 it does not have.\n * Consulted only after resolution has failed, like every other filter here.\n */\n readonly isIgnoredFamily?: ((family: string) => boolean) | undefined;\n}\n\nexport interface ResolvedCorpus {\n readonly nodes: ReadonlyMap<string, SpecNode>;\n readonly documents: readonly DocumentNode[];\n readonly items: readonly ItemNode[];\n readonly edges: readonly Edge[];\n readonly dangling: readonly DanglingRef[];\n readonly problems: readonly ParseProblem[];\n readonly misreadKeys: readonly MisreadKey[];\n /**\n * References this repository declared none of its business.\n *\n * Not findings, and never reported as such - they are the audit trail for\n * the two settings that can silence one. See ADR-0008.\n */\n readonly suppressed: readonly SuppressedRef[];\n}\n\ninterface Index {\n readonly byId: Map<string, string>;\n readonly byAlias: Map<string, Set<string>>;\n /** The literal path of a file. Unique: no two files share one. */\n readonly byPath: Map<string, string>;\n /**\n * Spellings that address a file without naming it exactly - the extension\n * dropped, a directory standing for its README. Several files can answer to\n * one of those, so this is a set, exactly as `byAlias` is.\n */\n readonly byPathAlias: Map<string, Set<string>>;\n readonly byFamilyNumber: Map<string, Set<string>>;\n readonly families: Set<string>;\n /** Every file by the directory holding it, for a basename that was mistyped. */\n readonly byDirectory: Map<string, { stem: string; id: string }[]>;\n /**\n * Alias keys grouped by length.\n *\n * A one-edit match can only differ in length by one, so this turns the scan\n * behind a suggestion from the whole corpus into three buckets of it.\n */\n readonly aliasesByLength: Map<number, string[]>;\n readonly anchors: Map<string, ReadonlySet<string>>;\n readonly itemIds: Set<string>;\n readonly familyOf: Map<string, string | null>;\n}\n\n/** Resolves every extracted reference against the whole corpus. */\nexport function resolveCorpus(\n extracted: readonly ExtractedDocument[],\n options: ResolveOptions = {},\n): ResolvedCorpus {\n const index = buildIndex(extracted);\n const nodes = new Map<string, SpecNode>();\n const documents: DocumentNode[] = [];\n const items: ItemNode[] = [];\n const problems: ParseProblem[] = [];\n const misreadKeys: MisreadKey[] = [];\n\n for (const entry of extracted) {\n const existing = nodes.get(entry.document.id);\n if (existing && existing.kind === 'document') {\n problems.push({\n message: `duplicate document id \"${entry.document.id}\", already declared by ${existing.path}`,\n at: entry.document.at,\n });\n continue;\n }\n nodes.set(entry.document.id, entry.document);\n documents.push(entry.document);\n for (const item of entry.items) {\n // Nodes are keyed by id, so a second item would replace the first in the\n // graph while both were still counted, and one of them would vanish from\n // the export without a word.\n const taken = nodes.get(item.id);\n if (taken) {\n problems.push({\n message: `duplicate item id \"${item.id}\", already taken at ${taken.at.file}:${taken.at.span.start.line}`,\n at: item.at,\n });\n continue;\n }\n nodes.set(item.id, item);\n items.push(item);\n }\n problems.push(...entry.problems);\n misreadKeys.push(...entry.misreadKeys);\n }\n\n const edges: Edge[] = [];\n const dangling: DanglingRef[] = [];\n const suppressed: SuppressedRef[] = [];\n const byKey = new Map<string, number>();\n\n // Structural edges: a register contains the specifications written inside it.\n for (const entry of extracted) {\n if (entry.containerId === null) continue;\n if (!nodes.has(entry.containerId) || !nodes.has(entry.document.id)) continue;\n edges.push({\n kind: 'contains',\n from: entry.containerId,\n to: entry.document.id,\n origin: 'structural',\n declaredAt: entry.document.at,\n raw: entry.document.title,\n declaredIn: [entry.document.at.file],\n reflexive: false,\n });\n }\n\n // Structural edges: a document contains its items.\n for (const item of items) {\n edges.push({\n kind: 'contains',\n from: item.document,\n to: item.id,\n origin: 'structural',\n declaredAt: item.at,\n raw: item.text,\n declaredIn: [item.at.file],\n reflexive: false,\n });\n }\n\n for (const entry of extracted) {\n for (const candidate of entry.references) {\n if (!nodes.has(candidate.from)) continue;\n const outcome = resolveOne(candidate, entry, index, nodes, options);\n if (outcome === null) continue;\n if ('by' in outcome) {\n suppressed.push(outcome);\n continue;\n }\n if ('reason' in outcome) {\n dangling.push(outcome);\n continue;\n }\n // Collapse duplicates: the same relation stated twice is one edge - but\n // every declaration site is kept, because a supersession recorded in only\n // one of the two documents is itself a finding.\n const key = `${outcome.kind} ${outcome.from} ${outcome.to}`;\n const existing = byKey.get(key);\n if (existing !== undefined) {\n const previous = edges[existing] as Edge;\n if (!previous.declaredIn.includes(outcome.declaredAt.file)) {\n edges[existing] = { ...previous, declaredIn: [...previous.declaredIn, outcome.declaredAt.file] };\n }\n continue;\n }\n byKey.set(key, edges.length);\n edges.push(outcome);\n }\n }\n\n return { nodes, documents, items, edges, dangling, problems, misreadKeys, suppressed };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Indexing */\n/* -------------------------------------------------------------------------- */\n\nfunction buildIndex(extracted: readonly ExtractedDocument[]): Index {\n const index: Index = {\n byId: new Map(),\n byAlias: new Map(),\n byPath: new Map(),\n byPathAlias: new Map(),\n byFamilyNumber: new Map(),\n families: new Set(),\n byDirectory: new Map(),\n aliasesByLength: new Map(),\n anchors: new Map(),\n itemIds: new Set(),\n familyOf: new Map(),\n };\n\n for (const entry of extracted) {\n const id = entry.document.id;\n index.byId.set(normaliseRef(id), id);\n index.anchors.set(id, entry.anchors);\n index.familyOf.set(id, entry.identity.family);\n\n for (const alias of entry.document.aliases) {\n const set = index.byAlias.get(alias) ?? new Set<string>();\n set.add(id);\n index.byAlias.set(alias, set);\n }\n\n // Only a file answers to its path. A region keeps the path on its node, so a\n // finding can say where it lives and `document[path=...]` can select it, but\n // it must not be reachable *by* that path: two nodes answering to one path\n // makes every link to the file ambiguous, and this index resolves a\n // collision by keeping whichever was written last. A link to a file holding\n // a register bound to its final row, quietly and with no diagnostic, and\n // anchors were then checked against that row's span rather than the file's.\n // ADR-0009 said a region does not claim the file's path; this is the index\n // where that had to be true.\n if (entry.containerId === null) {\n const path = entry.document.path.toLowerCase();\n index.byPath.set(path, id);\n for (const spelling of pathAliases(entry.document.path)) {\n const set = index.byPathAlias.get(spelling) ?? new Set<string>();\n set.add(id);\n index.byPathAlias.set(spelling, set);\n }\n const directory = dirnamePosix(path);\n const siblings = index.byDirectory.get(directory) ?? [];\n siblings.push({ stem: stemOf(path), id });\n index.byDirectory.set(directory, siblings);\n }\n\n if (entry.identity.family !== null && entry.identity.number !== null) {\n index.families.add(entry.identity.family);\n const key = `${entry.identity.family}:${entry.identity.number}`;\n const set = index.byFamilyNumber.get(key) ?? new Set<string>();\n set.add(id);\n index.byFamilyNumber.set(key, set);\n }\n\n for (const item of entry.items) index.itemIds.add(item.id);\n }\n\n for (const alias of index.byAlias.keys()) {\n const bucket = index.aliasesByLength.get(alias.length) ?? [];\n bucket.push(alias);\n index.aliasesByLength.set(alias.length, bucket);\n }\n\n return index;\n}\n\n/** A file name with its extension taken off: `docs/a/0004-sharding.md` -> `0004-sharding`. */\nfunction stemOf(path: string): string {\n const base = basenamePosix(path);\n return base.slice(0, base.length - extnamePosix(base).length);\n}\n\n/** Every spelling of a path that addresses a file without naming it exactly. */\nfunction pathAliases(path: string): string[] {\n const keys = new Set<string>();\n const extension = extnamePosix(path);\n if (extension.length > 0) keys.add(path.slice(0, path.length - extension.length).toLowerCase());\n // `docs/adr/0007/README.md` is also addressed as `docs/adr/0007`.\n const base = basenamePosix(path);\n if (/^(readme|index)\\./i.test(base)) {\n const parent = path.slice(0, path.length - base.length - 1);\n if (parent.length > 0) keys.add(parent.toLowerCase());\n }\n // A file never addresses itself inexactly. Leaving this in would let a\n // contrived name - `docs/a.md.md`, whose stem is another file's whole path -\n // make an exact link look ambiguous.\n keys.delete(path.toLowerCase());\n return [...keys];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Resolution */\n/* -------------------------------------------------------------------------- */\n\nfunction resolveOne(\n candidate: ReferenceCandidate,\n entry: ExtractedDocument,\n index: Index,\n nodes: ReadonlyMap<string, SpecNode>,\n options: ResolveOptions,\n): Edge | DanglingRef | SuppressedRef | null {\n const { target: bare, anchor } = splitAnchor(candidate.target);\n\n // A pure `#anchor` points inside the citing document.\n if (bare.length === 0 && anchor !== null) {\n return bindAnchor(candidate, entry.document.id, anchor, index, nodes, options);\n }\n\n if (isExternal(bare)) return null;\n // A link to source code, an image or a config file is not a specification\n // reference. Validating it would report an error on the most ordinary thing a\n // design document does.\n if (!isDocumentTarget(bare)) return null;\n\n const found = lookup(bare, entry, index);\n\n if (found.ids.length === 0) {\n if (candidate.opportunistic && !worthReporting(bare, index)) return null;\n const silenced = filteredBy(candidate.target, bare, options);\n if (silenced !== null) return suppression(candidate, silenced);\n // A trailing slash names a directory. Linking to one is ordinary - \"the\n // decisions live in [archive/](archive/)\" - and is not a citation of any\n // document. Resolution is still attempted first, so a directory-style\n // document written as `0007-sharding/` continues to resolve.\n if (bare.endsWith('/')) return null;\n const reason = pathMissingReason(bare, entry, options);\n return dangle(candidate, reason, found.near);\n }\n\n if (found.ids.length > 1) {\n if (candidate.opportunistic) return null;\n const silenced = filteredBy(candidate.target, bare, options);\n if (silenced !== null) return suppression(candidate, silenced);\n return dangle(candidate, 'ambiguous', found.ids);\n }\n\n const documentId = found.ids[0] as string;\n\n // A document naming itself in prose - almost always its own H1, `# ADR-0007:\n // Sharding` - is not a citation. Deliberate self-links are kept, because a\n // link that points at its own document is a real mistake worth reporting.\n if (candidate.opportunistic && documentId === entry.document.id) return null;\n\n if (anchor !== null) return bindAnchor(candidate, documentId, anchor, index, nodes, options);\n\n return makeEdge(candidate, documentId);\n}\n\ninterface Lookup {\n readonly ids: readonly string[];\n /** Near-misses worth suggesting in the report. */\n readonly near: readonly string[];\n}\n\n/**\n * The same path with its percent-escapes read.\n *\n * `[Design](docs/my%20design.md)` is what a renderer, a documentation site and\n * GitHub's own \"copy link\" all produce for a file with a space in its name, and\n * it names a real file. Returns `null` when there was nothing to decode or when\n * the escapes are malformed - `100%` in a path is not an escape sequence, and\n * `decodeURIComponent` throws on it.\n */\nfunction percentDecoded(target: string): string | null {\n if (!target.includes('%')) return null;\n try {\n const decoded = decodeURIComponent(target);\n return decoded === target ? null : decoded;\n } catch {\n return null;\n }\n}\n\nfunction lookup(target: string, entry: ExtractedDocument, index: Index): Lookup {\n const exact = lookupExact(target, entry, index);\n if (exact.length > 0) return { ids: exact, near: [] };\n // Tried second and never first, so a repository holding a file whose name\n // genuinely contains a percent escape keeps resolving by its written spelling.\n const decoded = percentDecoded(target);\n const relaxed = decoded === null ? [] : lookupExact(decoded, entry, index);\n if (relaxed.length > 0) return { ids: relaxed, near: [] };\n // Suggestions are computed only once resolution has failed outright, so their\n // cost is bounded by the number of findings rather than by the corpus.\n return { ids: [], near: nearMisses(target, entry, index) };\n}\n\nfunction lookupExact(target: string, entry: ExtractedDocument, index: Index): readonly string[] {\n if (looksLikePath(target)) {\n const resolved = resolveFrom(entry.document.path, target).toLowerCase();\n // Naming the file exactly is never ambiguous, whatever else is spelled the\n // same way.\n const byPath = index.byPath.get(resolved);\n if (byPath) return [byPath];\n // An inexact spelling can belong to more than one file: `docs/A` is both\n // `docs/A.md` with its extension dropped and `docs/A/README.md` standing\n // for its directory. Handing back every claimant makes that an\n // ambiguous-reference the author can settle, rather than a silent pick of\n // whichever happened to be indexed last.\n const spelled = index.byPathAlias.get(resolved);\n if (spelled && spelled.size > 0) return [...spelled];\n // A path may still be spelled as an identifier in a nested folder layout.\n const stem = basenamePosix(resolved);\n const byStem = index.byAlias.get(normaliseRef(stem.replace(/\\.[^.]+$/, '')));\n if (byStem && byStem.size > 0) return [...byStem];\n return [];\n }\n\n const key = normaliseRef(target);\n if (key.length === 0) return [];\n\n const byId = index.byId.get(key);\n if (byId) return [byId];\n\n const byAlias = index.byAlias.get(key);\n if (byAlias && byAlias.size > 0) return [...byAlias];\n\n const prefixed = parsePrefixedRef(target);\n if (prefixed) {\n const byFamily = index.byFamilyNumber.get(`${prefixed.family}:${prefixed.number}`);\n if (byFamily && byFamily.size > 0) return [...byFamily];\n return [];\n }\n\n // A bare number resolves only inside the citing document's own family. A\n // repository with both `adr/0007` and `rfc/0007` is ordinary, and guessing\n // between them would be worse than reporting nothing.\n const number = parseBareRef(target);\n if (number !== null) {\n const family = index.familyOf.get(entry.document.id) ?? null;\n if (family === null) return [];\n const byFamily = index.byFamilyNumber.get(`${family}:${number}`);\n if (byFamily && byFamily.size > 0) return [...byFamily];\n }\n\n return [];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Near misses */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The one document a failed reference was probably meant to name.\n *\n * A suggestion is cheap to read and expensive to get wrong: \"did you mean\n * ADR-0008?\" against a citation that meant ADR-0012 sends a reader to the wrong\n * decision with a confidence nobody earned. So the bar is not closeness, it is\n * closeness *in the part of the spelling that is not the identity*. A family\n * name is a word people misremember. A number is the identity itself, and\n * `ADR-0009` sits one edit from every neighbour it has - so numbers are never\n * guessed at, and a repository of fifteen ADRs citing a sixteenth is told the\n * plain truth instead.\n *\n * Two gates, plus a fallback:\n *\n * - a path in the right directory whose basename is one edit out - a typo;\n * - a family one edit out with its number intact - `ARD-0009` for `ADR-0009`.\n *\n * Anything else falls through to a one-edit match against the folded spellings\n * documents already answer to, which is where a mistyped slug or wiki name\n * lands.\n *\n * There is deliberately no gate for a file that moved. Resolution already binds\n * a path by its basename, so `../guides/onboarding.md` finds the document that\n * is now in `handbook/` without anybody being asked to confirm a guess - and a\n * suggestion nobody needs is a suggestion that can only be wrong.\n *\n * Every gate ends the same way: **exactly one** candidate, or nothing. Two\n * suggestions is the ambiguity this module refuses to resolve anywhere else,\n * and the tail of a hint is no place to start.\n */\nfunction nearMisses(target: string, entry: ExtractedDocument, index: Index): string[] {\n if (looksLikePath(target)) {\n const resolved = resolveFrom(entry.document.path, target).toLowerCase();\n const stem = stemOf(resolved);\n const siblings = index.byDirectory.get(dirnamePosix(resolved)) ?? [];\n return sole(siblings.filter((file) => withinOneEdit(file.stem, stem)).map((file) => file.id));\n }\n\n const prefixed = parsePrefixedRef(target);\n if (prefixed !== null) {\n const hits: string[] = [];\n for (const family of index.families) {\n // The family as written needs no exclusion: one edit is never zero edits.\n if (!withinOneEdit(family.toLowerCase(), prefixed.family.toLowerCase())) continue;\n for (const id of index.byFamilyNumber.get(`${family}:${prefixed.number}`) ?? []) hits.push(id);\n }\n return sole(hits);\n }\n\n // A bare `12` carries nothing but a number, which is the one thing this\n // refuses to guess at.\n if (parseBareRef(target) !== null) return [];\n\n const key = normaliseRef(target);\n if (key.length < MIN_SUGGESTIBLE) return [];\n const hits: string[] = [];\n for (const length of [key.length - 1, key.length, key.length + 1]) {\n for (const alias of index.aliasesByLength.get(length) ?? []) {\n if (!withinOneEdit(alias, key)) continue;\n for (const id of index.byAlias.get(alias) ?? []) hits.push(id);\n }\n }\n return sole(hits);\n}\n\n/**\n * How long a folded spelling has to be before one edit means anything.\n *\n * Below six characters an edit is most of the word, and every corpus is full of\n * short names that differ by exactly that much.\n */\nconst MIN_SUGGESTIBLE = 6;\n\n/** The candidates, if there is exactly one of them. Otherwise nothing. */\nfunction sole(ids: Iterable<string>): string[] {\n const unique = new Set(ids);\n return unique.size === 1 ? [...unique] : [];\n}\n\n/**\n * Which setting, if any, declared this reference none of the repository's\n * business.\n *\n * Returns the mechanism rather than a boolean so the audit trail can say which\n * line of configuration to go and look at. A team that silenced eight things\n * with one glob and one with a family needs to be told them apart.\n */\nfunction filteredBy(target: string, bare: string, options: ResolveOptions): SuppressedRef['by'] | null {\n if (options.isIgnoredReference?.(target)) return 'reference';\n const prefixed = parsePrefixedRef(bare);\n if (prefixed !== null && options.isIgnoredFamily?.(prefixed.family) === true) return 'family';\n return null;\n}\n\n/**\n * Whether an unresolved bare identifier deserves a report.\n *\n * Only when the corpus already contains that family. `ADR-0099` in a repository\n * of ADRs is a real dangling citation; `T-1000` in the same repository is a\n * sentence about a robot.\n */\nfunction worthReporting(target: string, index: Index): boolean {\n const prefixed = parsePrefixedRef(target);\n return prefixed !== null && index.families.has(prefixed.family);\n}\n\nfunction pathMissingReason(\n target: string,\n entry: ExtractedDocument,\n options: ResolveOptions,\n): DanglingRef['reason'] {\n if (!looksLikePath(target) || !options.fileExists) return 'unknown-target';\n const resolved = resolveFrom(entry.document.path, target);\n return options.fileExists(resolved) ? 'not-a-spec' : 'unknown-target';\n}\n\n/**\n * Binds a `#anchor` to an item or a section.\n *\n * Anchors are how a delegation names a specific obligation rather than a whole\n * document, so resolving them precisely is what lets a ghost-handover finding\n * say *which* question was handed into the archive.\n */\nfunction bindAnchor(\n candidate: ReferenceCandidate,\n documentId: string,\n anchor: string,\n index: Index,\n nodes: ReadonlyMap<string, SpecNode>,\n options: ResolveOptions,\n): Edge | DanglingRef | SuppressedRef | null {\n const itemId = `${documentId}#${anchor}`;\n if (index.itemIds.has(itemId)) return makeEdge(candidate, itemId);\n\n const anchors = index.anchors.get(documentId);\n if (anchors?.has(anchor.toLowerCase())) return makeEdge(candidate, documentId);\n\n // Some renderers slugify differently; try the loosest reasonable match before\n // calling it broken.\n const loose = anchor.toLowerCase().replace(/[^\\p{L}\\p{N}]+/gu, '-').replace(/^-|-$/g, '');\n if (anchors?.has(loose)) return makeEdge(candidate, documentId);\n\n const silenced = filteredBy(candidate.target, splitAnchor(candidate.target).target, options);\n if (silenced !== null) return suppression(candidate, silenced);\n\n const candidates = [...(nodes.keys() as Iterable<string>)].filter(\n (id) => id.startsWith(`${documentId}#`) && id.toLowerCase().includes(loose),\n );\n return {\n kind: candidate.kind,\n from: candidate.from,\n target: candidate.target,\n reason: 'unknown-anchor',\n origin: candidate.origin,\n declaredAt: candidate.declaredAt,\n raw: candidate.raw,\n candidates,\n };\n}\n\nfunction makeEdge(candidate: ReferenceCandidate, resolved: string): Edge {\n const from = candidate.inverted ? resolved : candidate.from;\n const to = candidate.inverted ? candidate.from : resolved;\n return {\n kind: candidate.kind,\n from,\n to,\n origin: candidate.origin,\n declaredAt: candidate.declaredAt,\n raw: candidate.raw,\n declaredIn: [candidate.declaredAt.file],\n reflexive: documentOf(from) === documentOf(to),\n };\n}\n\nfunction suppression(candidate: ReferenceCandidate, by: SuppressedRef['by']): SuppressedRef {\n return { target: candidate.target, from: candidate.from, at: candidate.declaredAt, by };\n}\n\nfunction dangle(\n candidate: ReferenceCandidate,\n reason: DanglingRef['reason'],\n candidates: readonly string[],\n): DanglingRef {\n return {\n kind: candidate.kind,\n from: candidate.from,\n target: candidate.target,\n reason,\n origin: candidate.origin,\n declaredAt: candidate.declaredAt,\n raw: candidate.raw,\n candidates,\n };\n}\n\n/** The document part of a node id: `ADR-7#q.1` -> `ADR-7`. */\nexport function documentOf(nodeId: string): string {\n const hash = nodeId.indexOf('#');\n return hash === -1 ? nodeId : nodeId.slice(0, hash);\n}\n"]}
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Corpus-level reference resolution - the foreign-key check.\n *\n * Extraction produces relations whose targets are still just text. This module\n * turns each into an edge or into an explicit, reportable failure. The whole\n * value of the exercise is in refusing to do either job carelessly:\n *\n * - **A deliberate reference that does not resolve is an error.** A link, a\n * front-matter field or a directive naming a document that does not exist is\n * a broken foreign key, and silence about it is how reference graphs rot.\n * - **An opportunistic reference that does not resolve is nothing.** `ADR-0007`\n * written in prose is a citation; `SHA-256` is not. Since the two are\n * indistinguishable in isolation, bare identifiers are only reported when\n * their family already exists in the corpus - so `ADR-0099` in a repository\n * full of ADRs is a finding, and `T-1000` in the same repository is not.\n * - **Two candidates is worse than none.** A reference that matches several\n * documents is reported as ambiguous rather than silently bound to whichever\n * one happened to be indexed first.\n */\n\nimport type { ExtractedDocument, ReferenceCandidate } from './extract.js';\nimport {\n isDocumentTarget,\n isExternal,\n looksLikePath,\n normaliseRef,\n parseBareRef,\n parsePrefixedRef,\n splitAnchor,\n withinOneEdit,\n} from './identity.js';\nimport { basenamePosix, dirnamePosix, extnamePosix, resolveFrom } from './paths.js';\nimport type {\n DanglingRef,\n DocumentNode,\n Edge,\n ItemNode,\n MisreadKey,\n ParseProblem,\n SpecNode,\n SuppressedRef,\n} from './types.js';\n\nexport interface ResolveOptions {\n /**\n * Whether a repository-relative path exists on disk.\n *\n * Used only to tell \"you linked to a file that is not a specification\" apart\n * from \"you linked to nothing at all\", which are different mistakes with\n * different fixes.\n */\n readonly fileExists?: ((path: string) => boolean) | undefined;\n /**\n * Targets whose failure to resolve should not be reported.\n *\n * This is a *suppression* filter, not a resolution one: it is consulted only\n * when a reference has already failed to resolve. A reference that resolves\n * still becomes an edge no matter what this says, so no amount of\n * configuration can silently delete a relation from the graph.\n */\n readonly isIgnoredReference?: ((target: string) => boolean) | undefined;\n /**\n * Families that are never citations in this repository.\n *\n * `RFC 2119` is the canonical case: every specification cites it, and a\n * repository that keeps its own `RFC-*` documents will otherwise read that\n * sentence as a dangling reference to a local RFC 2119 it does not have.\n * Consulted only after resolution has failed, like every other filter here.\n */\n readonly isIgnoredFamily?: ((family: string) => boolean) | undefined;\n}\n\nexport interface ResolvedCorpus {\n readonly nodes: ReadonlyMap<string, SpecNode>;\n readonly documents: readonly DocumentNode[];\n readonly items: readonly ItemNode[];\n readonly edges: readonly Edge[];\n readonly dangling: readonly DanglingRef[];\n readonly problems: readonly ParseProblem[];\n readonly misreadKeys: readonly MisreadKey[];\n /**\n * References this repository declared none of its business.\n *\n * Not findings, and never reported as such - they are the audit trail for\n * the two settings that can silence one. See ADR-0008.\n */\n readonly suppressed: readonly SuppressedRef[];\n}\n\n/**\n * Every set in these maps is created holding its first member, so an entry\n * that is there is never empty and a lookup asks only whether it is there.\n */\ninterface Index {\n readonly byId: Map<string, string>;\n readonly byAlias: Map<string, Set<string>>;\n /** The literal path of a file. Unique: no two files share one. */\n readonly byPath: Map<string, string>;\n /**\n * Spellings that address a file without naming it exactly - the extension\n * dropped, a directory standing for its README. Several files can answer to\n * one of those, so this is a set, exactly as `byAlias` is.\n */\n readonly byPathAlias: Map<string, Set<string>>;\n readonly byFamilyNumber: Map<string, Set<string>>;\n readonly families: Set<string>;\n /** Every file by the directory holding it, for a basename that was mistyped. */\n readonly byDirectory: Map<string, { stem: string; id: string }[]>;\n /**\n * Alias keys grouped by length.\n *\n * A one-edit match can only differ in length by one, so this turns the scan\n * behind a suggestion from the whole corpus into three buckets of it.\n */\n readonly aliasesByLength: Map<number, string[]>;\n readonly anchors: Map<string, ReadonlySet<string>>;\n readonly itemIds: Set<string>;\n readonly familyOf: Map<string, string | null>;\n}\n\n/** Resolves every extracted reference against the whole corpus. */\nexport function resolveCorpus(\n extracted: readonly ExtractedDocument[],\n options: ResolveOptions = {},\n): ResolvedCorpus {\n const index = buildIndex(extracted);\n const nodes = new Map<string, SpecNode>();\n const documents: DocumentNode[] = [];\n const items: ItemNode[] = [];\n const problems: ParseProblem[] = [];\n const misreadKeys: MisreadKey[] = [];\n\n for (const entry of extracted) {\n const existing = nodes.get(entry.document.id);\n if (existing && existing.kind === 'document') {\n problems.push({\n message: `duplicate document id \"${entry.document.id}\", already declared by ${existing.path}`,\n at: entry.document.at,\n });\n continue;\n }\n nodes.set(entry.document.id, entry.document);\n documents.push(entry.document);\n for (const item of entry.items) {\n // Nodes are keyed by id, so a second item would replace the first in the\n // graph while both were still counted, and one of them would vanish from\n // the export without a word.\n const taken = nodes.get(item.id);\n if (taken) {\n problems.push({\n message: `duplicate item id \"${item.id}\", already taken at ${taken.at.file}:${taken.at.span.start.line}`,\n at: item.at,\n });\n continue;\n }\n nodes.set(item.id, item);\n items.push(item);\n }\n problems.push(...entry.problems);\n misreadKeys.push(...entry.misreadKeys);\n }\n\n const edges: Edge[] = [];\n const dangling: DanglingRef[] = [];\n const suppressed: SuppressedRef[] = [];\n const byKey = new Map<string, number>();\n\n // Structural edges: a register contains the specifications written inside it.\n // A duplicate id leaves another file's document under that id, so the edge is\n // drawn only from this file's own document to a region of it the graph kept:\n // checked by presence alone, it said an unrelated file held the row. A\n // region's file is extracted before it, so its container's id is taken.\n for (const entry of extracted) {\n if (entry.containerId === null) continue;\n const container = nodes.get(entry.containerId) as SpecNode;\n if (container.at.file !== entry.document.at.file || nodes.get(entry.document.id) !== entry.document) continue;\n edges.push({\n kind: 'contains',\n from: entry.containerId,\n to: entry.document.id,\n origin: 'structural',\n declaredAt: entry.document.at,\n raw: entry.document.title,\n declaredIn: [entry.document.at.file],\n reflexive: false,\n });\n }\n\n // Structural edges: a document contains its items.\n for (const item of items) {\n edges.push({\n kind: 'contains',\n from: item.document,\n to: item.id,\n origin: 'structural',\n declaredAt: item.at,\n raw: item.text,\n declaredIn: [item.at.file],\n reflexive: false,\n });\n }\n\n for (const entry of extracted) {\n for (const candidate of entry.references) {\n if (!nodes.has(candidate.from)) continue;\n const outcome = resolveOne(candidate, entry, index, nodes, options);\n if (outcome === null) continue;\n if ('by' in outcome) {\n suppressed.push(outcome);\n continue;\n }\n if ('reason' in outcome) {\n dangling.push(outcome);\n continue;\n }\n // Collapse duplicates: the same relation stated twice is one edge - but\n // every declaration site is kept, because a supersession recorded in only\n // one of the two documents is itself a finding.\n const key = `${outcome.kind} ${outcome.from} ${outcome.to}`;\n const existing = byKey.get(key);\n if (existing !== undefined) {\n const previous = edges[existing] as Edge;\n if (!previous.declaredIn.includes(outcome.declaredAt.file)) {\n edges[existing] = { ...previous, declaredIn: [...previous.declaredIn, outcome.declaredAt.file] };\n }\n continue;\n }\n byKey.set(key, edges.length);\n edges.push(outcome);\n }\n }\n\n return { nodes, documents, items, edges, dangling, problems, misreadKeys, suppressed };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Indexing */\n/* -------------------------------------------------------------------------- */\n\nfunction buildIndex(extracted: readonly ExtractedDocument[]): Index {\n const index: Index = {\n byId: new Map(),\n byAlias: new Map(),\n byPath: new Map(),\n byPathAlias: new Map(),\n byFamilyNumber: new Map(),\n families: new Set(),\n byDirectory: new Map(),\n aliasesByLength: new Map(),\n anchors: new Map(),\n itemIds: new Set(),\n familyOf: new Map(),\n };\n\n for (const entry of extracted) {\n const id = entry.document.id;\n index.byId.set(normaliseRef(id), id);\n index.anchors.set(id, entry.anchors);\n index.familyOf.set(id, entry.identity.family);\n\n for (const alias of entry.document.aliases) {\n const set = index.byAlias.get(alias) ?? new Set<string>();\n set.add(id);\n index.byAlias.set(alias, set);\n }\n\n // Only a file answers to its path. A region keeps the path on its node, so a\n // finding can say where it lives and `document[path=...]` can select it, but\n // it must not be reachable *by* that path: two nodes answering to one path\n // makes every link to the file ambiguous, and this index resolves a\n // collision by keeping whichever was written last. A link to a file holding\n // a register bound to its final row, quietly and with no diagnostic, and\n // anchors were then checked against that row's span rather than the file's.\n // ADR-0009 said a region does not claim the file's path; this is the index\n // where that had to be true.\n if (entry.containerId === null) {\n const path = entry.document.path.toLowerCase();\n index.byPath.set(path, id);\n for (const spelling of pathAliases(entry.document.path)) {\n const set = index.byPathAlias.get(spelling) ?? new Set<string>();\n set.add(id);\n index.byPathAlias.set(spelling, set);\n }\n const directory = dirnamePosix(path);\n const siblings = index.byDirectory.get(directory) ?? [];\n siblings.push({ stem: stemOf(path), id });\n index.byDirectory.set(directory, siblings);\n }\n\n if (entry.identity.family !== null && entry.identity.number !== null) {\n index.families.add(entry.identity.family);\n const key = `${entry.identity.family}:${entry.identity.number}`;\n const set = index.byFamilyNumber.get(key) ?? new Set<string>();\n set.add(id);\n index.byFamilyNumber.set(key, set);\n }\n\n for (const item of entry.items) index.itemIds.add(item.id);\n }\n\n // A bucket seeded with anything but the empty list would hold a spelling no\n // document answers to, which a suggestion finds no id for and skips.\n for (const alias of index.byAlias.keys()) {\n const bucket = index.aliasesByLength.get(alias.length) ?? [];\n bucket.push(alias);\n index.aliasesByLength.set(alias.length, bucket);\n }\n\n return index;\n}\n\n/** A file name with its extension taken off: `docs/a/0004-sharding.md` -> `0004-sharding`. */\nfunction stemOf(path: string): string {\n const base = basenamePosix(path);\n return base.slice(0, base.length - extnamePosix(base).length);\n}\n\n/**\n * Every spelling of a path that addresses a file without naming it exactly.\n *\n * None of them is ever a file's own whole path, and one that is another file's\n * - the stem of `docs/a.md.md` is `docs/a.md` - cannot make a link to that file\n * ambiguous, because `lookupExact` asks for the literal path first.\n */\nfunction pathAliases(path: string): string[] {\n const keys = new Set<string>();\n const extension = extnamePosix(path);\n // Without an extension the stem is the path itself, which the literal path\n // already answers to first: adding it anyway changes no lookup.\n if (extension.length > 0) keys.add(path.slice(0, path.length - extension.length).toLowerCase());\n // `docs/adr/0007/README.md` is also addressed as `docs/adr/0007`.\n const base = basenamePosix(path);\n if (/^(readme|index)\\./i.test(base)) {\n // A repository-relative path never starts with `/`, so this is never\n // empty and the check decides nothing. At the root it is the name cut\n // short, `readme.m`: `.m` is not a document's extension, so only a link\n // that percent-escaped that dot would ever be looked up by it.\n const parent = path.slice(0, path.length - base.length - 1);\n if (parent.length > 0) keys.add(parent.toLowerCase());\n }\n return [...keys];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Resolution */\n/* -------------------------------------------------------------------------- */\n\nfunction resolveOne(\n candidate: ReferenceCandidate,\n entry: ExtractedDocument,\n index: Index,\n nodes: ReadonlyMap<string, SpecNode>,\n options: ResolveOptions,\n): Edge | DanglingRef | SuppressedRef | null {\n const { target: bare, anchor } = splitAnchor(candidate.target);\n\n // A pure `#anchor` points inside the citing document. Extraction never hands\n // over an empty target, so an empty part before the `#` always has an anchor\n // after it: the second test is for the type.\n if (bare.length === 0 && anchor !== null) {\n return bindAnchor(candidate, entry.document.id, anchor, index, nodes, options);\n }\n\n if (isExternal(bare)) return null;\n // A link to source code, an image or a config file is not a specification\n // reference. Validating it would report an error on the most ordinary thing a\n // design document does.\n if (!isDocumentTarget(bare)) return null;\n\n const found = lookup(bare, entry, index);\n\n if (found.ids.length === 0) {\n if (candidate.opportunistic && !worthReporting(bare, index)) return null;\n const silenced = filteredBy(candidate.target, bare, options);\n if (silenced !== null) return suppression(candidate, silenced);\n // A trailing slash names a directory. Linking to one is ordinary - \"the\n // decisions live in [archive/](archive/)\" - and is not a citation of any\n // document. Resolution is still attempted first, so a directory-style\n // document written as `0007-sharding/` continues to resolve.\n if (bare.endsWith('/')) return null;\n const reason = pathMissingReason(bare, entry, options);\n return dangle(candidate, reason, found.near);\n }\n\n if (found.ids.length > 1) {\n if (candidate.opportunistic) return null;\n const silenced = filteredBy(candidate.target, bare, options);\n if (silenced !== null) return suppression(candidate, silenced);\n return dangle(candidate, 'ambiguous', found.ids);\n }\n\n const documentId = found.ids[0] as string;\n\n // A document naming itself in prose - almost always its own H1, `# ADR-0007:\n // Sharding` - is not a citation. Deliberate self-links are kept, because a\n // link that points at its own document is a real mistake worth reporting.\n if (candidate.opportunistic && documentId === entry.document.id) return null;\n\n if (anchor !== null) return bindAnchor(candidate, documentId, anchor, index, nodes, options);\n\n return makeEdge(candidate, documentId);\n}\n\ninterface Lookup {\n readonly ids: readonly string[];\n /** Near-misses worth suggesting in the report. */\n readonly near: readonly string[];\n}\n\n/**\n * The same path with its percent-escapes read.\n *\n * `[Design](docs/my%20design.md)` is what a renderer, a documentation site and\n * GitHub's own \"copy link\" all produce for a file with a space in its name, and\n * it names a real file. Returns `null` when there was nothing to decode or when\n * the escapes are malformed - `100%` in a path is not an escape sequence, and\n * `decodeURIComponent` throws on it.\n */\nfunction percentDecoded(target: string): string | null {\n // Either test stands in for the other: with no `%` the decode gives the text\n // back unchanged, and with one a decode that succeeds always changes it.\n if (!target.includes('%')) return null;\n try {\n const decoded = decodeURIComponent(target);\n return decoded === target ? null : decoded;\n } catch {\n return null;\n }\n}\n\n/** `near` is read only when nothing resolved, so what the early returns put there is never read. */\nfunction lookup(target: string, entry: ExtractedDocument, index: Index): Lookup {\n const exact = lookupExact(target, entry, index);\n if (exact.length > 0) return { ids: exact, near: [] };\n // Tried second and never first, so a repository holding a file whose name\n // genuinely contains a percent escape keeps resolving by its written spelling.\n const decoded = percentDecoded(target);\n const relaxed = decoded === null ? [] : lookupExact(decoded, entry, index);\n if (relaxed.length > 0) return { ids: relaxed, near: [] };\n // Suggestions are computed only once resolution has failed outright, so their\n // cost is bounded by the number of findings rather than by the corpus.\n return { ids: [], near: nearMisses(target, entry, index) };\n}\n\nfunction lookupExact(target: string, entry: ExtractedDocument, index: Index): readonly string[] {\n if (looksLikePath(target)) {\n const resolved = resolveFrom(entry.document.path, target).toLowerCase();\n // Naming the file exactly is never ambiguous, whatever else is spelled the\n // same way.\n const byPath = index.byPath.get(resolved);\n if (byPath) return [byPath];\n // An inexact spelling can belong to more than one file: `docs/A` is both\n // `docs/A.md` with its extension dropped and `docs/A/README.md` standing\n // for its directory. Handing back every claimant makes that an\n // ambiguous-reference the author can settle, rather than a silent pick of\n // whichever happened to be indexed last.\n const spelled = index.byPathAlias.get(resolved);\n if (spelled) return [...spelled];\n // A path may still be spelled as an identifier in a nested folder layout.\n const stem = basenamePosix(resolved);\n const byStem = index.byAlias.get(normaliseRef(stem.replace(/\\.[^.]+$/, '')));\n if (byStem) return [...byStem];\n return [];\n }\n\n const key = normaliseRef(target);\n if (key.length === 0) return [];\n\n const byId = index.byId.get(key);\n if (byId) return [byId];\n\n const byAlias = index.byAlias.get(key);\n if (byAlias) return [...byAlias];\n\n const prefixed = parsePrefixedRef(target);\n if (prefixed) {\n const byFamily = index.byFamilyNumber.get(`${prefixed.family}:${prefixed.number}`);\n if (byFamily) return [...byFamily];\n return [];\n }\n\n // A bare number resolves only inside the citing document's own family. A\n // repository with both `adr/0007` and `rfc/0007` is ordinary, and guessing\n // between them would be worse than reporting nothing.\n // Without a number, or without a family, the key names no entry either, so\n // the two tests that return early save a lookup and decide nothing alone.\n const number = parseBareRef(target);\n if (number !== null) {\n const family = index.familyOf.get(entry.document.id) ?? null;\n if (family === null) return [];\n const byFamily = index.byFamilyNumber.get(`${family}:${number}`);\n if (byFamily) return [...byFamily];\n }\n\n return [];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Near misses */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The one document a failed reference was probably meant to name.\n *\n * A suggestion is cheap to read and expensive to get wrong: \"did you mean\n * ADR-0008?\" against a citation that meant ADR-0012 sends a reader to the wrong\n * decision with a confidence nobody earned. So the bar is not closeness, it is\n * closeness *in the part of the spelling that is not the identity*. A family\n * name is a word people misremember. A number is the identity itself, and\n * `ADR-0009` sits one edit from every neighbour it has - so numbers are never\n * guessed at, and a repository of fifteen ADRs citing a sixteenth is told the\n * plain truth instead.\n *\n * Two gates, plus a fallback:\n *\n * - a path in the right directory whose basename is one edit out - a typo;\n * - a family one edit out with its number intact - `ARD-0009` for `ADR-0009`.\n *\n * Anything else falls through to a one-edit match against the folded spellings\n * documents already answer to, which is where a mistyped slug or wiki name\n * lands.\n *\n * There is deliberately no gate for a file that moved. Resolution already binds\n * a path by its basename, so `../guides/onboarding.md` finds the document that\n * is now in `handbook/` without anybody being asked to confirm a guess - and a\n * suggestion nobody needs is a suggestion that can only be wrong.\n *\n * Every gate ends the same way: **exactly one** candidate, or nothing. Two\n * suggestions is the ambiguity this module refuses to resolve anywhere else,\n * and the tail of a hint is no place to start.\n */\nfunction nearMisses(target: string, entry: ExtractedDocument, index: Index): string[] {\n if (looksLikePath(target)) {\n const resolved = resolveFrom(entry.document.path, target).toLowerCase();\n const stem = stemOf(resolved);\n const siblings = index.byDirectory.get(dirnamePosix(resolved)) ?? [];\n return sole(siblings.filter((file) => withinOneEdit(file.stem, stem)).map((file) => file.id));\n }\n\n const prefixed = parsePrefixedRef(target);\n if (prefixed !== null) {\n const hits: string[] = [];\n for (const family of index.families) {\n // The family as written needs no exclusion: one edit is never zero edits.\n if (!withinOneEdit(family.toLowerCase(), prefixed.family.toLowerCase())) continue;\n for (const id of index.byFamilyNumber.get(`${family}:${prefixed.number}`) ?? []) hits.push(id);\n }\n return sole(hits);\n }\n\n // A bare `12` carries nothing but a number, which is the one thing this\n // refuses to guess at.\n if (parseBareRef(target) !== null) return [];\n\n const key = normaliseRef(target);\n if (key.length < MIN_SUGGESTIBLE) return [];\n const hits: string[] = [];\n // Every bucketed spelling is a key of `byAlias`, so its `?? []` is for the\n // type; and a spelling in place of an empty bucket names no document here.\n for (const length of [key.length - 1, key.length, key.length + 1]) {\n for (const alias of index.aliasesByLength.get(length) ?? []) {\n if (!withinOneEdit(alias, key)) continue;\n for (const id of index.byAlias.get(alias) ?? []) hits.push(id);\n }\n }\n return sole(hits);\n}\n\n/**\n * How long a folded spelling has to be before one edit means anything.\n *\n * Below six characters an edit is most of the word, and every corpus is full of\n * short names that differ by exactly that much.\n */\nconst MIN_SUGGESTIBLE = 6;\n\n/** The candidates, if there is exactly one of them. Otherwise nothing. */\nfunction sole(ids: Iterable<string>): string[] {\n const unique = new Set(ids);\n return unique.size === 1 ? [...unique] : [];\n}\n\n/**\n * Which setting, if any, declared this reference none of the repository's\n * business.\n *\n * Returns the mechanism rather than a boolean so the audit trail can say which\n * line of configuration to go and look at. A team that silenced eight things\n * with one glob and one with a family needs to be told them apart.\n */\nfunction filteredBy(target: string, bare: string, options: ResolveOptions): SuppressedRef['by'] | null {\n if (options.isIgnoredReference?.(target)) return 'reference';\n const prefixed = parsePrefixedRef(bare);\n if (prefixed !== null && options.isIgnoredFamily?.(prefixed.family) === true) return 'family';\n return null;\n}\n\n/**\n * Whether an unresolved bare identifier deserves a report.\n *\n * Only when the corpus already contains that family. `ADR-0099` in a repository\n * of ADRs is a real dangling citation; `T-1000` in the same repository is a\n * sentence about a robot.\n */\nfunction worthReporting(target: string, index: Index): boolean {\n const prefixed = parsePrefixedRef(target);\n return prefixed !== null && index.families.has(prefixed.family);\n}\n\nfunction pathMissingReason(\n target: string,\n entry: ExtractedDocument,\n options: ResolveOptions,\n): DanglingRef['reason'] {\n if (!looksLikePath(target) || !options.fileExists) return 'unknown-target';\n const resolved = resolveFrom(entry.document.path, target);\n return options.fileExists(resolved) ? 'not-a-spec' : 'unknown-target';\n}\n\n/**\n * Binds a `#anchor` to an item or a section.\n *\n * Anchors are how a delegation names a specific obligation rather than a whole\n * document, so resolving them precisely is what lets a ghost-handover finding\n * say *which* question was handed into the archive.\n */\nfunction bindAnchor(\n candidate: ReferenceCandidate,\n documentId: string,\n anchor: string,\n index: Index,\n nodes: ReadonlyMap<string, SpecNode>,\n options: ResolveOptions,\n): Edge | DanglingRef | SuppressedRef | null {\n const itemId = `${documentId}#${anchor}`;\n if (index.itemIds.has(itemId)) return makeEdge(candidate, itemId);\n\n // Every document, a file or a region of one, registered its anchors, and a\n // document id here is always one of theirs.\n const anchors = index.anchors.get(documentId) as ReadonlySet<string>;\n if (anchors.has(anchor.toLowerCase())) return makeEdge(candidate, documentId);\n\n // Some renderers slugify differently; try the loosest reasonable match before\n // calling it broken.\n const loose = anchor.toLowerCase().replace(/[^\\p{L}\\p{N}]+/gu, '-').replace(/^-|-$/g, '');\n if (anchors.has(loose)) return makeEdge(candidate, documentId);\n\n const silenced = filteredBy(candidate.target, splitAnchor(candidate.target).target, options);\n if (silenced !== null) return suppression(candidate, silenced);\n\n const candidates = [...(nodes.keys() as Iterable<string>)].filter(\n (id) => id.startsWith(`${documentId}#`) && id.toLowerCase().includes(loose),\n );\n return {\n kind: candidate.kind,\n from: candidate.from,\n target: candidate.target,\n reason: 'unknown-anchor',\n origin: candidate.origin,\n declaredAt: candidate.declaredAt,\n raw: candidate.raw,\n candidates,\n };\n}\n\nfunction makeEdge(candidate: ReferenceCandidate, resolved: string): Edge {\n const from = candidate.inverted ? resolved : candidate.from;\n const to = candidate.inverted ? candidate.from : resolved;\n return {\n kind: candidate.kind,\n from,\n to,\n origin: candidate.origin,\n declaredAt: candidate.declaredAt,\n raw: candidate.raw,\n declaredIn: [candidate.declaredAt.file],\n reflexive: documentOf(from) === documentOf(to),\n };\n}\n\nfunction suppression(candidate: ReferenceCandidate, by: SuppressedRef['by']): SuppressedRef {\n return { target: candidate.target, from: candidate.from, at: candidate.declaredAt, by };\n}\n\nfunction dangle(\n candidate: ReferenceCandidate,\n reason: DanglingRef['reason'],\n candidates: readonly string[],\n): DanglingRef {\n return {\n kind: candidate.kind,\n from: candidate.from,\n target: candidate.target,\n reason,\n origin: candidate.origin,\n declaredAt: candidate.declaredAt,\n raw: candidate.raw,\n candidates,\n };\n}\n\n/** The document part of a node id: `ADR-7#q.1` -> `ADR-7`. */\nexport function documentOf(nodeId: string): string {\n const hash = nodeId.indexOf('#');\n return hash === -1 ? nodeId : nodeId.slice(0, hash);\n}\n"]}
package/dist/rules.d.ts CHANGED
@@ -40,6 +40,14 @@ export declare const RULE_DESCRIPTIONS: Readonly<Record<RuleId, string>>;
40
40
  * been told their document is inconsistent finds out why anybody decided that.
41
41
  */
42
42
  export declare const RULE_DECISIONS: Readonly<Record<RuleId, string>>;
43
+ /**
44
+ * Where a reader opens the ADR a rule's decision is written in. The paths in
45
+ * `RULE_DECISIONS` are the repository's - selfcheck reads them, and a rename
46
+ * breaks the pointer in the same run - but no package ships `docs/`, so a path
47
+ * printed to somebody who installed spec-graph named nothing they had. The tag
48
+ * of the release they run is the ADR as it stood for the rules they got.
49
+ */
50
+ export declare function decisionUrl(path: string, version: string): string;
43
51
  /**
44
52
  * The severity map `--strict` implies, and which rules it actually moved.
45
53
  *
package/dist/rules.js CHANGED
@@ -19,7 +19,7 @@
19
19
  * For a supersession that the superseded document never acknowledged, that is
20
20
  * the superseded document, not the one that declared the relation.
21
21
  */
22
- import { OBLIGATION_EDGES } from './graph.js';
22
+ import { buildGraph, OBLIGATION_EDGES } from './graph.js';
23
23
  import { dirnamePosix, resolveFrom } from './paths.js';
24
24
  import { renderTemplate } from './project-rules.js';
25
25
  import { execute, parseQuery } from './select.js';
@@ -83,6 +83,16 @@ export const RULE_DECISIONS = Object.freeze({
83
83
  'self-reference': 'docs/adr/0006-false-positives-cost-more.md',
84
84
  'unknown-relation-key': 'docs/adr/0014-a-relation-is-spelled-both-ways.md',
85
85
  });
86
+ /**
87
+ * Where a reader opens the ADR a rule's decision is written in. The paths in
88
+ * `RULE_DECISIONS` are the repository's - selfcheck reads them, and a rename
89
+ * breaks the pointer in the same run - but no package ships `docs/`, so a path
90
+ * printed to somebody who installed spec-graph named nothing they had. The tag
91
+ * of the release they run is the ADR as it stood for the rules they got.
92
+ */
93
+ export function decisionUrl(path, version) {
94
+ return `https://github.com/DescentVTT/spec-graph/blob/v${version}/${path}`;
95
+ }
86
96
  /**
87
97
  * The severity map `--strict` implies, and which rules it actually moved.
88
98
  *
@@ -103,7 +113,9 @@ export function resolveStrict(overrides = {}, strict = false, projectRules = [])
103
113
  return { severities, escalated };
104
114
  // A project rule is escalated on the same terms as a built-in, reading its
105
115
  // declared severity as the default. Anything else would mean a team could
106
- // adopt --strict and quietly keep their own conventions advisory.
116
+ // adopt --strict and quietly keep their own conventions advisory. (A default
117
+ // list seeded with something that is not a rule is an equivalent mutant: with
118
+ // no severity, it is never a warning to raise.)
107
119
  const defaults = [
108
120
  ...RULE_IDS.map((id) => [id, DEFAULT_SEVERITIES[id]]),
109
121
  ...projectRules.map((rule) => [rule.id, rule.severity]),
@@ -157,7 +169,7 @@ export function runRules(graph, corpus, options = {}) {
157
169
  stalePremises(graph, emit, claimed);
158
170
  brokenReferences(corpus, emit);
159
171
  circularDelegations(graph, emit);
160
- orphanedObligations(graph, emit, maxRelated);
172
+ orphanedObligations(graph, emit);
161
173
  supersessions(graph, emit);
162
174
  stateConflicts(graph, emit);
163
175
  misreadKeys(corpus, emit);
@@ -204,6 +216,9 @@ const REFERENCE_RULES = new Set([
204
216
  function exempt(graph, rule, nodes) {
205
217
  if (REFERENCE_RULES.has(rule))
206
218
  return false;
219
+ // Every other rule names its subject first, and names a node of the graph,
220
+ // which has an owning document: the two guards below narrow types, and their
221
+ // mutants are equivalent.
207
222
  const subject = nodes[0];
208
223
  if (subject === undefined)
209
224
  return false;
@@ -236,6 +251,8 @@ function ghostHandovers(graph, emit) {
236
251
  const source = match.nodes[0];
237
252
  const target = match.nodes[1];
238
253
  const edge = match.edges[0];
254
+ // A node always has an owning document; the guard narrows the type, and its
255
+ // mutant is equivalent.
239
256
  const targetDocument = graph.owningDocument(target.id);
240
257
  if (!targetDocument)
241
258
  continue;
@@ -301,6 +318,10 @@ function stalePremises(graph, emit, claimed) {
301
318
  // terms the reader needs.
302
319
  if (claimed.has(edgeKey(edge)))
303
320
  continue;
321
+ // `execute` already returns one path per pair of nodes, and the two
322
+ // selectors reach different kinds of node, so this never skips today and
323
+ // its mutants are equivalent. It keeps one finding per premise if either
324
+ // of those stops being true.
304
325
  const key = `${source.id}>${target.id}>${edge.kind}`;
305
326
  if (seen.has(key))
306
327
  continue;
@@ -315,7 +336,8 @@ function stalePremises(graph, emit, claimed) {
315
336
  nodes: [source.id, target.id],
316
337
  // Point at the status line when there is one: that is the sentence that
317
338
  // makes the premise stale, and repeating the document's first line
318
- // underneath it adds nothing.
339
+ // underneath it adds nothing. A document target owns itself, so the
340
+ // optional chain is never taken.
319
341
  related: [related(target.kind === 'document' ? (targetDocument?.statusAt ?? target.at) : target.at, because)],
320
342
  hint: `re-check this dependency: the constraint it assumes may have been lifted when ${target.kind === 'item' ? 'the question closed' : `${target.id} was retired`}`,
321
343
  }));
@@ -412,13 +434,17 @@ function brokenHint(ref) {
412
434
  *
413
435
  * `trap 55` suggests `trap *` rather than `trap 55`, because these tags come in
414
436
  * numbered series and excluding them one at a time is not a fix anybody would
415
- * accept. A target with no trailing number is suggested verbatim.
437
+ * accept. The separator stays in the glob - `trap-55` suggests `trap-*` - or
438
+ * the flag the hint names would not silence the finding it is attached to. A
439
+ * target with no trailing number, or nothing before it, is suggested verbatim.
416
440
  */
417
441
  function suggestReferenceGlob(target) {
442
+ // The scanner hands over a wiki link's target trimmed already, so the trim's
443
+ // mutant is equivalent; and unanchored at the start the pattern would match
444
+ // the same, because the lazy stem can always begin at the first character.
418
445
  const trimmed = target.trim();
419
- const series = /^(.*?)[\s._-]*\d+$/.exec(trimmed);
420
- const stem = series?.[1]?.trim();
421
- return stem !== undefined && stem.length > 0 ? `${stem} *` : trimmed;
446
+ const stem = /^(.*?[\s._-]*)\d+$/.exec(trimmed)?.[1];
447
+ return stem !== undefined && /[^\s._-]/.test(stem) ? `${stem}*` : trimmed;
422
448
  }
423
449
  /**
424
450
  * The narrowest include pattern that would reach a target the corpus missed.
@@ -438,10 +464,15 @@ function suggestPattern(ref) {
438
464
  /* -------------------------------------------------------------------------- */
439
465
  function circularDelegations(graph, emit) {
440
466
  const kinds = [...OBLIGATION_EDGES, 'supersedes'];
467
+ const searched = withoutHandOffs(graph);
441
468
  // Projected onto documents, so a question handed back and forth between two
442
469
  // of them is found even though each delegation runs item -> document and the
443
470
  // raw graph therefore contains no cycle at all.
444
- for (const component of graph.cycles(kinds, { byDocument: true })) {
471
+ for (const component of searched.cycles(kinds, { byDocument: true })) {
472
+ // A projected component is two documents or more, each with the edge that
473
+ // holds it in: the filter, the length guard and the empty-edges cases below
474
+ // never decide, and their mutants are equivalent. They keep the rule true
475
+ // if `cycles` ever returns less.
445
476
  const members = component
446
477
  .map((id) => graph.document(id))
447
478
  .filter((node) => node !== undefined);
@@ -453,7 +484,7 @@ function circularDelegations(graph, emit) {
453
484
  // is not a log: its half of the loop is a claim it still makes.
454
485
  if (members.some((member) => member.history))
455
486
  continue;
456
- const edges = cycleEdges(graph, component, kinds);
487
+ const edges = cycleEdges(searched, component, kinds);
457
488
  const onlySupersession = edges.length > 0 && edges.every((edge) => edge.kind === 'supersedes');
458
489
  const head = members[0];
459
490
  emit('circular-delegation', () => ({
@@ -467,6 +498,34 @@ function circularDelegations(graph, emit) {
467
498
  }));
468
499
  }
469
500
  }
501
+ /**
502
+ * The graph without the questions a superseded document handed to its successor.
503
+ *
504
+ * `ADR-0002 supersedes ADR-0001`, and ADR-0001 defers its open question to
505
+ * ADR-0002: the question went to the decision that replaced the one asking it,
506
+ * which is the hand-off a supersession is for. Projected onto documents the
507
+ * two relations make a loop, and nothing in it is passed back - so it is not
508
+ * searched for one (ADR-0006). Two documents passing a question back and forth
509
+ * still close a cycle, and so does a question handed to any document other
510
+ * than one that replaced the document handing it.
511
+ */
512
+ function withoutHandOffs(graph) {
513
+ // Both ends of an edge are nodes, so the fallback is never taken.
514
+ const owner = (id) => graph.owningDocument(id)?.id ?? id;
515
+ const successors = new Map();
516
+ for (const edge of graph.edges) {
517
+ if (edge.kind !== 'supersedes')
518
+ continue;
519
+ const replaced = owner(edge.to);
520
+ const set = successors.get(replaced) ?? new Set();
521
+ set.add(owner(edge.from));
522
+ successors.set(replaced, set);
523
+ }
524
+ const kept = graph.edges.filter((edge) => !(OBLIGATION_EDGES.includes(edge.kind) && successors.get(owner(edge.from))?.has(owner(edge.to))));
525
+ // Rebuilding a graph with every edge kept gives back the same graph, so the
526
+ // comparison only saves the work: its mutants are equivalent.
527
+ return kept.length === graph.edges.length ? graph : buildGraph({ nodes: graph.nodes, edges: kept });
528
+ }
470
529
  /**
471
530
  * The relations that keep a component strongly connected.
472
531
  *
@@ -476,6 +535,7 @@ function circularDelegations(graph, emit) {
476
535
  */
477
536
  function cycleEdges(graph, component, kinds) {
478
537
  const inside = new Set(component);
538
+ // Both ends of an edge are nodes, so the fallback is never taken.
479
539
  const owner = (id) => graph.owningDocument(id)?.id ?? id;
480
540
  const out = [];
481
541
  for (const edge of graph.edges) {
@@ -498,7 +558,7 @@ function cycleEdges(graph, component, kinds) {
498
558
  * say "this archived ADR has open questions" is five times the noise and none
499
559
  * of the extra information.
500
560
  */
501
- function orphanedObligations(graph, emit, maxRelated) {
561
+ function orphanedObligations(graph, emit) {
502
562
  for (const document of graph.documents) {
503
563
  if (document.phase !== 'retired' && document.phase !== 'frozen')
504
564
  continue;
@@ -510,9 +570,8 @@ function orphanedObligations(graph, emit, maxRelated) {
510
570
  message: `${sealed} document still holds ${open.length} open ${open.length === 1 ? 'obligation' : 'obligations'}`,
511
571
  at: document.statusAt ?? document.at,
512
572
  nodes: [document.id, ...open.map((item) => item.id)],
513
- related: open
514
- .slice(0, maxRelated)
515
- .map((item) => related(item.at, `${item.disposition}: ${item.text}`)),
573
+ // Every item, where `emit` applies the cap on related locations.
574
+ related: open.map((item) => related(item.at, `${item.disposition}: ${item.text}`)),
516
575
  hint: `move each one to a live document or close it - as it stands, ${open.length === 1 ? 'it disappears' : 'they disappear'} with ${document.id}`,
517
576
  }));
518
577
  }
@@ -579,7 +638,9 @@ function stateConflicts(graph, emit) {
579
638
  message: `item reads as ${item.disposition} but also carries ${describeSignals(item)}`,
580
639
  at: item.evidence.at,
581
640
  nodes: [item.id],
582
- related: item.conflicts.map((signal) => related(signal.at, `${signal.source} says ${signal.disposition}${signal.raw ? `: ${signal.raw}` : ''}`)),
641
+ // A conflicting signal always has text: only a section's and the default
642
+ // can be empty, and neither is ever in conflict.
643
+ related: item.conflicts.map((signal) => related(signal.at, `${signal.source} says ${signal.disposition}: ${signal.raw}`)),
583
644
  hint: `make the two agree - spec-graph is treating it as ${item.openness}`,
584
645
  }));
585
646
  }
@@ -600,11 +661,14 @@ function describeSignals(item) {
600
661
  */
601
662
  function selfReferences(graph, emit) {
602
663
  for (const edge of graph.edges) {
603
- if (!edge.reflexive || edge.kind === 'contains')
664
+ if (!edge.reflexive)
604
665
  continue;
666
+ // `contains`, from a document to its own item, is reflexive and neither.
605
667
  const traits = EDGE_TRAITS[edge.kind];
606
668
  if (!traits.transfersObligation && !traits.loadBearing)
607
669
  continue;
670
+ // The graph keeps no edge whose ends are not nodes; the guard narrows the
671
+ // type, and its mutant is equivalent.
608
672
  const node = graph.node(edge.from);
609
673
  if (!node)
610
674
  continue;
@@ -669,6 +733,7 @@ function describe(node) {
669
733
  function truncate(text, limit) {
670
734
  return text.length <= limit ? text : `${text.slice(0, limit - 3)}...`;
671
735
  }
736
+ /** Every caller has the document in hand, so the optional chain is never taken. */
672
737
  function statusSuffix(document) {
673
738
  return document?.rawStatus ? ` ("${document.rawStatus}")` : '';
674
739
  }
@@ -687,6 +752,8 @@ export function sortDiagnostics(diagnostics) {
687
752
  const bySeverity = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity];
688
753
  if (bySeverity !== 0)
689
754
  return bySeverity;
755
+ // A string comparison is reached only when the two differ, where `<` and
756
+ // `<=` agree: those mutants are equivalent.
690
757
  if (a.at.file !== b.at.file)
691
758
  return a.at.file < b.at.file ? -1 : 1;
692
759
  if (a.at.span.start.line !== b.at.span.start.line)