@descent-vtt/spec-graph 0.9.3 → 0.9.4
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.
- package/CHANGELOG.md +28 -0
- package/README.md +7 -5
- package/dist/resolve.d.ts +8 -0
- package/dist/resolve.js +9 -2
- package/dist/resolve.js.map +1 -1
- package/dist/rules.js +34 -14
- package/dist/rules.js.map +1 -1
- package/dist/runner.d.ts +1 -0
- package/dist/runner.js +3 -1
- package/dist/runner.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,34 @@
|
|
|
3
3
|
Notable changes, newest first. Versions follow [semver](https://semver.org):
|
|
4
4
|
a patch fixes behaviour without asking anything of a repository that upgrades.
|
|
5
5
|
|
|
6
|
+
## 0.9.4
|
|
7
|
+
|
|
8
|
+
Three false alarms, each raised in review of 0.9.3's changes: a loop that
|
|
9
|
+
takes a supersession to close read as a delegation cycle, a question handed
|
|
10
|
+
on to a live document read as an orphaned obligation, and a link to a
|
|
11
|
+
directory without its trailing slash read as a reference outside the corpus.
|
|
12
|
+
On upgrading, expect those findings to go, and a finding for each kind
|
|
13
|
+
where one cycle held a loop of obligations and a loop of supersessions
|
|
14
|
+
together; under `--ratchet`, record the baseline once.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **A loop that takes a supersession to close is not a delegation cycle.** A
|
|
19
|
+
document superseded twice over handing its question to the latest successor,
|
|
20
|
+
or a superseded document handing its question to a third that hands it on to
|
|
21
|
+
the successor, read as `delegation cycle ... nothing in it can ever land`.
|
|
22
|
+
The question lands on the successor. Obligations and supersessions are now
|
|
23
|
+
searched for cycles apart, and each finding names only its own relations.
|
|
24
|
+
- **A question a retired or frozen document delegated to a live document is
|
|
25
|
+
not an orphaned obligation.** It was counted as open work the document still
|
|
26
|
+
held, though the finding's hint - move it to a live document - had been
|
|
27
|
+
followed. Delegated into another sealed document, it is still counted.
|
|
28
|
+
- **A link to a directory without its trailing slash is not a reference.**
|
|
29
|
+
`[the guides](../guides)` warned that it "resolves to a file that is not a
|
|
30
|
+
specification" and suggested include patterns that could not help, while
|
|
31
|
+
`../guides/` was quiet. `analyseSources` takes an `isDirectory` option for
|
|
32
|
+
this; without it a directory link is a broken reference as before.
|
|
33
|
+
|
|
6
34
|
## 0.9.3
|
|
7
35
|
|
|
8
36
|
Thirteen defects, found by reading the mutation survivors of nineteen
|
package/README.md
CHANGED
|
@@ -71,8 +71,8 @@ npx @descent-vtt/spec-graph "docs/**/*.md"
|
|
|
71
71
|
| `ghost-handover` | error | An open obligation handed to a document that can no longer absorb it |
|
|
72
72
|
| `stale-premise` | error | A live document resting on a decision that was retired or obviated |
|
|
73
73
|
| `broken-reference` | error | A citation naming a document or anchor that does not exist |
|
|
74
|
-
| `circular-delegation` | error | Obligations
|
|
75
|
-
| `orphaned-obligation` | error | A retired or frozen document still holding open work |
|
|
74
|
+
| `circular-delegation` | error | Obligations in a cycle, so none can ever land, or supersessions in a cycle - not a loop that takes one of each to close, such as a superseded document handing its question to what replaced it |
|
|
75
|
+
| `orphaned-obligation` | error | A retired or frozen document still holding open work - not a question it delegated to a document still taking work |
|
|
76
76
|
| `live-supersession` | error | A superseded document still presenting itself as current |
|
|
77
77
|
| `reference-outside-corpus` | warn | A citation naming a real document the include patterns did not reach |
|
|
78
78
|
| `ambiguous-reference` | warn | A citation matching more than one document |
|
|
@@ -286,7 +286,9 @@ trusts:
|
|
|
286
286
|
Nor is a link to any other file that is not a document: an image, a dotfile
|
|
287
287
|
such as `../.nvmrc`, or a drawing such as `arch.excalidraw`, however long its
|
|
288
288
|
extension. A dot followed by a digit, as in `ADR-0007.1`, is part of an
|
|
289
|
-
identifier and is still resolved.
|
|
289
|
+
identifier and is still resolved. A link to a directory is not a reference
|
|
290
|
+
either, `../guides/` or `../guides` alike - without the slash the disk says
|
|
291
|
+
which it is, and a name with no slash at all, `guides`, is an identifier.
|
|
290
292
|
|
|
291
293
|
### Registers: many specifications in one file
|
|
292
294
|
|
|
@@ -1099,9 +1101,9 @@ tools share, whose code is copied into `src/vendor/` and checked by hash rather
|
|
|
1099
1101
|
than installed. So the whole package is auditable in an afternoon — and nothing
|
|
1100
1102
|
in the pipeline can take longer than its input.
|
|
1101
1103
|
|
|
1102
|
-
**Verified, not just covered.** 1,
|
|
1104
|
+
**Verified, not just covered.** 1,776 tests; 99.3% statement and 99.7% line
|
|
1103
1105
|
coverage. Coverage says a line ran, so the suite is also held to a mutation
|
|
1104
|
-
score - 96.
|
|
1106
|
+
score - 96.34% over 8,242 mutants, measured by CI's full sweep, and a build
|
|
1105
1107
|
fails under 93 - because a vocabulary entry or a boundary
|
|
1106
1108
|
condition can be weakened by an ordinary-looking refactor without a single test
|
|
1107
1109
|
going red. [ADR-0007](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0007-mutation-testing.md) is straight about what
|
package/dist/resolve.d.ts
CHANGED
|
@@ -28,6 +28,14 @@ export interface ResolveOptions {
|
|
|
28
28
|
* different fixes.
|
|
29
29
|
*/
|
|
30
30
|
readonly fileExists?: ((path: string) => boolean) | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Whether a repository-relative path is a directory on disk.
|
|
33
|
+
*
|
|
34
|
+
* A link to a directory cites no document, written with a trailing slash or
|
|
35
|
+
* without one: `[the guides](../guides)` names a directory as surely as
|
|
36
|
+
* `[the guides](../guides/)` does.
|
|
37
|
+
*/
|
|
38
|
+
readonly isDirectory?: ((path: string) => boolean) | undefined;
|
|
31
39
|
/**
|
|
32
40
|
* Targets whose failure to resolve should not be reported.
|
|
33
41
|
*
|
package/dist/resolve.js
CHANGED
|
@@ -256,8 +256,9 @@ function resolveOne(candidate, entry, index, nodes, options) {
|
|
|
256
256
|
// A trailing slash names a directory. Linking to one is ordinary - "the
|
|
257
257
|
// decisions live in [archive/](archive/)" - and is not a citation of any
|
|
258
258
|
// document. Resolution is still attempted first, so a directory-style
|
|
259
|
-
// document written as `0007-sharding/` continues to resolve.
|
|
260
|
-
|
|
259
|
+
// document written as `0007-sharding/` continues to resolve. Without the
|
|
260
|
+
// slash, the disk says whether the path is a directory.
|
|
261
|
+
if (bare.endsWith('/') || isDirectoryPath(bare, entry, options))
|
|
261
262
|
return null;
|
|
262
263
|
const reason = pathMissingReason(bare, entry, options);
|
|
263
264
|
return dangle(candidate, reason, found.near);
|
|
@@ -483,6 +484,12 @@ function worthReporting(target, index) {
|
|
|
483
484
|
const prefixed = parsePrefixedRef(target);
|
|
484
485
|
return prefixed !== null && index.families.has(prefixed.family);
|
|
485
486
|
}
|
|
487
|
+
/** True when a path-like target names a directory, as `../guides` can. */
|
|
488
|
+
function isDirectoryPath(target, entry, options) {
|
|
489
|
+
if (!looksLikePath(target) || !options.isDirectory)
|
|
490
|
+
return false;
|
|
491
|
+
return options.isDirectory(resolveFrom(entry.document.path, target));
|
|
492
|
+
}
|
|
486
493
|
function pathMissingReason(target, entry, options) {
|
|
487
494
|
if (!looksLikePath(target) || !options.fileExists)
|
|
488
495
|
return 'unknown-target';
|
package/dist/resolve.js.map
CHANGED
|
@@ -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\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"]}
|
|
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 * Whether a repository-relative path is a directory on disk.\n *\n * A link to a directory cites no document, written with a trailing slash or\n * without one: `[the guides](../guides)` names a directory as surely as\n * `[the guides](../guides/)` does.\n */\n readonly isDirectory?: ((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. Without the\n // slash, the disk says whether the path is a directory.\n if (bare.endsWith('/') || isDirectoryPath(bare, entry, options)) 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\n/** True when a path-like target names a directory, as `../guides` can. */\nfunction isDirectoryPath(target: string, entry: ExtractedDocument, options: ResolveOptions): boolean {\n if (!looksLikePath(target) || !options.isDirectory) return false;\n return options.isDirectory(resolveFrom(entry.document.path, target));\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.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* the superseded document, not the one that declared the relation.
|
|
21
21
|
*/
|
|
22
22
|
import { buildGraph, OBLIGATION_EDGES } from './graph.js';
|
|
23
|
+
import { receptivityOf } from './lifecycle.js';
|
|
23
24
|
import { dirnamePosix, resolveFrom } from './paths.js';
|
|
24
25
|
import { renderTemplate } from './project-rules.js';
|
|
25
26
|
import { execute, parseQuery } from './select.js';
|
|
@@ -49,7 +50,7 @@ export const RULE_DESCRIPTIONS = Object.freeze({
|
|
|
49
50
|
'broken-reference': 'a citation names a document or anchor that does not exist',
|
|
50
51
|
'reference-outside-corpus': 'a citation names a real document the include patterns did not reach',
|
|
51
52
|
'ambiguous-reference': 'a citation matches more than one document',
|
|
52
|
-
'circular-delegation': 'obligations
|
|
53
|
+
'circular-delegation': 'obligations form a cycle, so none of them can ever land, or supersessions do',
|
|
53
54
|
'orphaned-obligation': 'a retired or frozen document still holds open obligations',
|
|
54
55
|
'live-supersession': 'a document has been superseded but still presents itself as current',
|
|
55
56
|
'unreciprocated-supersession': 'a retired or archived document does not say what replaced it',
|
|
@@ -463,8 +464,15 @@ function suggestPattern(ref) {
|
|
|
463
464
|
/* Cycles */
|
|
464
465
|
/* -------------------------------------------------------------------------- */
|
|
465
466
|
function circularDelegations(graph, emit) {
|
|
466
|
-
|
|
467
|
-
|
|
467
|
+
// Obligations and supersessions are searched apart. A loop that needs a
|
|
468
|
+
// supersession to close carries a question only as far as the document at
|
|
469
|
+
// the head of that supersession, which passes it no further: the question
|
|
470
|
+
// lands, and the loop is a cycle of neither kind (ADR-0005).
|
|
471
|
+
cycles(graph, withoutHandOffs(graph), OBLIGATION_EDGES, emit, (count) => `delegation cycle across ${count} documents: nothing in it can ever land`);
|
|
472
|
+
cycles(graph, graph, ['supersedes'], emit, (count) => `supersession cycle across ${count} documents`);
|
|
473
|
+
}
|
|
474
|
+
/** Reports each loop `searched` holds over relations of `kinds`. */
|
|
475
|
+
function cycles(graph, searched, kinds, emit, message) {
|
|
468
476
|
// Projected onto documents, so a question handed back and forth between two
|
|
469
477
|
// of them is found even though each delegation runs item -> document and the
|
|
470
478
|
// raw graph therefore contains no cycle at all.
|
|
@@ -485,12 +493,9 @@ function circularDelegations(graph, emit) {
|
|
|
485
493
|
if (members.some((member) => member.history))
|
|
486
494
|
continue;
|
|
487
495
|
const edges = cycleEdges(searched, component, kinds);
|
|
488
|
-
const onlySupersession = edges.length > 0 && edges.every((edge) => edge.kind === 'supersedes');
|
|
489
496
|
const head = members[0];
|
|
490
497
|
emit('circular-delegation', () => ({
|
|
491
|
-
message:
|
|
492
|
-
? `supersession cycle across ${members.length} documents`
|
|
493
|
-
: `delegation cycle across ${members.length} documents: nothing in it can ever land`,
|
|
498
|
+
message: message(members.length),
|
|
494
499
|
at: edges[0]?.declaredAt ?? head.at,
|
|
495
500
|
nodes: component,
|
|
496
501
|
related: edges.map((edge) => related(edge.declaredAt, `${edge.from} ${EDGE_TRAITS[edge.kind].phrase} ${edge.to}`)),
|
|
@@ -503,11 +508,11 @@ function circularDelegations(graph, emit) {
|
|
|
503
508
|
*
|
|
504
509
|
* `ADR-0002 supersedes ADR-0001`, and ADR-0001 defers its open question to
|
|
505
510
|
* ADR-0002: the question went to the decision that replaced the one asking it,
|
|
506
|
-
* which is the hand-off a supersession is for.
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
* still close a cycle, and so does a question handed
|
|
510
|
-
* than one that replaced the document handing it.
|
|
511
|
+
* which is the hand-off a supersession is for (ADR-0006). It closes no loop of
|
|
512
|
+
* obligations: a successor that hands a question back into the document it
|
|
513
|
+
* replaced is a ghost handover, reported as that. Two documents passing a
|
|
514
|
+
* question back and forth still close a cycle, and so does a question handed
|
|
515
|
+
* to any document other than one that replaced the document handing it.
|
|
511
516
|
*/
|
|
512
517
|
function withoutHandOffs(graph) {
|
|
513
518
|
// Both ends of an edge are nodes, so the fallback is never taken.
|
|
@@ -562,7 +567,7 @@ function orphanedObligations(graph, emit) {
|
|
|
562
567
|
for (const document of graph.documents) {
|
|
563
568
|
if (document.phase !== 'retired' && document.phase !== 'frozen')
|
|
564
569
|
continue;
|
|
565
|
-
const open = graph.itemsOf(document.id).filter((item) => item.openness !== 'closed');
|
|
570
|
+
const open = graph.itemsOf(document.id).filter((item) => item.openness !== 'closed' && !handedOn(graph, item.id));
|
|
566
571
|
if (open.length === 0)
|
|
567
572
|
continue;
|
|
568
573
|
const sealed = document.phase === 'retired' ? 'retired' : 'frozen';
|
|
@@ -576,6 +581,21 @@ function orphanedObligations(graph, emit) {
|
|
|
576
581
|
}));
|
|
577
582
|
}
|
|
578
583
|
}
|
|
584
|
+
/**
|
|
585
|
+
* Whether an item's question went on to a document that can still take it.
|
|
586
|
+
*
|
|
587
|
+
* ADR-0001 was superseded, and its open question is delegated to ADR-0002: the
|
|
588
|
+
* question lives on in ADR-0002, which is what the finding would ask for.
|
|
589
|
+
* Delegated into another sealed document, it disappears all the same - and no
|
|
590
|
+
* ghost handover says so, since that rule leaves a retired source out.
|
|
591
|
+
*/
|
|
592
|
+
function handedOn(graph, itemId) {
|
|
593
|
+
return graph.out(itemId, ['delegates-to']).some((edge) => {
|
|
594
|
+
// Both ends of an edge are nodes, and every node has a document.
|
|
595
|
+
const target = graph.owningDocument(edge.to);
|
|
596
|
+
return receptivityOf(target.phase) !== 'sealed';
|
|
597
|
+
});
|
|
598
|
+
}
|
|
579
599
|
/* -------------------------------------------------------------------------- */
|
|
580
600
|
/* Supersession consistency */
|
|
581
601
|
/* -------------------------------------------------------------------------- */
|
|
@@ -769,7 +789,7 @@ export function sortDiagnostics(diagnostics) {
|
|
|
769
789
|
export const RULE_QUERIES = Object.freeze({
|
|
770
790
|
'ghost-handover': '*[openness!=closed][phase!=retired] -delegates-to,blocked-by-> *[receptivity=sealed]',
|
|
771
791
|
'stale-premise': '*[phase!=retired] -depends-on,assumes,amends,blocked-by-> document[phase=retired]',
|
|
772
|
-
'orphaned-obligation': 'document[receptivity=sealed] -contains-> item[openness!=closed]',
|
|
792
|
+
'orphaned-obligation': 'document[receptivity=sealed] -contains-> item[openness!=closed], less an item delegated to a document that is not sealed',
|
|
773
793
|
'self-reference': 'see --format json; reflexive edges are excluded from traversal by default',
|
|
774
794
|
});
|
|
775
795
|
//# sourceMappingURL=rules.js.map
|
package/dist/rules.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The built-in diagnostics.\n *\n * Two of them - the two the whole project exists for - are written as selector\n * queries rather than as bespoke traversals. That is not a stunt: it is the\n * proof that the query language is expressive enough to be worth exposing. A\n * team whose convention spec-graph never anticipated can write the equivalent\n * of a built-in rule on the command line.\n *\n * ```text\n * ghost-handover *[openness!=closed][phase!=retired] -delegates-to,blocked-by-> *[receptivity=sealed]\n * stale-premise *[phase!=retired] -depends-on,assumes,amends,blocked-by-> document[phase=retired]\n * ```\n *\n * The rest are not path queries and are not pretended to be: dangling\n * references never became edges, and a cycle is not a fixed-length path.\n *\n * Every rule here obeys one discipline - **report the place a human can fix**.\n * For a supersession that the superseded document never acknowledged, that is\n * the superseded document, not the one that declared the relation.\n */\n\nimport { buildGraph, OBLIGATION_EDGES, type SpecGraph } from './graph.js';\nimport { dirnamePosix, resolveFrom } from './paths.js';\nimport { renderTemplate, type ProjectRule } from './project-rules.js';\nimport { execute, parseQuery, type QuerySpec } from './select.js';\nimport type { ResolvedCorpus } from './resolve.js';\nimport type {\n AnyRuleId,\n DanglingRef,\n Diagnostic,\n DocumentNode,\n Edge,\n ItemNode,\n RelatedLocation,\n RuleId,\n Severity,\n SourceRef,\n SpecNode,\n} from './types.js';\nimport { EDGE_TRAITS } from './types.js';\n\n/* -------------------------------------------------------------------------- */\n/* Configuration */\n/* -------------------------------------------------------------------------- */\n\nexport const DEFAULT_SEVERITIES: Readonly<Record<RuleId, Severity>> = Object.freeze({\n 'ghost-handover': 'error',\n 'stale-premise': 'error',\n 'broken-reference': 'error',\n 'reference-outside-corpus': 'warn',\n 'ambiguous-reference': 'warn',\n 'circular-delegation': 'error',\n 'orphaned-obligation': 'error',\n 'live-supersession': 'error',\n 'unreciprocated-supersession': 'warn',\n 'state-conflict': 'warn',\n 'self-reference': 'info',\n 'unknown-relation-key': 'warn',\n});\n\nexport const RULE_IDS = Object.keys(DEFAULT_SEVERITIES) as readonly RuleId[];\n\n/** One-line descriptions, printed by `spec-graph rules`. */\nexport const RULE_DESCRIPTIONS: Readonly<Record<RuleId, string>> = Object.freeze({\n 'ghost-handover': 'an open obligation is handed to a document that can no longer absorb it',\n 'stale-premise': 'a live document rests on a decision that has been retired or obviated',\n 'broken-reference': 'a citation names a document or anchor that does not exist',\n 'reference-outside-corpus': 'a citation names a real document the include patterns did not reach',\n 'ambiguous-reference': 'a citation matches more than one document',\n 'circular-delegation': 'obligations or supersessions form a cycle, so none of them can ever land',\n 'orphaned-obligation': 'a retired or frozen document still holds open obligations',\n 'live-supersession': 'a document has been superseded but still presents itself as current',\n 'unreciprocated-supersession': 'a retired or archived document does not say what replaced it',\n 'state-conflict': 'an item declares two states that disagree about whether work remains',\n 'self-reference': 'a document delegates to or depends on itself',\n 'unknown-relation-key': 'a front-matter key reads as a relation but declares none',\n});\n\n/**\n * The decision behind each rule.\n *\n * A rule that fires is a claim about somebody's repository, and the reasoning\n * for the claim is written down - in the ADR that decided it, not in a string\n * table. So the table holds a pointer rather than a paraphrase: a second copy\n * of the reasoning would drift from the first, and the first is the one\n * `npm run selfcheck` keeps honest.\n *\n * Printed by `spec-graph rules --explain`, which is how somebody who has just\n * been told their document is inconsistent finds out why anybody decided that.\n */\nexport const RULE_DECISIONS: Readonly<Record<RuleId, string>> = Object.freeze({\n 'ghost-handover': 'docs/adr/0002-lifecycle-lattice.md',\n 'stale-premise': 'docs/adr/0002-lifecycle-lattice.md',\n 'broken-reference': 'docs/adr/0004-reference-resolution.md',\n 'reference-outside-corpus': 'docs/adr/0004-reference-resolution.md',\n 'ambiguous-reference': 'docs/adr/0004-reference-resolution.md',\n 'circular-delegation': 'docs/adr/0005-rules-are-queries.md',\n 'orphaned-obligation': 'docs/adr/0003-item-state-signals.md',\n 'live-supersession': 'docs/adr/0002-lifecycle-lattice.md',\n 'unreciprocated-supersession': 'docs/adr/0006-false-positives-cost-more.md',\n 'state-conflict': 'docs/adr/0003-item-state-signals.md',\n 'self-reference': 'docs/adr/0006-false-positives-cost-more.md',\n 'unknown-relation-key': 'docs/adr/0014-a-relation-is-spelled-both-ways.md',\n});\n\n/**\n * Where a reader opens the ADR a rule's decision is written in. The paths in\n * `RULE_DECISIONS` are the repository's - selfcheck reads them, and a rename\n * breaks the pointer in the same run - but no package ships `docs/`, so a path\n * printed to somebody who installed spec-graph named nothing they had. The tag\n * of the release they run is the ADR as it stood for the rules they got.\n */\nexport function decisionUrl(path: string, version: string): string {\n return `https://github.com/DescentVTT/spec-graph/blob/v${version}/${path}`;\n}\n\n\n/**\n * The severity map `--strict` implies, and which rules it actually moved.\n *\n * Strict raises `warn` to `error` and leaves `info` alone. That asymmetry is\n * deliberate: the `info` rules are advisory by nature - a self-link in a table\n * of contents is a formatting quirk - and promoting them would resurrect\n * precisely the false-positive problem ADR-0006 exists to prevent.\n *\n * An explicit `--rule` always wins over strict, so `--strict --rule x=warn`\n * keeps `x` at warn. That is what makes strict usable: a team can turn it on\n * and exempt the one rule their repository disagrees with, rather than choosing\n * between all of it and none of it.\n */\nexport function resolveStrict(\n overrides: Partial<Record<AnyRuleId, Severity>> = {},\n strict = false,\n projectRules: readonly ProjectRule[] = [],\n): { severities: Partial<Record<AnyRuleId, Severity>>; escalated: ReadonlySet<AnyRuleId> } {\n const severities: Partial<Record<AnyRuleId, Severity>> = { ...overrides };\n const escalated = new Set<AnyRuleId>();\n if (!strict) return { severities, escalated };\n\n // A project rule is escalated on the same terms as a built-in, reading its\n // declared severity as the default. Anything else would mean a team could\n // adopt --strict and quietly keep their own conventions advisory. (A default\n // list seeded with something that is not a rule is an equivalent mutant: with\n // no severity, it is never a warning to raise.)\n const defaults: [AnyRuleId, Severity][] = [\n ...RULE_IDS.map((id): [AnyRuleId, Severity] => [id, DEFAULT_SEVERITIES[id]]),\n ...projectRules.map((rule): [AnyRuleId, Severity] => [rule.id, rule.severity]),\n ];\n\n for (const [id, level] of defaults) {\n if (overrides[id] !== undefined) continue;\n if (level !== 'warn') continue;\n severities[id] = 'error';\n escalated.add(id);\n }\n return { severities, escalated };\n}\n\nexport interface RuleOptions {\n readonly severities?: Partial<Record<AnyRuleId, Severity>> | undefined;\n /** Cap on related locations attached to one finding. */\n readonly maxRelated?: number | undefined;\n /** Conventions the repository wrote for itself. See ADR-0016. */\n readonly projectRules?: readonly ProjectRule[] | undefined;\n}\n\nconst DEFAULT_MAX_RELATED = 8;\n\n/* -------------------------------------------------------------------------- */\n/* Entry point */\n/* -------------------------------------------------------------------------- */\n\n/** Runs every enabled rule and returns findings in report order. */\nexport function runRules(graph: SpecGraph, corpus: ResolvedCorpus, options: RuleOptions = {}): Diagnostic[] {\n const projectRules = options.projectRules ?? [];\n const severities: Partial<Record<AnyRuleId, Severity>> = { ...DEFAULT_SEVERITIES };\n // A project rule's declared severity is its default, and sits in the same\n // table the built-ins do - so `--rule`, `--strict` and `severities` reach it\n // by exactly the machinery they already had.\n for (const rule of projectRules) severities[rule.id] = rule.severity;\n Object.assign(severities, options.severities ?? {});\n const maxRelated = options.maxRelated ?? DEFAULT_MAX_RELATED;\n const out: Diagnostic[] = [];\n\n const emit = (rule: AnyRuleId, build: () => Built): void => {\n const severity = severities[rule];\n // The `undefined` arm is the type system talking, not a case: the table is\n // seeded from every built-in and every project rule, and nothing emits an\n // id that is in neither. Deliberately untested - a mutant that drops it is\n // equivalent, and a test asserting otherwise would be asserting a fiction.\n if (severity === undefined || severity === 'off') return;\n const body = build();\n if (exempt(graph, rule, body.nodes)) return;\n // `target` ahead of the spread: a rule that names one overrides the default,\n // and the eight that have nothing to name say nothing.\n out.push({ rule, severity, target: null, ...body, related: body.related.slice(0, maxRelated) });\n };\n\n // Ghost handovers are reported first and claim their edges, because\n // `blocked-by` is both obligation-transferring and load-bearing: without this,\n // one open question blocked by an archived decision would produce two\n // findings on the same line, for the same defect, with the same fix.\n const claimed = ghostHandovers(graph, emit);\n stalePremises(graph, emit, claimed);\n brokenReferences(corpus, emit);\n circularDelegations(graph, emit);\n orphanedObligations(graph, emit);\n supersessions(graph, emit);\n stateConflicts(graph, emit);\n misreadKeys(corpus, emit);\n selfReferences(graph, emit);\n projectFindings(graph, projectRules, emit);\n\n return sortDiagnostics(out);\n}\n\n/** What a rule returns: the finding, minus what `emit` knows better. */\ntype Built = Omit<Diagnostic, 'rule' | 'severity' | 'target'> & { readonly target?: string };\n\ntype Emit = (rule: AnyRuleId, build: () => Built) => void;\n\n/**\n * Rules about what a document cites, rather than about what it owes.\n *\n * These are the ones a historical record still answers for. A link that goes\n * nowhere is broken whoever wrote it and whenever they wrote it.\n */\nconst REFERENCE_RULES: ReadonlySet<AnyRuleId> = new Set<AnyRuleId>([\n 'broken-reference',\n 'reference-outside-corpus',\n 'ambiguous-reference',\n // A key that declares no relation is a citation that did not happen, which a\n // journal answers for as squarely as a link that goes nowhere. ADR-0011 keeps\n // a record's obligations out of the report, not its references.\n 'unknown-relation-key',\n]);\n\n/**\n * Whether a finding is about history rather than about work.\n *\n * A record reports that something was decided; it does not decide anything. Its\n * open checkboxes are minutes, its delegations are things that were said, and\n * its lifecycle is not a lifecycle. So a record is never the *subject* of a\n * finding about obligations or lifecycle - though it is still a legitimate\n * target of one, because delegating live work into a log is exactly the ghost\n * handover this tool exists to find.\n *\n * Stated once, here, rather than as a guard repeated in eight rules: the\n * exemption is a property of what counts as a finding, not of any one rule, and\n * a rule added later inherits it without having to remember. See ADR-0011.\n *\n * All of that is a log's, declared by `historyPatterns` or `@spec-history`. A\n * document that is a record because it says `archived` is a closed round of\n * work, and nothing about it makes its claims history: a supersession it\n * declares, a cycle it closes and a link to itself are still somebody's to\n * correct. What it owes and what it rests on are history, as they were when\n * `archived` read as retired, and that is all it is exempt from.\n */\nfunction exempt(graph: SpecGraph, rule: AnyRuleId, nodes: readonly string[]): boolean {\n if (REFERENCE_RULES.has(rule)) return false;\n // Every other rule names its subject first, and names a node of the graph,\n // which has an owning document: the two guards below narrow types, and their\n // mutants are equivalent.\n const subject = nodes[0];\n if (subject === undefined) return false;\n const owner = graph.owningDocument(subject);\n if (owner?.phase !== 'record') return false;\n return owner.history || CLOSED_ROUND_RULES.has(rule);\n}\n\n/**\n * The rules an archived document is exempt from as their subject: work it\n * hands on and premises it rests on. Their queries left a retired source out,\n * and `archived` was retired until it became a record.\n */\nconst CLOSED_ROUND_RULES: ReadonlySet<AnyRuleId> = new Set<AnyRuleId>(['ghost-handover', 'stale-premise']);\n\n/* -------------------------------------------------------------------------- */\n/* Ghost handovers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The flagship failure mode: an obligation handed into a closed ledger.\n *\n * The source must still be open and must not itself be retired - a retired\n * document delegating to another retired document is history, not a defect,\n * and reporting it would bury the live findings underneath the archive.\n */\nconst GHOST_HANDOVER: QuerySpec = parseQuery(\n '*[openness!=closed][phase!=retired] -delegates-to,blocked-by-> *[receptivity=sealed]',\n);\n\n/** Emits ghost-handover findings and returns the edges they account for. */\nfunction ghostHandovers(graph: SpecGraph, emit: Emit): Set<string> {\n const claimed = new Set<string>();\n for (const match of execute(graph, GHOST_HANDOVER)) {\n const source = match.nodes[0] as SpecNode;\n const target = match.nodes[1] as SpecNode;\n const edge = match.edges[0] as Edge;\n // A node always has an owning document; the guard narrows the type, and its\n // mutant is equivalent.\n const targetDocument = graph.owningDocument(target.id);\n if (!targetDocument) continue;\n\n const seal = sealOf(targetDocument);\n const sealed = SEALED_WORD[seal] ?? 'frozen';\n const what = source.kind === 'item' ? 'open obligation' : 'obligation';\n const verb = EDGE_TRAITS[edge.kind].phrase;\n claimed.add(edgeKey(edge));\n\n emit('ghost-handover', () => ({\n message: `${what} ${verb} ${describe(target)}, which is ${sealed}`,\n at: edge.declaredAt,\n nodes: [source.id, target.id],\n related: [\n related(targetDocument.statusAt ?? targetDocument.at, `${targetDocument.id} is ${sealed}${statusSuffix(targetDocument)}`),\n ...(source.kind === 'item' ? [related(source.at, `the obligation: ${source.text}`)] : []),\n ],\n hint: HANDOVER_HINT[seal]?.(targetDocument.id) ?? FROZEN_HINT(targetDocument.id),\n }));\n }\n return claimed;\n}\n\n/** Which kind of sealed document this is: its phase, or `archived` for a closed round. */\nfunction sealOf(document: DocumentNode): string {\n return document.phase === 'record' && !document.history ? 'archived' : document.phase;\n}\n\n/** How a sealed target is described in a ghost-handover message. */\nconst SEALED_WORD: Readonly<Partial<Record<string, string>>> = {\n retired: 'retired',\n record: 'a historical record',\n archived: 'archived',\n};\n\nconst FROZEN_HINT = (id: string): string =>\n `${id} is frozen and cannot take on new work - open an amendment, or close this here`;\n\n/** What to do about work handed into each kind of sealed document. */\nconst HANDOVER_HINT: Readonly<Partial<Record<string, (id: string) => string>>> = {\n retired: (id) => `nothing will be read from ${id} again - re-home this in a live document, or close it here`,\n record: (id) => `${id} is a log of what happened and will never act - re-home this in a live document`,\n archived: (id) => `${id} is a closed round of work and will never act - re-home this in a live document`,\n};\n\n/** Identity of a relation, for the hand-off between overlapping rules. */\nfunction edgeKey(edge: Edge): string {\n return `${edge.kind} ${edge.from} ${edge.to}`;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Stale premises */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A live document resting on something that stopped being true.\n *\n * Only load-bearing relations count. A \"See also\" pointing at a superseded ADR\n * is ordinary cross-referencing; `assumes` pointing at the same document means\n * somebody is designing around a constraint that was abolished.\n */\nconst STALE_PREMISE_DOCUMENT: QuerySpec = parseQuery(\n '*[phase!=retired] -depends-on,assumes,amends,blocked-by-> document[phase=retired]',\n);\nconst STALE_PREMISE_ITEM: QuerySpec = parseQuery(\n '*[phase!=retired] -depends-on,assumes,amends,blocked-by-> item[state=obviated]',\n);\n\nfunction stalePremises(graph: SpecGraph, emit: Emit, claimed: ReadonlySet<string>): void {\n const seen = new Set<string>();\n for (const spec of [STALE_PREMISE_DOCUMENT, STALE_PREMISE_ITEM]) {\n for (const match of execute(graph, spec)) {\n const source = match.nodes[0] as SpecNode;\n const target = match.nodes[1] as SpecNode;\n const edge = match.edges[0] as Edge;\n // Already reported as a ghost handover, which says the same thing in the\n // terms the reader needs.\n if (claimed.has(edgeKey(edge))) continue;\n // `execute` already returns one path per pair of nodes, and the two\n // selectors reach different kinds of node, so this never skips today and\n // its mutants are equivalent. It keeps one finding per premise if either\n // of those stops being true.\n const key = `${source.id}>${target.id}>${edge.kind}`;\n if (seen.has(key)) continue;\n seen.add(key);\n\n const targetDocument = graph.owningDocument(target.id);\n const because =\n target.kind === 'item'\n ? 'that question was obviated - its premise no longer holds'\n : `${target.id} is retired${statusSuffix(targetDocument)}`;\n\n emit('stale-premise', () => ({\n message: `${describe(source)} ${EDGE_TRAITS[edge.kind].phrase} ${describe(target)}, which no longer holds`,\n at: edge.declaredAt,\n nodes: [source.id, target.id],\n // Point at the status line when there is one: that is the sentence that\n // makes the premise stale, and repeating the document's first line\n // underneath it adds nothing. A document target owns itself, so the\n // optional chain is never taken.\n related: [related(target.kind === 'document' ? (targetDocument?.statusAt ?? target.at) : target.at, because)],\n hint: `re-check this dependency: the constraint it assumes may have been lifted when ${target.kind === 'item' ? 'the question closed' : `${target.id} was retired`}`,\n }));\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Reference integrity */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Keys that read as relations and declared none.\n *\n * The only rule here that is about the text rather than about the graph, and it\n * earns the place by what its absence costs: the finding is that an edge the\n * author wrote down is *not* in the graph, and a missing edge is invisible in a\n * report that says everything is consistent. Nothing else would ever mention it.\n */\nfunction misreadKeys(corpus: ResolvedCorpus, emit: Emit): void {\n for (const misread of corpus.misreadKeys) {\n emit('unknown-relation-key', () => ({\n message: `\"${misread.key}\" declares no relation`,\n at: misread.at,\n nodes: [misread.from],\n // The key, so two misspellings in one document are two findings rather\n // than one with a count of two - `nodes` names the document and nothing\n // else here names which key.\n target: misread.key,\n related: [],\n hint: `spell it ${misread.suggestion}, or move it out of front matter if it is not a relation`,\n }));\n }\n}\n\nfunction brokenReferences(corpus: ResolvedCorpus, emit: Emit): void {\n for (const ref of corpus.dangling) {\n if (ref.reason === 'ambiguous') {\n emit('ambiguous-reference', () => ({\n message: `\"${ref.target}\" matches ${ref.candidates.length} documents`,\n at: ref.declaredAt,\n nodes: [ref.from],\n target: ref.target,\n related: [],\n hint: `disambiguate it - candidates: ${ref.candidates.join(', ')}`,\n }));\n continue;\n }\n\n // A link to a file that exists but was not included is a configuration\n // problem: the graph is not broken, the corpus is just incomplete. Reporting\n // it as an error means a repository whose specifications live somewhere the\n // default patterns miss fails on its first run, for something the user has\n // not done wrong.\n const rule: RuleId = ref.reason === 'not-a-spec' ? 'reference-outside-corpus' : 'broken-reference';\n\n emit(rule, () => ({\n message: brokenMessage(ref),\n at: ref.declaredAt,\n nodes: [ref.from],\n target: ref.target,\n related: [],\n hint: brokenHint(ref),\n }));\n }\n}\n\nfunction brokenMessage(ref: DanglingRef): string {\n switch (ref.reason) {\n case 'unknown-anchor':\n return `\"${ref.target}\" points at an anchor that does not exist`;\n case 'not-a-spec':\n return `\"${ref.target}\" resolves to a file that is not a specification`;\n default:\n return `\"${ref.target}\" does not resolve to any document`;\n }\n}\n\nfunction brokenHint(ref: DanglingRef): string {\n if (ref.candidates.length > 0) return `did you mean ${ref.candidates.slice(0, 3).join(', ')}?`;\n // A wiki link carries no path, only a name, so whether it means a document at\n // all is a repository convention. Naming the flag here is what keeps that a\n // one-run discovery rather than a support question.\n if (ref.raw.startsWith('[[') && ref.reason === 'unknown-target') {\n return `fix the identifier, or - if [[...]] tags a concept here - exclude it: --ignore-ref \"${suggestReferenceGlob(ref.target)}\"`;\n }\n switch (ref.reason) {\n case 'unknown-anchor':\n return 'check the heading or item id it is meant to address';\n case 'not-a-spec':\n // Name the pattern that would include it: \"widen your patterns\" is advice\n // the reader then has to translate, and this is the translation.\n return `include it, for example: spec-graph \"${suggestPattern(ref)}\"`;\n default:\n return 'fix the identifier, or add the document it names';\n }\n}\n\n/**\n * A glob covering the family of concept tags a target belongs to.\n *\n * `trap 55` suggests `trap *` rather than `trap 55`, because these tags come in\n * numbered series and excluding them one at a time is not a fix anybody would\n * accept. The separator stays in the glob - `trap-55` suggests `trap-*` - or\n * the flag the hint names would not silence the finding it is attached to. A\n * target with no trailing number, or nothing before it, is suggested verbatim.\n */\nfunction suggestReferenceGlob(target: string): string {\n // The scanner hands over a wiki link's target trimmed already, so the trim's\n // mutant is equivalent; and unanchored at the start the pattern would match\n // the same, because the lazy stem can always begin at the first character.\n const trimmed = target.trim();\n const stem = /^(.*?[\\s._-]*)\\d+$/.exec(trimmed)?.[1];\n return stem !== undefined && /[^\\s._-]/.test(stem) ? `${stem}*` : trimmed;\n}\n\n/**\n * The narrowest include pattern that would reach a target the corpus missed.\n *\n * Built from the *resolved* repository-relative path, not the link as written:\n * `../notes.md` cited from `docs/adr/` needs `docs/**\\/*.md`, and suggesting\n * `../**\\/*.md` would be worse than saying nothing.\n */\nfunction suggestPattern(ref: DanglingRef): string {\n const withoutAnchor = (ref.target.split('#')[0] ?? ref.target).trim();\n const resolved = resolveFrom(ref.declaredAt.file, withoutAnchor);\n const directory = dirnamePosix(resolved);\n return directory.length > 0 ? `${directory}/**/*.md` : '**/*.md';\n}\n\n/* -------------------------------------------------------------------------- */\n/* Cycles */\n/* -------------------------------------------------------------------------- */\n\nfunction circularDelegations(graph: SpecGraph, emit: Emit): void {\n const kinds = [...OBLIGATION_EDGES, 'supersedes' as const];\n const searched = withoutHandOffs(graph);\n // Projected onto documents, so a question handed back and forth between two\n // of them is found even though each delegation runs item -> document and the\n // raw graph therefore contains no cycle at all.\n for (const component of searched.cycles(kinds, { byDocument: true })) {\n // A projected component is two documents or more, each with the edge that\n // holds it in: the filter, the length guard and the empty-edges cases below\n // never decide, and their mutants are equivalent. They keep the rule true\n // if `cycles` ever returns less.\n const members = component\n .map((id) => graph.document(id))\n .filter((node): node is DocumentNode => node !== undefined);\n if (members.length < 2) continue;\n // A cycle is a set, not a subject, so the exemption at `emit` cannot see it.\n // One log in the loop means the loop is partly a report of what was once\n // said, and nothing in it is owed by anybody. An archived document in it\n // is not a log: its half of the loop is a claim it still makes.\n if (members.some((member) => member.history)) continue;\n\n const edges = cycleEdges(searched, component, kinds);\n const onlySupersession = edges.length > 0 && edges.every((edge) => edge.kind === 'supersedes');\n const head = members[0] as DocumentNode;\n\n emit('circular-delegation', () => ({\n message: onlySupersession\n ? `supersession cycle across ${members.length} documents`\n : `delegation cycle across ${members.length} documents: nothing in it can ever land`,\n at: edges[0]?.declaredAt ?? head.at,\n nodes: component,\n related: edges.map((edge) => related(edge.declaredAt, `${edge.from} ${EDGE_TRAITS[edge.kind].phrase} ${edge.to}`)),\n hint: 'break the loop: one of these must own the work outright, or be closed',\n }));\n }\n}\n\n/**\n * The graph without the questions a superseded document handed to its successor.\n *\n * `ADR-0002 supersedes ADR-0001`, and ADR-0001 defers its open question to\n * ADR-0002: the question went to the decision that replaced the one asking it,\n * which is the hand-off a supersession is for. Projected onto documents the\n * two relations make a loop, and nothing in it is passed back - so it is not\n * searched for one (ADR-0006). Two documents passing a question back and forth\n * still close a cycle, and so does a question handed to any document other\n * than one that replaced the document handing it.\n */\nfunction withoutHandOffs(graph: SpecGraph): SpecGraph {\n // Both ends of an edge are nodes, so the fallback is never taken.\n const owner = (id: string): string => graph.owningDocument(id)?.id ?? id;\n const successors = new Map<string, Set<string>>();\n for (const edge of graph.edges) {\n if (edge.kind !== 'supersedes') continue;\n const replaced = owner(edge.to);\n const set = successors.get(replaced) ?? new Set<string>();\n set.add(owner(edge.from));\n successors.set(replaced, set);\n }\n const kept = graph.edges.filter(\n (edge) => !(OBLIGATION_EDGES.includes(edge.kind) && successors.get(owner(edge.from))?.has(owner(edge.to))),\n );\n // Rebuilding a graph with every edge kept gives back the same graph, so the\n // comparison only saves the work: its mutants are equivalent.\n return kept.length === graph.edges.length ? graph : buildGraph({ nodes: graph.nodes, edges: kept });\n}\n\n/**\n * The relations that keep a component strongly connected.\n *\n * Membership is by owning document, because the component is a projection: the\n * edge that closes the loop is usually written on an item, and that is the line\n * the report has to point at.\n */\nfunction cycleEdges(graph: SpecGraph, component: readonly string[], kinds: readonly Edge['kind'][]): Edge[] {\n const inside = new Set(component);\n // Both ends of an edge are nodes, so the fallback is never taken.\n const owner = (id: string): string => graph.owningDocument(id)?.id ?? id;\n const out: Edge[] = [];\n for (const edge of graph.edges) {\n if (!kinds.includes(edge.kind)) continue;\n const from = owner(edge.from);\n const to = owner(edge.to);\n if (from !== to && inside.has(from) && inside.has(to)) out.push(edge);\n }\n return out;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Orphaned obligations */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The black hole itself: a document nobody reads any more, still holding work.\n *\n * Reported once per document rather than once per item. Five findings that all\n * say \"this archived ADR has open questions\" is five times the noise and none\n * of the extra information.\n */\nfunction orphanedObligations(graph: SpecGraph, emit: Emit): void {\n for (const document of graph.documents) {\n if (document.phase !== 'retired' && document.phase !== 'frozen') continue;\n const open = graph.itemsOf(document.id).filter((item) => item.openness !== 'closed');\n if (open.length === 0) continue;\n\n const sealed = document.phase === 'retired' ? 'retired' : 'frozen';\n emit('orphaned-obligation', () => ({\n message: `${sealed} document still holds ${open.length} open ${open.length === 1 ? 'obligation' : 'obligations'}`,\n at: document.statusAt ?? document.at,\n nodes: [document.id, ...open.map((item) => item.id)],\n // Every item, where `emit` applies the cap on related locations.\n related: open.map((item) => related(item.at, `${item.disposition}: ${item.text}`)),\n hint: `move each one to a live document or close it - as it stands, ${open.length === 1 ? 'it disappears' : 'they disappear'} with ${document.id}`,\n }));\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Supersession consistency */\n/* -------------------------------------------------------------------------- */\n\nfunction supersessions(graph: SpecGraph, emit: Emit): void {\n for (const edge of graph.edges) {\n if (edge.kind !== 'supersedes' || edge.reflexive) continue;\n const superseded = graph.document(edge.to);\n const superseding = graph.document(edge.from);\n if (!superseded || !superseding) continue;\n // A log narrating \"ADR-0005 replaced ADR-0001\" is reporting a\n // supersession, not declaring one. Both hints below ask somebody to edit\n // the document that made the claim, and a log is the one document nobody\n // can edit - it says what was true when it was written. The exemption at\n // `emit` cannot see this: the subject here is the *other* document. An\n // archived brief that says it supersedes another is declaring it.\n if (superseding.history) continue;\n\n // Closed either way: an archived document is not live, and like a retired\n // one it can fail to say what replaced it. A log in its place is the\n // subject, and exempt at `emit`.\n const archived = superseded.phase === 'record';\n if (superseded.phase !== 'retired' && !archived && superseded.phase !== 'unknown') {\n emit('live-supersession', () => ({\n // The fix belongs in the superseded document, so that is where the\n // finding points - not at the document that made the claim.\n message: `${superseded.id} is superseded by ${superseding.id} but still reads as ${superseded.phase}`,\n at: superseded.statusAt ?? superseded.at,\n nodes: [superseded.id, superseding.id],\n related: [related(edge.declaredAt, `the supersession is declared here`)],\n hint: `mark ${superseded.id} as superseded, or drop the claim in ${superseding.id}`,\n }));\n continue;\n }\n\n if ((superseded.phase === 'retired' || archived) && !edge.declaredIn.includes(superseded.path)) {\n emit('unreciprocated-supersession', () => ({\n message: `${superseded.id} is ${archived ? 'archived' : 'retired'} but never says that ${superseding.id} replaced it`,\n at: superseded.statusAt ?? superseded.at,\n nodes: [superseded.id, superseding.id],\n related: [related(edge.declaredAt, `only ${superseding.id} records the relationship`)],\n // Archived and superseded is retired, so that status also tells what\n // rests on the document that it no longer holds, which a\n // `superseded-by` key beside `archived` would not.\n hint: archived\n ? `make the status of ${superseded.path} \"archived, superseded by ${superseding.id}\" so a reader who lands there is redirected`\n : `add \"superseded-by: ${superseding.id}\" to ${superseded.path} so a reader who lands there is redirected`,\n }));\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Item state */\n/* -------------------------------------------------------------------------- */\n\nfunction stateConflicts(graph: SpecGraph, emit: Emit): void {\n for (const item of graph.items) {\n if (item.conflicts.length === 0) continue;\n emit('state-conflict', () => ({\n message: `item reads as ${item.disposition} but also carries ${describeSignals(item)}`,\n at: item.evidence.at,\n nodes: [item.id],\n // A conflicting signal always has text: only a section's and the default\n // can be empty, and neither is ever in conflict.\n related: item.conflicts.map((signal) => related(signal.at, `${signal.source} says ${signal.disposition}: ${signal.raw}`)),\n hint: `make the two agree - spec-graph is treating it as ${item.openness}`,\n }));\n }\n}\n\nfunction describeSignals(item: ItemNode): string {\n const parts = item.conflicts.map((signal) => `a ${signal.source} saying ${signal.disposition}`);\n return parts.length === 1 ? (parts[0] as string) : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Self references */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A document that delegates to, or depends on, itself.\n *\n * Almost always a copied link that was never repointed. Informational, because\n * a self-link in a table of contents is harmless - but a *delegation* to self\n * means an obligation that looks handed off and was not.\n */\nfunction selfReferences(graph: SpecGraph, emit: Emit): void {\n for (const edge of graph.edges) {\n if (!edge.reflexive) continue;\n // `contains`, from a document to its own item, is reflexive and neither.\n const traits = EDGE_TRAITS[edge.kind];\n if (!traits.transfersObligation && !traits.loadBearing) continue;\n // The graph keeps no edge whose ends are not nodes; the guard narrows the\n // type, and its mutant is equivalent.\n const node = graph.node(edge.from);\n if (!node) continue;\n\n emit('self-reference', () => ({\n message: `${describe(node)} ${traits.phrase} itself`,\n at: edge.declaredAt,\n nodes: [edge.from],\n related: [],\n hint: traits.transfersObligation\n ? 'this obligation looks delegated but never left - point it at another document, or own it here'\n : 'a document cannot depend on itself - repoint or remove the reference',\n }));\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Project rules */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The conventions a repository wrote for itself.\n *\n * Run last, and through the same `emit` as everything else, which is the whole\n * point: a project rule inherits the record exemption, the severity table,\n * `--strict`, the related-location cap, the sort order and the baseline without\n * any of them being taught about it. See ADR-0016.\n */\nfunction projectFindings(graph: SpecGraph, rules: readonly ProjectRule[], emit: Emit): void {\n for (const rule of rules) {\n const seen = new Set<string>();\n for (const spec of rule.queries) {\n for (const match of execute(graph, spec)) {\n const nodes = match.nodes.map((node) => node.id);\n // Two selectors in one rule are a union, so a path both of them find is\n // one finding. The alternative is a repository being told twice about a\n // convention it broke once.\n const key = nodes.join('>');\n if (seen.has(key)) continue;\n seen.add(key);\n\n const head = match.nodes[0] as SpecNode;\n emit(rule.id, () => ({\n message: renderTemplate(rule.message, match, graph),\n // The line that declares the relation, not the document it points at:\n // that is where a human goes to change the answer. A rule with no\n // steps has no relation, so it points at the node it selected.\n at: match.edges[0]?.declaredAt ?? head.at,\n nodes,\n related: match.edges\n .slice(1)\n .map((edge) => related(edge.declaredAt, `${edge.from} ${EDGE_TRAITS[edge.kind].phrase} ${edge.to}`)),\n hint: renderTemplate(rule.hint, match, graph),\n }));\n }\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------------- */\n\nfunction describe(node: SpecNode): string {\n return node.kind === 'document' ? node.id : `${node.document} \"${truncate(node.text, 48)}\"`;\n}\n\nfunction truncate(text: string, limit: number): string {\n return text.length <= limit ? text : `${text.slice(0, limit - 3)}...`;\n}\n\n/** Every caller has the document in hand, so the optional chain is never taken. */\nfunction statusSuffix(document: DocumentNode | undefined): string {\n return document?.rawStatus ? ` (\"${document.rawStatus}\")` : '';\n}\n\nfunction related(at: SourceRef, note: string): RelatedLocation {\n return { at, note };\n}\n\nconst SEVERITY_ORDER: Readonly<Record<Exclude<Severity, 'off'>, number>> = { error: 0, warn: 1, info: 2 };\n\n/**\n * Total ordering, so two runs on the same corpus produce byte-identical output.\n *\n * CI output that reorders between runs cannot be diffed, and output that cannot\n * be diffed stops being read.\n */\nexport function sortDiagnostics(diagnostics: readonly Diagnostic[]): Diagnostic[] {\n return [...diagnostics].sort((a, b) => {\n const bySeverity = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity];\n if (bySeverity !== 0) return bySeverity;\n // A string comparison is reached only when the two differ, where `<` and\n // `<=` agree: those mutants are equivalent.\n if (a.at.file !== b.at.file) return a.at.file < b.at.file ? -1 : 1;\n if (a.at.span.start.line !== b.at.span.start.line) return a.at.span.start.line - b.at.span.start.line;\n if (a.at.span.start.column !== b.at.span.start.column) return a.at.span.start.column - b.at.span.start.column;\n if (a.rule !== b.rule) return a.rule < b.rule ? -1 : 1;\n return a.message < b.message ? -1 : a.message > b.message ? 1 : 0;\n });\n}\n\n/** The selector equivalent of a rule, for `spec-graph rules --explain`. */\nexport const RULE_QUERIES: Readonly<Partial<Record<RuleId, string>>> = Object.freeze({\n 'ghost-handover': '*[openness!=closed][phase!=retired] -delegates-to,blocked-by-> *[receptivity=sealed]',\n 'stale-premise': '*[phase!=retired] -depends-on,assumes,amends,blocked-by-> document[phase=retired]',\n 'orphaned-obligation': 'document[receptivity=sealed] -contains-> item[openness!=closed]',\n 'self-reference': 'see --format json; reflexive edges are excluded from traversal by default',\n});\n"]}
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,cAAc,EAAoB,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAkB,MAAM,aAAa,CAAC;AAelE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF,MAAM,CAAC,MAAM,kBAAkB,GAAuC,MAAM,CAAC,MAAM,CAAC;IAClF,gBAAgB,EAAE,OAAO;IACzB,eAAe,EAAE,OAAO;IACxB,kBAAkB,EAAE,OAAO;IAC3B,0BAA0B,EAAE,MAAM;IAClC,qBAAqB,EAAE,MAAM;IAC7B,qBAAqB,EAAE,OAAO;IAC9B,qBAAqB,EAAE,OAAO;IAC9B,mBAAmB,EAAE,OAAO;IAC5B,6BAA6B,EAAE,MAAM;IACrC,gBAAgB,EAAE,MAAM;IACxB,gBAAgB,EAAE,MAAM;IACxB,sBAAsB,EAAE,MAAM;CAC/B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAsB,CAAC;AAE7E,4DAA4D;AAC5D,MAAM,CAAC,MAAM,iBAAiB,GAAqC,MAAM,CAAC,MAAM,CAAC;IAC/E,gBAAgB,EAAE,yEAAyE;IAC3F,eAAe,EAAE,uEAAuE;IACxF,kBAAkB,EAAE,2DAA2D;IAC/E,0BAA0B,EAAE,qEAAqE;IACjG,qBAAqB,EAAE,2CAA2C;IAClE,qBAAqB,EAAE,8EAA8E;IACrG,qBAAqB,EAAE,2DAA2D;IAClF,mBAAmB,EAAE,qEAAqE;IAC1F,6BAA6B,EAAE,8DAA8D;IAC7F,gBAAgB,EAAE,sEAAsE;IACxF,gBAAgB,EAAE,8CAA8C;IAChE,sBAAsB,EAAE,0DAA0D;CACnF,CAAC,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAqC,MAAM,CAAC,MAAM,CAAC;IAC5E,gBAAgB,EAAE,oCAAoC;IACtD,eAAe,EAAE,oCAAoC;IACrD,kBAAkB,EAAE,uCAAuC;IAC3D,0BAA0B,EAAE,uCAAuC;IACnE,qBAAqB,EAAE,uCAAuC;IAC9D,qBAAqB,EAAE,oCAAoC;IAC3D,qBAAqB,EAAE,qCAAqC;IAC5D,mBAAmB,EAAE,oCAAoC;IACzD,6BAA6B,EAAE,4CAA4C;IAC3E,gBAAgB,EAAE,qCAAqC;IACvD,gBAAgB,EAAE,4CAA4C;IAC9D,sBAAsB,EAAE,kDAAkD;CAC3E,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAAe;IACvD,OAAO,kDAAkD,OAAO,IAAI,IAAI,EAAE,CAAC;AAC7E,CAAC;AAGD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAC3B,SAAS,GAAyC,EAAE,EACpD,MAAM,GAAG,KAAK,EACd,YAAY,GAA2B,EAAE;IAEzC,MAAM,UAAU,GAAyC,EAAE,GAAG,SAAS,EAAE,CAAC;IAC1E,MAAM,SAAS,GAAG,IAAI,GAAG,EAAa,CAAC;IACvC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;IAE9C,2EAA2E;IAC3E,0EAA0E;IAC1E,6EAA6E;IAC7E,8EAA8E;IAC9E,gDAAgD;IAChD,MAAM,QAAQ,GAA4B;QACxC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAyB,EAAE,CAAC,CAAC,EAAE,EAAE,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5E,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAyB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;KAC/E,CAAC;IAEF,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;QACnC,IAAI,SAAS,CAAC,EAAE,CAAC,KAAK,SAAS;YAAE,SAAS;QAC1C,IAAI,KAAK,KAAK,MAAM;YAAE,SAAS;QAC/B,UAAU,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;QACzB,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;AACnC,CAAC;AAUD,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF,oEAAoE;AACpE,MAAM,UAAU,QAAQ,CAAC,KAAgB,EAAE,MAAsB,EAAE,OAAO,GAAgB,EAAE;IAC1F,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;IAChD,MAAM,UAAU,GAAyC,EAAE,GAAG,kBAAkB,EAAE,CAAC;IACnF,0EAA0E;IAC1E,6EAA6E;IAC7E,6CAA6C;IAC7C,KAAK,MAAM,IAAI,IAAI,YAAY;QAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;IACrE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC;IAC7D,MAAM,GAAG,GAAiB,EAAE,CAAC;IAE7B,MAAM,IAAI,GAAG,CAAC,IAAe,EAAE,KAAkB,EAAQ,EAAE;QACzD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAClC,2EAA2E;QAC3E,0EAA0E;QAC1E,2EAA2E;QAC3E,2EAA2E;QAC3E,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,KAAK;YAAE,OAAO;QACzD,MAAM,IAAI,GAAG,KAAK,EAAE,CAAC;QACrB,IAAI,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO;QAC5C,6EAA6E;QAC7E,uDAAuD;QACvD,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC;IAClG,CAAC,CAAC;IAEF,oEAAoE;IACpE,+EAA+E;IAC/E,sEAAsE;IACtE,qEAAqE;IACrE,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC5C,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACpC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC/B,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACjC,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACjC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC3B,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC5B,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC1B,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC5B,eAAe,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;IAE3C,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC;AAC9B,CAAC;AAOD;;;;;GAKG;AACH,MAAM,eAAe,GAA2B,IAAI,GAAG,CAAY;IACjE,kBAAkB;IAClB,0BAA0B;IAC1B,qBAAqB;IACrB,6EAA6E;IAC7E,8EAA8E;IAC9E,gEAAgE;IAChE,sBAAsB;CACvB,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAS,MAAM,CAAC,KAAgB,EAAE,IAAe,EAAE,KAAwB;IACzE,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,2EAA2E;IAC3E,6EAA6E;IAC7E,0BAA0B;IAC1B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IAC5C,IAAI,KAAK,EAAE,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,KAAK,CAAC,OAAO,IAAI,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,kBAAkB,GAA2B,IAAI,GAAG,CAAY,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC,CAAC;AAE3G,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;;;GAMG;AACH,MAAM,cAAc,GAAc,UAAU,CAC1C,sFAAsF,CACvF,CAAC;AAEF,4EAA4E;AAC5E,SAAS,cAAc,CAAC,KAAgB,EAAE,IAAU;IAClD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,cAAc,CAAC,EAAE,CAAC;QACnD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAa,CAAC;QAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAa,CAAC;QAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAS,CAAC;QACpC,4EAA4E;QAC5E,wBAAwB;QACxB,MAAM,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,cAAc;YAAE,SAAS;QAE9B,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;QACpC,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC;QAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,YAAY,CAAC;QACvE,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAE3B,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC;YAC5B,OAAO,EAAE,GAAG,IAAI,IAAI,IAAI,IAAI,QAAQ,CAAC,MAAM,CAAC,cAAc,MAAM,EAAE;YAClE,EAAE,EAAE,IAAI,CAAC,UAAU;YACnB,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE;gBACP,OAAO,CAAC,cAAc,CAAC,QAAQ,IAAI,cAAc,CAAC,EAAE,EAAE,GAAG,cAAc,CAAC,EAAE,OAAO,MAAM,GAAG,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;gBACzH,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,mBAAmB,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;aAC1F;YACD,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC,IAAI,WAAW,CAAC,cAAc,CAAC,EAAE,CAAC;SACjF,CAAC,CAAC,CAAC;IACN,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,0FAA0F;AAC1F,SAAS,MAAM,CAAC,QAAsB;IACpC,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;AACxF,CAAC;AAED,oEAAoE;AACpE,MAAM,WAAW,GAA8C;IAC7D,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,qBAAqB;IAC7B,QAAQ,EAAE,UAAU;CACrB,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,EAAU,EAAU,EAAE,CACzC,GAAG,EAAE,gFAAgF,CAAC;AAExF,sEAAsE;AACtE,MAAM,aAAa,GAA8D;IAC/E,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,6BAA6B,EAAE,4DAA4D;IAC5G,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,iFAAiF;IACtG,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,iFAAiF;CACzG,CAAC;AAEF,0EAA0E;AAC1E,SAAS,OAAO,CAAC,IAAU;IACzB,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;AAChD,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;;;GAMG;AACH,MAAM,sBAAsB,GAAc,UAAU,CAClD,mFAAmF,CACpF,CAAC;AACF,MAAM,kBAAkB,GAAc,UAAU,CAC9C,gFAAgF,CACjF,CAAC;AAEF,SAAS,aAAa,CAAC,KAAgB,EAAE,IAAU,EAAE,OAA4B;IAC/E,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,CAAC,sBAAsB,EAAE,kBAAkB,CAAC,EAAE,CAAC;QAChE,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAa,CAAC;YAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAa,CAAC;YAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAS,CAAC;YACpC,yEAAyE;YACzE,0BAA0B;YAC1B,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBAAE,SAAS;YACzC,oEAAoE;YACpE,yEAAyE;YACzE,yEAAyE;YACzE,6BAA6B;YAC7B,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACrD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAEd,MAAM,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACvD,MAAM,OAAO,GACX,MAAM,CAAC,IAAI,KAAK,MAAM;gBACpB,CAAC,CAAC,0DAA0D;gBAC5D,CAAC,CAAC,GAAG,MAAM,CAAC,EAAE,cAAc,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;YAE/D,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;gBAC3B,OAAO,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,yBAAyB;gBAC1G,EAAE,EAAE,IAAI,CAAC,UAAU;gBACnB,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC;gBAC7B,wEAAwE;gBACxE,mEAAmE;gBACnE,oEAAoE;gBACpE,iCAAiC;gBACjC,OAAO,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,QAAQ,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;gBAC7G,IAAI,EAAE,iFAAiF,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,EAAE,cAAc,EAAE;aACrK,CAAC,CAAC,CAAC;QACN,CAAC;IACH,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;;;;GAOG;AACH,SAAS,WAAW,CAAC,MAAsB,EAAE,IAAU;IACrD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACzC,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE,CAAC,CAAC;YAClC,OAAO,EAAE,IAAI,OAAO,CAAC,GAAG,wBAAwB;YAChD,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,KAAK,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;YACrB,uEAAuE;YACvE,wEAAwE;YACxE,6BAA6B;YAC7B,MAAM,EAAE,OAAO,CAAC,GAAG;YACnB,OAAO,EAAE,EAAE;YACX,IAAI,EAAE,YAAY,OAAO,CAAC,UAAU,0DAA0D;SAC/F,CAAC,CAAC,CAAC;IACN,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAsB,EAAE,IAAU;IAC1D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAClC,IAAI,GAAG,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YAC/B,IAAI,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC;gBACjC,OAAO,EAAE,IAAI,GAAG,CAAC,MAAM,aAAa,GAAG,CAAC,UAAU,CAAC,MAAM,YAAY;gBACrE,EAAE,EAAE,GAAG,CAAC,UAAU;gBAClB,KAAK,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;gBACjB,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,OAAO,EAAE,EAAE;gBACX,IAAI,EAAE,iCAAiC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aACnE,CAAC,CAAC,CAAC;YACJ,SAAS;QACX,CAAC;QAED,uEAAuE;QACvE,6EAA6E;QAC7E,4EAA4E;QAC5E,2EAA2E;QAC3E,kBAAkB;QAClB,MAAM,IAAI,GAAW,GAAG,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,kBAAkB,CAAC;QAEnG,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;YAChB,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC;YAC3B,EAAE,EAAE,GAAG,CAAC,UAAU;YAClB,KAAK,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;YACjB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,EAAE;YACX,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC;SACtB,CAAC,CAAC,CAAC;IACN,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,GAAgB;IACrC,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,gBAAgB;YACnB,OAAO,IAAI,GAAG,CAAC,MAAM,2CAA2C,CAAC;QACnE,KAAK,YAAY;YACf,OAAO,IAAI,GAAG,CAAC,MAAM,kDAAkD,CAAC;QAC1E;YACE,OAAO,IAAI,GAAG,CAAC,MAAM,oCAAoC,CAAC;IAC9D,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,GAAgB;IAClC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,gBAAgB,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IAC/F,8EAA8E;IAC9E,4EAA4E;IAC5E,oDAAoD;IACpD,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,gBAAgB,EAAE,CAAC;QAChE,OAAO,uFAAuF,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;IACpI,CAAC;IACD,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,gBAAgB;YACnB,OAAO,qDAAqD,CAAC;QAC/D,KAAK,YAAY;YACf,0EAA0E;YAC1E,iEAAiE;YACjE,OAAO,wCAAwC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;QACxE;YACE,OAAO,kDAAkD,CAAC;IAC9D,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,oBAAoB,CAAC,MAAc;IAC1C,6EAA6E;IAC7E,4EAA4E;IAC5E,2EAA2E;IAC3E,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACrD,OAAO,IAAI,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;AAC5E,CAAC;AAED;;;;;;GAMG;AACH,SAAS,cAAc,CAAC,GAAgB;IACtC,MAAM,aAAa,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACtE,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IACjE,MAAM,SAAS,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACzC,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;AACnE,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF,SAAS,mBAAmB,CAAC,KAAgB,EAAE,IAAU;IACvD,wEAAwE;IACxE,0EAA0E;IAC1E,0EAA0E;IAC1E,6DAA6D;IAC7D,MAAM,CAAC,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,2BAA2B,KAAK,yCAAyC,CAAC,CAAC;IACpJ,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,6BAA6B,KAAK,YAAY,CAAC,CAAC;AACxG,CAAC;AAED,oEAAoE;AACpE,SAAS,MAAM,CACb,KAAgB,EAChB,QAAmB,EACnB,KAA8B,EAC9B,IAAU,EACV,OAAkC;IAElC,4EAA4E;IAC5E,6EAA6E;IAC7E,gDAAgD;IAChD,KAAK,MAAM,SAAS,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QACrE,0EAA0E;QAC1E,4EAA4E;QAC5E,0EAA0E;QAC1E,iCAAiC;QACjC,MAAM,OAAO,GAAG,SAAS;aACtB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;aAC/B,MAAM,CAAC,CAAC,IAAI,EAAwB,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;QAC9D,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QACjC,6EAA6E;QAC7E,yEAAyE;QACzE,yEAAyE;QACzE,gEAAgE;QAChE,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC;YAAE,SAAS;QAEvD,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;QACrD,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAiB,CAAC;QAExC,IAAI,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC;YACjC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;YAChC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,IAAI,IAAI,CAAC,EAAE;YACnC,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YAClH,IAAI,EAAE,uEAAuE;SAC9E,CAAC,CAAC,CAAC;IACN,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,eAAe,CAAC,KAAgB;IACvC,kEAAkE;IAClE,MAAM,KAAK,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC;IACzE,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuB,CAAC;IAClD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;YAAE,SAAS;QACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChC,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,GAAG,EAAU,CAAC;QAC1D,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1B,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAChC,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAC7B,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAC3G,CAAC;IACF,4EAA4E;IAC5E,8DAA8D;IAC9D,OAAO,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACtG,CAAC;AAED;;;;;;GAMG;AACH,SAAS,UAAU,CAAC,KAAgB,EAAE,SAA4B,EAAE,KAA8B;IAChG,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IAClC,kEAAkE;IAClE,MAAM,KAAK,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC;IACzE,MAAM,GAAG,GAAW,EAAE,CAAC;IACvB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QACzC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1B,IAAI,IAAI,KAAK,EAAE,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;;;GAMG;AACH,SAAS,mBAAmB,CAAC,KAAgB,EAAE,IAAU;IACvD,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACvC,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,IAAI,QAAQ,CAAC,KAAK,KAAK,QAAQ;YAAE,SAAS;QAC1E,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAClH,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAEhC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;QACnE,IAAI,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC;YACjC,OAAO,EAAE,GAAG,MAAM,yBAAyB,IAAI,CAAC,MAAM,SAAS,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,EAAE;YACjH,EAAE,EAAE,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,EAAE;YACpC,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACpD,iEAAiE;YACjE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAClF,IAAI,EAAE,gEAAgE,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,gBAAgB,SAAS,QAAQ,CAAC,EAAE,EAAE;SACnJ,CAAC,CAAC,CAAC;IACN,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,QAAQ,CAAC,KAAgB,EAAE,MAAc;IAChD,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QACvD,iEAAiE;QACjE,MAAM,MAAM,GAAG,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAiB,CAAC;QAC7D,OAAO,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF,SAAS,aAAa,CAAC,KAAgB,EAAE,IAAU;IACjD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,SAAS;YAAE,SAAS;QAC3D,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,IAAI,CAAC,WAAW;YAAE,SAAS;QAC1C,8DAA8D;QAC9D,yEAAyE;QACzE,yEAAyE;QACzE,yEAAyE;QACzE,uEAAuE;QACvE,kEAAkE;QAClE,IAAI,WAAW,CAAC,OAAO;YAAE,SAAS;QAElC,0EAA0E;QAC1E,qEAAqE;QACrE,iCAAiC;QACjC,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,KAAK,QAAQ,CAAC;QAC/C,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,QAAQ,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAClF,IAAI,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,CAAC;gBAC/B,mEAAmE;gBACnE,4DAA4D;gBAC5D,OAAO,EAAE,GAAG,UAAU,CAAC,EAAE,qBAAqB,WAAW,CAAC,EAAE,uBAAuB,UAAU,CAAC,KAAK,EAAE;gBACrG,EAAE,EAAE,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,EAAE;gBACxC,KAAK,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,WAAW,CAAC,EAAE,CAAC;gBACtC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,mCAAmC,CAAC,CAAC;gBACxE,IAAI,EAAE,QAAQ,UAAU,CAAC,EAAE,wCAAwC,WAAW,CAAC,EAAE,EAAE;aACpF,CAAC,CAAC,CAAC;YACJ,SAAS;QACX,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,KAAK,KAAK,SAAS,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/F,IAAI,CAAC,6BAA6B,EAAE,GAAG,EAAE,CAAC,CAAC;gBACzC,OAAO,EAAE,GAAG,UAAU,CAAC,EAAE,OAAO,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,wBAAwB,WAAW,CAAC,EAAE,cAAc;gBACrH,EAAE,EAAE,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,EAAE;gBACxC,KAAK,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,WAAW,CAAC,EAAE,CAAC;gBACtC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,WAAW,CAAC,EAAE,2BAA2B,CAAC,CAAC;gBACtF,qEAAqE;gBACrE,yDAAyD;gBACzD,mDAAmD;gBACnD,IAAI,EAAE,QAAQ;oBACZ,CAAC,CAAC,sBAAsB,UAAU,CAAC,IAAI,6BAA6B,WAAW,CAAC,EAAE,6CAA6C;oBAC/H,CAAC,CAAC,uBAAuB,WAAW,CAAC,EAAE,QAAQ,UAAU,CAAC,IAAI,4CAA4C;aAC7G,CAAC,CAAC,CAAC;QACN,CAAC;IACH,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF,SAAS,cAAc,CAAC,KAAgB,EAAE,IAAU;IAClD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAC1C,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC;YAC5B,OAAO,EAAE,iBAAiB,IAAI,CAAC,WAAW,qBAAqB,eAAe,CAAC,IAAI,CAAC,EAAE;YACtF,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE;YACpB,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAChB,yEAAyE;YACzE,iDAAiD;YACjD,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,WAAW,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;YACzH,IAAI,EAAE,qDAAqD,IAAI,CAAC,QAAQ,EAAE;SAC3E,CAAC,CAAC,CAAC;IACN,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,IAAc;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,MAAM,CAAC,MAAM,WAAW,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAChG,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAE,KAAK,CAAC,CAAC,CAAY,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;AACvH,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;;;GAMG;AACH,SAAS,cAAc,CAAC,KAAgB,EAAE,IAAU;IAClD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,SAAS;QAC9B,yEAAyE;QACzE,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,mBAAmB,IAAI,CAAC,MAAM,CAAC,WAAW;YAAE,SAAS;QACjE,0EAA0E;QAC1E,sCAAsC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI;YAAE,SAAS;QAEpB,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC;YAC5B,OAAO,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,SAAS;YACpD,EAAE,EAAE,IAAI,CAAC,UAAU;YACnB,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;YAClB,OAAO,EAAE,EAAE;YACX,IAAI,EAAE,MAAM,CAAC,mBAAmB;gBAC9B,CAAC,CAAC,+FAA+F;gBACjG,CAAC,CAAC,sEAAsE;SAC3E,CAAC,CAAC,CAAC;IACN,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;;;;GAOG;AACH,SAAS,eAAe,CAAC,KAAgB,EAAE,KAA6B,EAAE,IAAU;IAClF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAChC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;gBACzC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACjD,wEAAwE;gBACxE,wEAAwE;gBACxE,4BAA4B;gBAC5B,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC5B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAEd,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAa,CAAC;gBACxC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;oBACnB,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC;oBACnD,sEAAsE;oBACtE,kEAAkE;oBAClE,+DAA+D;oBAC/D,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,IAAI,IAAI,CAAC,EAAE;oBACzC,KAAK;oBACL,OAAO,EAAE,KAAK,CAAC,KAAK;yBACjB,KAAK,CAAC,CAAC,CAAC;yBACR,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;oBACtG,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC;iBAC9C,CAAC,CAAC,CAAC;YACN,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF,SAAS,QAAQ,CAAC,IAAc;IAC9B,OAAO,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC;AAC9F,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,KAAa;IAC3C,OAAO,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;AACxE,CAAC;AAED,mFAAmF;AACnF,SAAS,YAAY,CAAC,QAAkC;IACtD,OAAO,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;AACjE,CAAC;AAED,SAAS,OAAO,CAAC,EAAa,EAAE,IAAY;IAC1C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,MAAM,cAAc,GAAuD,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAE1G;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,WAAkC;IAChE,OAAO,CAAC,GAAG,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACpC,MAAM,UAAU,GAAG,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC3E,IAAI,UAAU,KAAK,CAAC;YAAE,OAAO,UAAU,CAAC;QACxC,yEAAyE;QACzE,4CAA4C;QAC5C,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,IAAI;YAAE,OAAO,CAAC,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;YAAE,OAAO,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QACtG,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,OAAO,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAC9G,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;YAAE,OAAO,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvD,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,MAAM,YAAY,GAA8C,MAAM,CAAC,MAAM,CAAC;IACnF,gBAAgB,EAAE,sFAAsF;IACxG,eAAe,EAAE,mFAAmF;IACpG,qBAAqB,EACnB,0HAA0H;IAC5H,gBAAgB,EAAE,2EAA2E;CAC9F,CAAC,CAAC","sourcesContent":["/**\n * The built-in diagnostics.\n *\n * Two of them - the two the whole project exists for - are written as selector\n * queries rather than as bespoke traversals. That is not a stunt: it is the\n * proof that the query language is expressive enough to be worth exposing. A\n * team whose convention spec-graph never anticipated can write the equivalent\n * of a built-in rule on the command line.\n *\n * ```text\n * ghost-handover *[openness!=closed][phase!=retired] -delegates-to,blocked-by-> *[receptivity=sealed]\n * stale-premise *[phase!=retired] -depends-on,assumes,amends,blocked-by-> document[phase=retired]\n * ```\n *\n * The rest are not path queries and are not pretended to be: dangling\n * references never became edges, and a cycle is not a fixed-length path.\n *\n * Every rule here obeys one discipline - **report the place a human can fix**.\n * For a supersession that the superseded document never acknowledged, that is\n * the superseded document, not the one that declared the relation.\n */\n\nimport { buildGraph, OBLIGATION_EDGES, type SpecGraph } from './graph.js';\nimport { receptivityOf } from './lifecycle.js';\nimport { dirnamePosix, resolveFrom } from './paths.js';\nimport { renderTemplate, type ProjectRule } from './project-rules.js';\nimport { execute, parseQuery, type QuerySpec } from './select.js';\nimport type { ResolvedCorpus } from './resolve.js';\nimport type {\n AnyRuleId,\n DanglingRef,\n Diagnostic,\n DocumentNode,\n Edge,\n ItemNode,\n RelatedLocation,\n RuleId,\n Severity,\n SourceRef,\n SpecNode,\n} from './types.js';\nimport { EDGE_TRAITS } from './types.js';\n\n/* -------------------------------------------------------------------------- */\n/* Configuration */\n/* -------------------------------------------------------------------------- */\n\nexport const DEFAULT_SEVERITIES: Readonly<Record<RuleId, Severity>> = Object.freeze({\n 'ghost-handover': 'error',\n 'stale-premise': 'error',\n 'broken-reference': 'error',\n 'reference-outside-corpus': 'warn',\n 'ambiguous-reference': 'warn',\n 'circular-delegation': 'error',\n 'orphaned-obligation': 'error',\n 'live-supersession': 'error',\n 'unreciprocated-supersession': 'warn',\n 'state-conflict': 'warn',\n 'self-reference': 'info',\n 'unknown-relation-key': 'warn',\n});\n\nexport const RULE_IDS = Object.keys(DEFAULT_SEVERITIES) as readonly RuleId[];\n\n/** One-line descriptions, printed by `spec-graph rules`. */\nexport const RULE_DESCRIPTIONS: Readonly<Record<RuleId, string>> = Object.freeze({\n 'ghost-handover': 'an open obligation is handed to a document that can no longer absorb it',\n 'stale-premise': 'a live document rests on a decision that has been retired or obviated',\n 'broken-reference': 'a citation names a document or anchor that does not exist',\n 'reference-outside-corpus': 'a citation names a real document the include patterns did not reach',\n 'ambiguous-reference': 'a citation matches more than one document',\n 'circular-delegation': 'obligations form a cycle, so none of them can ever land, or supersessions do',\n 'orphaned-obligation': 'a retired or frozen document still holds open obligations',\n 'live-supersession': 'a document has been superseded but still presents itself as current',\n 'unreciprocated-supersession': 'a retired or archived document does not say what replaced it',\n 'state-conflict': 'an item declares two states that disagree about whether work remains',\n 'self-reference': 'a document delegates to or depends on itself',\n 'unknown-relation-key': 'a front-matter key reads as a relation but declares none',\n});\n\n/**\n * The decision behind each rule.\n *\n * A rule that fires is a claim about somebody's repository, and the reasoning\n * for the claim is written down - in the ADR that decided it, not in a string\n * table. So the table holds a pointer rather than a paraphrase: a second copy\n * of the reasoning would drift from the first, and the first is the one\n * `npm run selfcheck` keeps honest.\n *\n * Printed by `spec-graph rules --explain`, which is how somebody who has just\n * been told their document is inconsistent finds out why anybody decided that.\n */\nexport const RULE_DECISIONS: Readonly<Record<RuleId, string>> = Object.freeze({\n 'ghost-handover': 'docs/adr/0002-lifecycle-lattice.md',\n 'stale-premise': 'docs/adr/0002-lifecycle-lattice.md',\n 'broken-reference': 'docs/adr/0004-reference-resolution.md',\n 'reference-outside-corpus': 'docs/adr/0004-reference-resolution.md',\n 'ambiguous-reference': 'docs/adr/0004-reference-resolution.md',\n 'circular-delegation': 'docs/adr/0005-rules-are-queries.md',\n 'orphaned-obligation': 'docs/adr/0003-item-state-signals.md',\n 'live-supersession': 'docs/adr/0002-lifecycle-lattice.md',\n 'unreciprocated-supersession': 'docs/adr/0006-false-positives-cost-more.md',\n 'state-conflict': 'docs/adr/0003-item-state-signals.md',\n 'self-reference': 'docs/adr/0006-false-positives-cost-more.md',\n 'unknown-relation-key': 'docs/adr/0014-a-relation-is-spelled-both-ways.md',\n});\n\n/**\n * Where a reader opens the ADR a rule's decision is written in. The paths in\n * `RULE_DECISIONS` are the repository's - selfcheck reads them, and a rename\n * breaks the pointer in the same run - but no package ships `docs/`, so a path\n * printed to somebody who installed spec-graph named nothing they had. The tag\n * of the release they run is the ADR as it stood for the rules they got.\n */\nexport function decisionUrl(path: string, version: string): string {\n return `https://github.com/DescentVTT/spec-graph/blob/v${version}/${path}`;\n}\n\n\n/**\n * The severity map `--strict` implies, and which rules it actually moved.\n *\n * Strict raises `warn` to `error` and leaves `info` alone. That asymmetry is\n * deliberate: the `info` rules are advisory by nature - a self-link in a table\n * of contents is a formatting quirk - and promoting them would resurrect\n * precisely the false-positive problem ADR-0006 exists to prevent.\n *\n * An explicit `--rule` always wins over strict, so `--strict --rule x=warn`\n * keeps `x` at warn. That is what makes strict usable: a team can turn it on\n * and exempt the one rule their repository disagrees with, rather than choosing\n * between all of it and none of it.\n */\nexport function resolveStrict(\n overrides: Partial<Record<AnyRuleId, Severity>> = {},\n strict = false,\n projectRules: readonly ProjectRule[] = [],\n): { severities: Partial<Record<AnyRuleId, Severity>>; escalated: ReadonlySet<AnyRuleId> } {\n const severities: Partial<Record<AnyRuleId, Severity>> = { ...overrides };\n const escalated = new Set<AnyRuleId>();\n if (!strict) return { severities, escalated };\n\n // A project rule is escalated on the same terms as a built-in, reading its\n // declared severity as the default. Anything else would mean a team could\n // adopt --strict and quietly keep their own conventions advisory. (A default\n // list seeded with something that is not a rule is an equivalent mutant: with\n // no severity, it is never a warning to raise.)\n const defaults: [AnyRuleId, Severity][] = [\n ...RULE_IDS.map((id): [AnyRuleId, Severity] => [id, DEFAULT_SEVERITIES[id]]),\n ...projectRules.map((rule): [AnyRuleId, Severity] => [rule.id, rule.severity]),\n ];\n\n for (const [id, level] of defaults) {\n if (overrides[id] !== undefined) continue;\n if (level !== 'warn') continue;\n severities[id] = 'error';\n escalated.add(id);\n }\n return { severities, escalated };\n}\n\nexport interface RuleOptions {\n readonly severities?: Partial<Record<AnyRuleId, Severity>> | undefined;\n /** Cap on related locations attached to one finding. */\n readonly maxRelated?: number | undefined;\n /** Conventions the repository wrote for itself. See ADR-0016. */\n readonly projectRules?: readonly ProjectRule[] | undefined;\n}\n\nconst DEFAULT_MAX_RELATED = 8;\n\n/* -------------------------------------------------------------------------- */\n/* Entry point */\n/* -------------------------------------------------------------------------- */\n\n/** Runs every enabled rule and returns findings in report order. */\nexport function runRules(graph: SpecGraph, corpus: ResolvedCorpus, options: RuleOptions = {}): Diagnostic[] {\n const projectRules = options.projectRules ?? [];\n const severities: Partial<Record<AnyRuleId, Severity>> = { ...DEFAULT_SEVERITIES };\n // A project rule's declared severity is its default, and sits in the same\n // table the built-ins do - so `--rule`, `--strict` and `severities` reach it\n // by exactly the machinery they already had.\n for (const rule of projectRules) severities[rule.id] = rule.severity;\n Object.assign(severities, options.severities ?? {});\n const maxRelated = options.maxRelated ?? DEFAULT_MAX_RELATED;\n const out: Diagnostic[] = [];\n\n const emit = (rule: AnyRuleId, build: () => Built): void => {\n const severity = severities[rule];\n // The `undefined` arm is the type system talking, not a case: the table is\n // seeded from every built-in and every project rule, and nothing emits an\n // id that is in neither. Deliberately untested - a mutant that drops it is\n // equivalent, and a test asserting otherwise would be asserting a fiction.\n if (severity === undefined || severity === 'off') return;\n const body = build();\n if (exempt(graph, rule, body.nodes)) return;\n // `target` ahead of the spread: a rule that names one overrides the default,\n // and the eight that have nothing to name say nothing.\n out.push({ rule, severity, target: null, ...body, related: body.related.slice(0, maxRelated) });\n };\n\n // Ghost handovers are reported first and claim their edges, because\n // `blocked-by` is both obligation-transferring and load-bearing: without this,\n // one open question blocked by an archived decision would produce two\n // findings on the same line, for the same defect, with the same fix.\n const claimed = ghostHandovers(graph, emit);\n stalePremises(graph, emit, claimed);\n brokenReferences(corpus, emit);\n circularDelegations(graph, emit);\n orphanedObligations(graph, emit);\n supersessions(graph, emit);\n stateConflicts(graph, emit);\n misreadKeys(corpus, emit);\n selfReferences(graph, emit);\n projectFindings(graph, projectRules, emit);\n\n return sortDiagnostics(out);\n}\n\n/** What a rule returns: the finding, minus what `emit` knows better. */\ntype Built = Omit<Diagnostic, 'rule' | 'severity' | 'target'> & { readonly target?: string };\n\ntype Emit = (rule: AnyRuleId, build: () => Built) => void;\n\n/**\n * Rules about what a document cites, rather than about what it owes.\n *\n * These are the ones a historical record still answers for. A link that goes\n * nowhere is broken whoever wrote it and whenever they wrote it.\n */\nconst REFERENCE_RULES: ReadonlySet<AnyRuleId> = new Set<AnyRuleId>([\n 'broken-reference',\n 'reference-outside-corpus',\n 'ambiguous-reference',\n // A key that declares no relation is a citation that did not happen, which a\n // journal answers for as squarely as a link that goes nowhere. ADR-0011 keeps\n // a record's obligations out of the report, not its references.\n 'unknown-relation-key',\n]);\n\n/**\n * Whether a finding is about history rather than about work.\n *\n * A record reports that something was decided; it does not decide anything. Its\n * open checkboxes are minutes, its delegations are things that were said, and\n * its lifecycle is not a lifecycle. So a record is never the *subject* of a\n * finding about obligations or lifecycle - though it is still a legitimate\n * target of one, because delegating live work into a log is exactly the ghost\n * handover this tool exists to find.\n *\n * Stated once, here, rather than as a guard repeated in eight rules: the\n * exemption is a property of what counts as a finding, not of any one rule, and\n * a rule added later inherits it without having to remember. See ADR-0011.\n *\n * All of that is a log's, declared by `historyPatterns` or `@spec-history`. A\n * document that is a record because it says `archived` is a closed round of\n * work, and nothing about it makes its claims history: a supersession it\n * declares, a cycle it closes and a link to itself are still somebody's to\n * correct. What it owes and what it rests on are history, as they were when\n * `archived` read as retired, and that is all it is exempt from.\n */\nfunction exempt(graph: SpecGraph, rule: AnyRuleId, nodes: readonly string[]): boolean {\n if (REFERENCE_RULES.has(rule)) return false;\n // Every other rule names its subject first, and names a node of the graph,\n // which has an owning document: the two guards below narrow types, and their\n // mutants are equivalent.\n const subject = nodes[0];\n if (subject === undefined) return false;\n const owner = graph.owningDocument(subject);\n if (owner?.phase !== 'record') return false;\n return owner.history || CLOSED_ROUND_RULES.has(rule);\n}\n\n/**\n * The rules an archived document is exempt from as their subject: work it\n * hands on and premises it rests on. Their queries left a retired source out,\n * and `archived` was retired until it became a record.\n */\nconst CLOSED_ROUND_RULES: ReadonlySet<AnyRuleId> = new Set<AnyRuleId>(['ghost-handover', 'stale-premise']);\n\n/* -------------------------------------------------------------------------- */\n/* Ghost handovers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The flagship failure mode: an obligation handed into a closed ledger.\n *\n * The source must still be open and must not itself be retired - a retired\n * document delegating to another retired document is history, not a defect,\n * and reporting it would bury the live findings underneath the archive.\n */\nconst GHOST_HANDOVER: QuerySpec = parseQuery(\n '*[openness!=closed][phase!=retired] -delegates-to,blocked-by-> *[receptivity=sealed]',\n);\n\n/** Emits ghost-handover findings and returns the edges they account for. */\nfunction ghostHandovers(graph: SpecGraph, emit: Emit): Set<string> {\n const claimed = new Set<string>();\n for (const match of execute(graph, GHOST_HANDOVER)) {\n const source = match.nodes[0] as SpecNode;\n const target = match.nodes[1] as SpecNode;\n const edge = match.edges[0] as Edge;\n // A node always has an owning document; the guard narrows the type, and its\n // mutant is equivalent.\n const targetDocument = graph.owningDocument(target.id);\n if (!targetDocument) continue;\n\n const seal = sealOf(targetDocument);\n const sealed = SEALED_WORD[seal] ?? 'frozen';\n const what = source.kind === 'item' ? 'open obligation' : 'obligation';\n const verb = EDGE_TRAITS[edge.kind].phrase;\n claimed.add(edgeKey(edge));\n\n emit('ghost-handover', () => ({\n message: `${what} ${verb} ${describe(target)}, which is ${sealed}`,\n at: edge.declaredAt,\n nodes: [source.id, target.id],\n related: [\n related(targetDocument.statusAt ?? targetDocument.at, `${targetDocument.id} is ${sealed}${statusSuffix(targetDocument)}`),\n ...(source.kind === 'item' ? [related(source.at, `the obligation: ${source.text}`)] : []),\n ],\n hint: HANDOVER_HINT[seal]?.(targetDocument.id) ?? FROZEN_HINT(targetDocument.id),\n }));\n }\n return claimed;\n}\n\n/** Which kind of sealed document this is: its phase, or `archived` for a closed round. */\nfunction sealOf(document: DocumentNode): string {\n return document.phase === 'record' && !document.history ? 'archived' : document.phase;\n}\n\n/** How a sealed target is described in a ghost-handover message. */\nconst SEALED_WORD: Readonly<Partial<Record<string, string>>> = {\n retired: 'retired',\n record: 'a historical record',\n archived: 'archived',\n};\n\nconst FROZEN_HINT = (id: string): string =>\n `${id} is frozen and cannot take on new work - open an amendment, or close this here`;\n\n/** What to do about work handed into each kind of sealed document. */\nconst HANDOVER_HINT: Readonly<Partial<Record<string, (id: string) => string>>> = {\n retired: (id) => `nothing will be read from ${id} again - re-home this in a live document, or close it here`,\n record: (id) => `${id} is a log of what happened and will never act - re-home this in a live document`,\n archived: (id) => `${id} is a closed round of work and will never act - re-home this in a live document`,\n};\n\n/** Identity of a relation, for the hand-off between overlapping rules. */\nfunction edgeKey(edge: Edge): string {\n return `${edge.kind} ${edge.from} ${edge.to}`;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Stale premises */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A live document resting on something that stopped being true.\n *\n * Only load-bearing relations count. A \"See also\" pointing at a superseded ADR\n * is ordinary cross-referencing; `assumes` pointing at the same document means\n * somebody is designing around a constraint that was abolished.\n */\nconst STALE_PREMISE_DOCUMENT: QuerySpec = parseQuery(\n '*[phase!=retired] -depends-on,assumes,amends,blocked-by-> document[phase=retired]',\n);\nconst STALE_PREMISE_ITEM: QuerySpec = parseQuery(\n '*[phase!=retired] -depends-on,assumes,amends,blocked-by-> item[state=obviated]',\n);\n\nfunction stalePremises(graph: SpecGraph, emit: Emit, claimed: ReadonlySet<string>): void {\n const seen = new Set<string>();\n for (const spec of [STALE_PREMISE_DOCUMENT, STALE_PREMISE_ITEM]) {\n for (const match of execute(graph, spec)) {\n const source = match.nodes[0] as SpecNode;\n const target = match.nodes[1] as SpecNode;\n const edge = match.edges[0] as Edge;\n // Already reported as a ghost handover, which says the same thing in the\n // terms the reader needs.\n if (claimed.has(edgeKey(edge))) continue;\n // `execute` already returns one path per pair of nodes, and the two\n // selectors reach different kinds of node, so this never skips today and\n // its mutants are equivalent. It keeps one finding per premise if either\n // of those stops being true.\n const key = `${source.id}>${target.id}>${edge.kind}`;\n if (seen.has(key)) continue;\n seen.add(key);\n\n const targetDocument = graph.owningDocument(target.id);\n const because =\n target.kind === 'item'\n ? 'that question was obviated - its premise no longer holds'\n : `${target.id} is retired${statusSuffix(targetDocument)}`;\n\n emit('stale-premise', () => ({\n message: `${describe(source)} ${EDGE_TRAITS[edge.kind].phrase} ${describe(target)}, which no longer holds`,\n at: edge.declaredAt,\n nodes: [source.id, target.id],\n // Point at the status line when there is one: that is the sentence that\n // makes the premise stale, and repeating the document's first line\n // underneath it adds nothing. A document target owns itself, so the\n // optional chain is never taken.\n related: [related(target.kind === 'document' ? (targetDocument?.statusAt ?? target.at) : target.at, because)],\n hint: `re-check this dependency: the constraint it assumes may have been lifted when ${target.kind === 'item' ? 'the question closed' : `${target.id} was retired`}`,\n }));\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Reference integrity */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Keys that read as relations and declared none.\n *\n * The only rule here that is about the text rather than about the graph, and it\n * earns the place by what its absence costs: the finding is that an edge the\n * author wrote down is *not* in the graph, and a missing edge is invisible in a\n * report that says everything is consistent. Nothing else would ever mention it.\n */\nfunction misreadKeys(corpus: ResolvedCorpus, emit: Emit): void {\n for (const misread of corpus.misreadKeys) {\n emit('unknown-relation-key', () => ({\n message: `\"${misread.key}\" declares no relation`,\n at: misread.at,\n nodes: [misread.from],\n // The key, so two misspellings in one document are two findings rather\n // than one with a count of two - `nodes` names the document and nothing\n // else here names which key.\n target: misread.key,\n related: [],\n hint: `spell it ${misread.suggestion}, or move it out of front matter if it is not a relation`,\n }));\n }\n}\n\nfunction brokenReferences(corpus: ResolvedCorpus, emit: Emit): void {\n for (const ref of corpus.dangling) {\n if (ref.reason === 'ambiguous') {\n emit('ambiguous-reference', () => ({\n message: `\"${ref.target}\" matches ${ref.candidates.length} documents`,\n at: ref.declaredAt,\n nodes: [ref.from],\n target: ref.target,\n related: [],\n hint: `disambiguate it - candidates: ${ref.candidates.join(', ')}`,\n }));\n continue;\n }\n\n // A link to a file that exists but was not included is a configuration\n // problem: the graph is not broken, the corpus is just incomplete. Reporting\n // it as an error means a repository whose specifications live somewhere the\n // default patterns miss fails on its first run, for something the user has\n // not done wrong.\n const rule: RuleId = ref.reason === 'not-a-spec' ? 'reference-outside-corpus' : 'broken-reference';\n\n emit(rule, () => ({\n message: brokenMessage(ref),\n at: ref.declaredAt,\n nodes: [ref.from],\n target: ref.target,\n related: [],\n hint: brokenHint(ref),\n }));\n }\n}\n\nfunction brokenMessage(ref: DanglingRef): string {\n switch (ref.reason) {\n case 'unknown-anchor':\n return `\"${ref.target}\" points at an anchor that does not exist`;\n case 'not-a-spec':\n return `\"${ref.target}\" resolves to a file that is not a specification`;\n default:\n return `\"${ref.target}\" does not resolve to any document`;\n }\n}\n\nfunction brokenHint(ref: DanglingRef): string {\n if (ref.candidates.length > 0) return `did you mean ${ref.candidates.slice(0, 3).join(', ')}?`;\n // A wiki link carries no path, only a name, so whether it means a document at\n // all is a repository convention. Naming the flag here is what keeps that a\n // one-run discovery rather than a support question.\n if (ref.raw.startsWith('[[') && ref.reason === 'unknown-target') {\n return `fix the identifier, or - if [[...]] tags a concept here - exclude it: --ignore-ref \"${suggestReferenceGlob(ref.target)}\"`;\n }\n switch (ref.reason) {\n case 'unknown-anchor':\n return 'check the heading or item id it is meant to address';\n case 'not-a-spec':\n // Name the pattern that would include it: \"widen your patterns\" is advice\n // the reader then has to translate, and this is the translation.\n return `include it, for example: spec-graph \"${suggestPattern(ref)}\"`;\n default:\n return 'fix the identifier, or add the document it names';\n }\n}\n\n/**\n * A glob covering the family of concept tags a target belongs to.\n *\n * `trap 55` suggests `trap *` rather than `trap 55`, because these tags come in\n * numbered series and excluding them one at a time is not a fix anybody would\n * accept. The separator stays in the glob - `trap-55` suggests `trap-*` - or\n * the flag the hint names would not silence the finding it is attached to. A\n * target with no trailing number, or nothing before it, is suggested verbatim.\n */\nfunction suggestReferenceGlob(target: string): string {\n // The scanner hands over a wiki link's target trimmed already, so the trim's\n // mutant is equivalent; and unanchored at the start the pattern would match\n // the same, because the lazy stem can always begin at the first character.\n const trimmed = target.trim();\n const stem = /^(.*?[\\s._-]*)\\d+$/.exec(trimmed)?.[1];\n return stem !== undefined && /[^\\s._-]/.test(stem) ? `${stem}*` : trimmed;\n}\n\n/**\n * The narrowest include pattern that would reach a target the corpus missed.\n *\n * Built from the *resolved* repository-relative path, not the link as written:\n * `../notes.md` cited from `docs/adr/` needs `docs/**\\/*.md`, and suggesting\n * `../**\\/*.md` would be worse than saying nothing.\n */\nfunction suggestPattern(ref: DanglingRef): string {\n const withoutAnchor = (ref.target.split('#')[0] ?? ref.target).trim();\n const resolved = resolveFrom(ref.declaredAt.file, withoutAnchor);\n const directory = dirnamePosix(resolved);\n return directory.length > 0 ? `${directory}/**/*.md` : '**/*.md';\n}\n\n/* -------------------------------------------------------------------------- */\n/* Cycles */\n/* -------------------------------------------------------------------------- */\n\nfunction circularDelegations(graph: SpecGraph, emit: Emit): void {\n // Obligations and supersessions are searched apart. A loop that needs a\n // supersession to close carries a question only as far as the document at\n // the head of that supersession, which passes it no further: the question\n // lands, and the loop is a cycle of neither kind (ADR-0005).\n cycles(graph, withoutHandOffs(graph), OBLIGATION_EDGES, emit, (count) => `delegation cycle across ${count} documents: nothing in it can ever land`);\n cycles(graph, graph, ['supersedes'], emit, (count) => `supersession cycle across ${count} documents`);\n}\n\n/** Reports each loop `searched` holds over relations of `kinds`. */\nfunction cycles(\n graph: SpecGraph,\n searched: SpecGraph,\n kinds: readonly Edge['kind'][],\n emit: Emit,\n message: (count: number) => string,\n): void {\n // Projected onto documents, so a question handed back and forth between two\n // of them is found even though each delegation runs item -> document and the\n // raw graph therefore contains no cycle at all.\n for (const component of searched.cycles(kinds, { byDocument: true })) {\n // A projected component is two documents or more, each with the edge that\n // holds it in: the filter, the length guard and the empty-edges cases below\n // never decide, and their mutants are equivalent. They keep the rule true\n // if `cycles` ever returns less.\n const members = component\n .map((id) => graph.document(id))\n .filter((node): node is DocumentNode => node !== undefined);\n if (members.length < 2) continue;\n // A cycle is a set, not a subject, so the exemption at `emit` cannot see it.\n // One log in the loop means the loop is partly a report of what was once\n // said, and nothing in it is owed by anybody. An archived document in it\n // is not a log: its half of the loop is a claim it still makes.\n if (members.some((member) => member.history)) continue;\n\n const edges = cycleEdges(searched, component, kinds);\n const head = members[0] as DocumentNode;\n\n emit('circular-delegation', () => ({\n message: message(members.length),\n at: edges[0]?.declaredAt ?? head.at,\n nodes: component,\n related: edges.map((edge) => related(edge.declaredAt, `${edge.from} ${EDGE_TRAITS[edge.kind].phrase} ${edge.to}`)),\n hint: 'break the loop: one of these must own the work outright, or be closed',\n }));\n }\n}\n\n/**\n * The graph without the questions a superseded document handed to its successor.\n *\n * `ADR-0002 supersedes ADR-0001`, and ADR-0001 defers its open question to\n * ADR-0002: the question went to the decision that replaced the one asking it,\n * which is the hand-off a supersession is for (ADR-0006). It closes no loop of\n * obligations: a successor that hands a question back into the document it\n * replaced is a ghost handover, reported as that. Two documents passing a\n * question back and forth still close a cycle, and so does a question handed\n * to any document other than one that replaced the document handing it.\n */\nfunction withoutHandOffs(graph: SpecGraph): SpecGraph {\n // Both ends of an edge are nodes, so the fallback is never taken.\n const owner = (id: string): string => graph.owningDocument(id)?.id ?? id;\n const successors = new Map<string, Set<string>>();\n for (const edge of graph.edges) {\n if (edge.kind !== 'supersedes') continue;\n const replaced = owner(edge.to);\n const set = successors.get(replaced) ?? new Set<string>();\n set.add(owner(edge.from));\n successors.set(replaced, set);\n }\n const kept = graph.edges.filter(\n (edge) => !(OBLIGATION_EDGES.includes(edge.kind) && successors.get(owner(edge.from))?.has(owner(edge.to))),\n );\n // Rebuilding a graph with every edge kept gives back the same graph, so the\n // comparison only saves the work: its mutants are equivalent.\n return kept.length === graph.edges.length ? graph : buildGraph({ nodes: graph.nodes, edges: kept });\n}\n\n/**\n * The relations that keep a component strongly connected.\n *\n * Membership is by owning document, because the component is a projection: the\n * edge that closes the loop is usually written on an item, and that is the line\n * the report has to point at.\n */\nfunction cycleEdges(graph: SpecGraph, component: readonly string[], kinds: readonly Edge['kind'][]): Edge[] {\n const inside = new Set(component);\n // Both ends of an edge are nodes, so the fallback is never taken.\n const owner = (id: string): string => graph.owningDocument(id)?.id ?? id;\n const out: Edge[] = [];\n for (const edge of graph.edges) {\n if (!kinds.includes(edge.kind)) continue;\n const from = owner(edge.from);\n const to = owner(edge.to);\n if (from !== to && inside.has(from) && inside.has(to)) out.push(edge);\n }\n return out;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Orphaned obligations */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The black hole itself: a document nobody reads any more, still holding work.\n *\n * Reported once per document rather than once per item. Five findings that all\n * say \"this archived ADR has open questions\" is five times the noise and none\n * of the extra information.\n */\nfunction orphanedObligations(graph: SpecGraph, emit: Emit): void {\n for (const document of graph.documents) {\n if (document.phase !== 'retired' && document.phase !== 'frozen') continue;\n const open = graph.itemsOf(document.id).filter((item) => item.openness !== 'closed' && !handedOn(graph, item.id));\n if (open.length === 0) continue;\n\n const sealed = document.phase === 'retired' ? 'retired' : 'frozen';\n emit('orphaned-obligation', () => ({\n message: `${sealed} document still holds ${open.length} open ${open.length === 1 ? 'obligation' : 'obligations'}`,\n at: document.statusAt ?? document.at,\n nodes: [document.id, ...open.map((item) => item.id)],\n // Every item, where `emit` applies the cap on related locations.\n related: open.map((item) => related(item.at, `${item.disposition}: ${item.text}`)),\n hint: `move each one to a live document or close it - as it stands, ${open.length === 1 ? 'it disappears' : 'they disappear'} with ${document.id}`,\n }));\n }\n}\n\n/**\n * Whether an item's question went on to a document that can still take it.\n *\n * ADR-0001 was superseded, and its open question is delegated to ADR-0002: the\n * question lives on in ADR-0002, which is what the finding would ask for.\n * Delegated into another sealed document, it disappears all the same - and no\n * ghost handover says so, since that rule leaves a retired source out.\n */\nfunction handedOn(graph: SpecGraph, itemId: string): boolean {\n return graph.out(itemId, ['delegates-to']).some((edge) => {\n // Both ends of an edge are nodes, and every node has a document.\n const target = graph.owningDocument(edge.to) as DocumentNode;\n return receptivityOf(target.phase) !== 'sealed';\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* Supersession consistency */\n/* -------------------------------------------------------------------------- */\n\nfunction supersessions(graph: SpecGraph, emit: Emit): void {\n for (const edge of graph.edges) {\n if (edge.kind !== 'supersedes' || edge.reflexive) continue;\n const superseded = graph.document(edge.to);\n const superseding = graph.document(edge.from);\n if (!superseded || !superseding) continue;\n // A log narrating \"ADR-0005 replaced ADR-0001\" is reporting a\n // supersession, not declaring one. Both hints below ask somebody to edit\n // the document that made the claim, and a log is the one document nobody\n // can edit - it says what was true when it was written. The exemption at\n // `emit` cannot see this: the subject here is the *other* document. An\n // archived brief that says it supersedes another is declaring it.\n if (superseding.history) continue;\n\n // Closed either way: an archived document is not live, and like a retired\n // one it can fail to say what replaced it. A log in its place is the\n // subject, and exempt at `emit`.\n const archived = superseded.phase === 'record';\n if (superseded.phase !== 'retired' && !archived && superseded.phase !== 'unknown') {\n emit('live-supersession', () => ({\n // The fix belongs in the superseded document, so that is where the\n // finding points - not at the document that made the claim.\n message: `${superseded.id} is superseded by ${superseding.id} but still reads as ${superseded.phase}`,\n at: superseded.statusAt ?? superseded.at,\n nodes: [superseded.id, superseding.id],\n related: [related(edge.declaredAt, `the supersession is declared here`)],\n hint: `mark ${superseded.id} as superseded, or drop the claim in ${superseding.id}`,\n }));\n continue;\n }\n\n if ((superseded.phase === 'retired' || archived) && !edge.declaredIn.includes(superseded.path)) {\n emit('unreciprocated-supersession', () => ({\n message: `${superseded.id} is ${archived ? 'archived' : 'retired'} but never says that ${superseding.id} replaced it`,\n at: superseded.statusAt ?? superseded.at,\n nodes: [superseded.id, superseding.id],\n related: [related(edge.declaredAt, `only ${superseding.id} records the relationship`)],\n // Archived and superseded is retired, so that status also tells what\n // rests on the document that it no longer holds, which a\n // `superseded-by` key beside `archived` would not.\n hint: archived\n ? `make the status of ${superseded.path} \"archived, superseded by ${superseding.id}\" so a reader who lands there is redirected`\n : `add \"superseded-by: ${superseding.id}\" to ${superseded.path} so a reader who lands there is redirected`,\n }));\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Item state */\n/* -------------------------------------------------------------------------- */\n\nfunction stateConflicts(graph: SpecGraph, emit: Emit): void {\n for (const item of graph.items) {\n if (item.conflicts.length === 0) continue;\n emit('state-conflict', () => ({\n message: `item reads as ${item.disposition} but also carries ${describeSignals(item)}`,\n at: item.evidence.at,\n nodes: [item.id],\n // A conflicting signal always has text: only a section's and the default\n // can be empty, and neither is ever in conflict.\n related: item.conflicts.map((signal) => related(signal.at, `${signal.source} says ${signal.disposition}: ${signal.raw}`)),\n hint: `make the two agree - spec-graph is treating it as ${item.openness}`,\n }));\n }\n}\n\nfunction describeSignals(item: ItemNode): string {\n const parts = item.conflicts.map((signal) => `a ${signal.source} saying ${signal.disposition}`);\n return parts.length === 1 ? (parts[0] as string) : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Self references */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A document that delegates to, or depends on, itself.\n *\n * Almost always a copied link that was never repointed. Informational, because\n * a self-link in a table of contents is harmless - but a *delegation* to self\n * means an obligation that looks handed off and was not.\n */\nfunction selfReferences(graph: SpecGraph, emit: Emit): void {\n for (const edge of graph.edges) {\n if (!edge.reflexive) continue;\n // `contains`, from a document to its own item, is reflexive and neither.\n const traits = EDGE_TRAITS[edge.kind];\n if (!traits.transfersObligation && !traits.loadBearing) continue;\n // The graph keeps no edge whose ends are not nodes; the guard narrows the\n // type, and its mutant is equivalent.\n const node = graph.node(edge.from);\n if (!node) continue;\n\n emit('self-reference', () => ({\n message: `${describe(node)} ${traits.phrase} itself`,\n at: edge.declaredAt,\n nodes: [edge.from],\n related: [],\n hint: traits.transfersObligation\n ? 'this obligation looks delegated but never left - point it at another document, or own it here'\n : 'a document cannot depend on itself - repoint or remove the reference',\n }));\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Project rules */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The conventions a repository wrote for itself.\n *\n * Run last, and through the same `emit` as everything else, which is the whole\n * point: a project rule inherits the record exemption, the severity table,\n * `--strict`, the related-location cap, the sort order and the baseline without\n * any of them being taught about it. See ADR-0016.\n */\nfunction projectFindings(graph: SpecGraph, rules: readonly ProjectRule[], emit: Emit): void {\n for (const rule of rules) {\n const seen = new Set<string>();\n for (const spec of rule.queries) {\n for (const match of execute(graph, spec)) {\n const nodes = match.nodes.map((node) => node.id);\n // Two selectors in one rule are a union, so a path both of them find is\n // one finding. The alternative is a repository being told twice about a\n // convention it broke once.\n const key = nodes.join('>');\n if (seen.has(key)) continue;\n seen.add(key);\n\n const head = match.nodes[0] as SpecNode;\n emit(rule.id, () => ({\n message: renderTemplate(rule.message, match, graph),\n // The line that declares the relation, not the document it points at:\n // that is where a human goes to change the answer. A rule with no\n // steps has no relation, so it points at the node it selected.\n at: match.edges[0]?.declaredAt ?? head.at,\n nodes,\n related: match.edges\n .slice(1)\n .map((edge) => related(edge.declaredAt, `${edge.from} ${EDGE_TRAITS[edge.kind].phrase} ${edge.to}`)),\n hint: renderTemplate(rule.hint, match, graph),\n }));\n }\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------------- */\n\nfunction describe(node: SpecNode): string {\n return node.kind === 'document' ? node.id : `${node.document} \"${truncate(node.text, 48)}\"`;\n}\n\nfunction truncate(text: string, limit: number): string {\n return text.length <= limit ? text : `${text.slice(0, limit - 3)}...`;\n}\n\n/** Every caller has the document in hand, so the optional chain is never taken. */\nfunction statusSuffix(document: DocumentNode | undefined): string {\n return document?.rawStatus ? ` (\"${document.rawStatus}\")` : '';\n}\n\nfunction related(at: SourceRef, note: string): RelatedLocation {\n return { at, note };\n}\n\nconst SEVERITY_ORDER: Readonly<Record<Exclude<Severity, 'off'>, number>> = { error: 0, warn: 1, info: 2 };\n\n/**\n * Total ordering, so two runs on the same corpus produce byte-identical output.\n *\n * CI output that reorders between runs cannot be diffed, and output that cannot\n * be diffed stops being read.\n */\nexport function sortDiagnostics(diagnostics: readonly Diagnostic[]): Diagnostic[] {\n return [...diagnostics].sort((a, b) => {\n const bySeverity = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity];\n if (bySeverity !== 0) return bySeverity;\n // A string comparison is reached only when the two differ, where `<` and\n // `<=` agree: those mutants are equivalent.\n if (a.at.file !== b.at.file) return a.at.file < b.at.file ? -1 : 1;\n if (a.at.span.start.line !== b.at.span.start.line) return a.at.span.start.line - b.at.span.start.line;\n if (a.at.span.start.column !== b.at.span.start.column) return a.at.span.start.column - b.at.span.start.column;\n if (a.rule !== b.rule) return a.rule < b.rule ? -1 : 1;\n return a.message < b.message ? -1 : a.message > b.message ? 1 : 0;\n });\n}\n\n/** The selector equivalent of a rule, for `spec-graph rules --explain`. */\nexport const RULE_QUERIES: Readonly<Partial<Record<RuleId, string>>> = Object.freeze({\n 'ghost-handover': '*[openness!=closed][phase!=retired] -delegates-to,blocked-by-> *[receptivity=sealed]',\n 'stale-premise': '*[phase!=retired] -depends-on,assumes,amends,blocked-by-> document[phase=retired]',\n 'orphaned-obligation':\n 'document[receptivity=sealed] -contains-> item[openness!=closed], less an item delegated to a document that is not sealed',\n 'self-reference': 'see --format json; reflexive edges are excluded from traversal by default',\n});\n"]}
|
package/dist/runner.d.ts
CHANGED
|
@@ -64,6 +64,7 @@ export interface Source {
|
|
|
64
64
|
}
|
|
65
65
|
export interface AnalyseSourcesOptions extends RuleOptions {
|
|
66
66
|
readonly fileExists?: ((path: string) => boolean) | undefined;
|
|
67
|
+
readonly isDirectory?: ((path: string) => boolean) | undefined;
|
|
67
68
|
readonly isIgnoredReference?: ((target: string) => boolean) | undefined;
|
|
68
69
|
readonly isIgnoredFamily?: ((family: string) => boolean) | undefined;
|
|
69
70
|
/** Whether a path holds a historical record rather than a specification. */
|
package/dist/runner.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* corpora in memory and why a build script can feed spec-graph documents that
|
|
7
7
|
* never existed on disk.
|
|
8
8
|
*/
|
|
9
|
-
import { existsSync } from 'node:fs';
|
|
9
|
+
import { existsSync, statSync } from 'node:fs';
|
|
10
10
|
import { readFile } from 'node:fs/promises';
|
|
11
11
|
import { extractSpecifications } from './extract.js';
|
|
12
12
|
import { createGlobMatcher, createReferenceFilter, walkFiles } from './glob.js';
|
|
@@ -47,6 +47,7 @@ export async function analyse(options) {
|
|
|
47
47
|
// "that document does not exist" and "that document exists but your
|
|
48
48
|
// include patterns did not reach it", which are different fixes.
|
|
49
49
|
fileExists: (path) => present.has(path.toLowerCase()) || existsSync(`${root}/${path}`),
|
|
50
|
+
isDirectory: (path) => statSync(`${root}/${path}`, { throwIfNoEntry: false })?.isDirectory() === true,
|
|
50
51
|
// A stray entry in the empty fallback changes a run only for a reference
|
|
51
52
|
// whose target it matches, so the mutant that plants one survives every
|
|
52
53
|
// corpus but one written for it.
|
|
@@ -109,6 +110,7 @@ export function analyseSources(sources, options = {}) {
|
|
|
109
110
|
}
|
|
110
111
|
const corpus = resolveCorpus(extracted, {
|
|
111
112
|
fileExists: options.fileExists,
|
|
113
|
+
isDirectory: options.isDirectory,
|
|
112
114
|
isIgnoredReference: options.isIgnoredReference,
|
|
113
115
|
isIgnoredFamily: options.isIgnoredFamily,
|
|
114
116
|
});
|
package/dist/runner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,qBAAqB,EAA0B,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,SAAS,EAAmB,MAAM,WAAW,CAAC;AACjG,OAAO,EAAE,UAAU,EAAkB,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,aAAa,EAAuB,MAAM,cAAc,CAAC;AAElE,OAAO,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAGxD,gDAAgD;AAChD,MAAM,CAAC,MAAM,gBAAgB,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC/D,cAAc;IACd,aAAa;IACb,aAAa;IACb,cAAc;IACd,eAAe;IACf,MAAM;CACP,CAAC,CAAC;AAEH,uCAAuC;AACvC,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AA4CtC,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,OAAuB;IACnD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAClC,wEAAwE;IACxE,yEAAyE;IACzE,0EAA0E;IAC1E,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAEvG,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC;QAC7B,IAAI;QACJ,QAAQ;QACR,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,cAAc,EAAE,OAAO,CAAC,cAAc;KACvC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,IAAI,mBAAmB,CAAC,CAAC;IAClF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAEvE,OAAO,MAAM,CACX,cAAc,CAAC,OAAO,EAAE;QACtB,uEAAuE;QACvE,yEAAyE;QACzE,oEAAoE;QACpE,iEAAiE;QACjE,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,UAAU,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;QACtF,yEAAyE;QACzE,wEAAwE;QACxE,iCAAiC;QACjC,kBAAkB,EAAE,qBAAqB,CAAC,OAAO,CAAC,gBAAgB,IAAI,EAAE,CAAC;QACzE,eAAe,EAAE,kBAAkB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,cAAc,CAAC;QAC7E,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,eAAe,CAAC;QACvD,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,UAAU,EAAE,OAAO,CAAC,UAAU;KAC/B,CAAC,EACF,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAC/B,OAAO,CACR,CAAC;AACJ,CAAC;AAgBD;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAuC;IAC1E,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACtE,OAAO,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAuC,EACvC,cAA6C;IAE7C,MAAM,OAAO,GAAG,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9G,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAClF,IAAI,OAAO,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5D,OAAO,CAAC,MAAc,EAAW,EAAE;QACjC,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;QACjC,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACjC,OAAO,OAAO,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC/C,CAAC,CAAC;AACJ,CAAC;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAA0B,EAAE,OAAO,GAA0B,EAAE;IAC5F,MAAM,SAAS,GAAwB,EAAE,CAAC;IAC1C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,SAAS,CAAC,IAAI,CACZ,GAAG,qBAAqB,CAAC;YACvB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI;SACjD,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,EAAE;QACtC,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;QAC9C,eAAe,EAAE,OAAO,CAAC,eAAe;KACzC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IACvE,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACrD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AACxC,CAAC;AAED,SAAS,MAAM,CAAC,QAAkB,EAAE,KAAwB,EAAE,OAAe;IAC3E,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,QAAQ,CAAC;IAChD,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAC9C,KAAK,MAAM,UAAU,IAAI,WAAW;QAAE,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACvE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAE3G,MAAM,OAAO,GAAoB;QAC/B,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM;QAClC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;QAC1B,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM;QACzB,wEAAwE;QACxE,0EAA0E;QAC1E,eAAe,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM;QAChH,MAAM,EAAE,MAAM,CAAC,KAAK;QACpB,QAAQ,EAAE,MAAM,CAAC,IAAI;QACrB,KAAK,EAAE,MAAM,CAAC,IAAI;QAClB,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG;KAClE,CAAC;IAEF,OAAO;QACL,KAAK;QACL,MAAM;QACN,WAAW;QACX,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,KAAK;QACL,OAAO;QACP,EAAE,EAAE,MAAM,CAAC,KAAK,KAAK,CAAC;KACvB,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,KAAK,UAAU,OAAO,CAAC,KAA4B,EAAE,WAAmB;IACtE,2EAA2E;IAC3E,0EAA0E;IAC1E,MAAM,GAAG,GAAa,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,MAAM,MAAM,GAAG,KAAK,IAAmB,EAAE;QACvC,SAAS,CAAC;YACR,MAAM,KAAK,GAAG,MAAM,CAAC;YACrB,MAAM,IAAI,CAAC,CAAC;YACZ,uEAAuE;YACvE,yEAAyE;YACzE,wBAAwB;YACxB,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM;gBAAE,OAAO;YAClC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAe,CAAC;YACxC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBACnD,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;gBACzC,OAAO,IAAI,CAAC,CAAC;YACf,CAAC;YAAC,MAAM,CAAC;gBACP,mEAAmE;YACrE,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,wEAAwE;IACxE,6EAA6E;IAC7E,6EAA6E;IAC7E,8BAA8B;IAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACtG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3B,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC;IACrB,0EAA0E;IAC1E,4EAA4E;IAC5E,6EAA6E;IAC7E,uDAAuD;IACvD,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,MAAsB,EAAE,WAAkC;IACxF,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAC9C,KAAK,MAAM,UAAU,IAAI,WAAW;QAAE,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACvE,OAAO;QACL,GAAG,MAAM;QACT,WAAW;QACX,OAAO,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE;QAC/F,EAAE,EAAE,MAAM,CAAC,KAAK,KAAK,CAAC;KACvB,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Orchestration: files in, graph and findings out.\n *\n * The only part of spec-graph that touches the filesystem. Everything below it\n * is a pure function of text, which is why the test suite can build pathological\n * corpora in memory and why a build script can feed spec-graph documents that\n * never existed on disk.\n */\n\nimport { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\n\nimport { extractSpecifications, type ExtractedDocument } from './extract.js';\nimport { createGlobMatcher, createReferenceFilter, walkFiles, type WalkedFile } from './glob.js';\nimport { buildGraph, type SpecGraph } from './graph.js';\nimport { toPosix } from './paths.js';\nimport { resolveCorpus, type ResolvedCorpus } from './resolve.js';\nimport type { ProjectRule } from './project-rules.js';\nimport { runRules, type RuleOptions } from './rules.js';\nimport type { AnalysisSummary, AnyRuleId, Diagnostic, ParseProblem, Severity } from './types.js';\n\n/** Patterns used when the caller names none. */\nexport const DEFAULT_PATTERNS: readonly string[] = Object.freeze([\n 'docs/**/*.md',\n 'doc/**/*.md',\n 'adr/**/*.md',\n 'rfcs/**/*.md',\n 'specs/**/*.md',\n '*.md',\n]);\n\n/** How many files are read at once. */\nexport const DEFAULT_CONCURRENCY = 16;\n\nexport interface AnalyseOptions {\n /** Absolute or relative directory the patterns are resolved against. */\n readonly root: string;\n readonly patterns?: readonly string[] | undefined;\n readonly ignore?: readonly string[] | undefined;\n /**\n * Reference targets to leave unreported when they do not resolve.\n *\n * For repositories where `[[...]]` tags concepts rather than naming files.\n * Suppresses findings only; a reference that resolves is still an edge.\n */\n readonly ignoreReferences?: readonly string[] | undefined;\n /** Families a bare identifier may name. Unset means any family in the corpus. */\n readonly families?: readonly string[] | undefined;\n /** Families that are never citations, whatever the corpus contains. */\n readonly ignoreFamilies?: readonly string[] | undefined;\n /**\n * Files that are logs of what was decided rather than decisions.\n *\n * Journals, changelogs, meeting minutes, sprint summaries. Their references\n * are still checked; their obligations and lifecycle are not. See ADR-0011.\n */\n readonly historyPatterns?: readonly string[] | undefined;\n readonly severities?: Partial<Record<AnyRuleId, Severity>> | undefined;\n /** Conventions the repository wrote for itself. See ADR-0016. */\n readonly projectRules?: readonly ProjectRule[] | undefined;\n readonly concurrency?: number | undefined;\n readonly maxRelated?: number | undefined;\n readonly followSymlinks?: boolean | undefined;\n}\n\nexport interface AnalysisResult {\n readonly graph: SpecGraph;\n readonly corpus: ResolvedCorpus;\n readonly diagnostics: readonly Diagnostic[];\n readonly problems: readonly ParseProblem[];\n readonly files: readonly string[];\n readonly summary: AnalysisSummary;\n /** True when nothing at error severity was found. */\n readonly ok: boolean;\n}\n\n/** Reads, extracts, resolves, and checks a corpus of specifications. */\nexport async function analyse(options: AnalyseOptions): Promise<AnalysisResult> {\n const started = performance.now();\n // The walk strips the root again, and past it the root only prefixes an\n // existence check, where `docs//x.md` names what `docs/x.md` does - so a\n // mutant that strips one separator rather than all of them is equivalent.\n const root = toPosix(options.root).replace(/\\/+$/, '');\n const patterns = options.patterns && options.patterns.length > 0 ? options.patterns : DEFAULT_PATTERNS;\n\n const walked = await walkFiles({\n root,\n patterns,\n ignore: options.ignore,\n followSymlinks: options.followSymlinks,\n });\n\n const sources = await readAll(walked, options.concurrency ?? DEFAULT_CONCURRENCY);\n const present = new Set(walked.map((file) => file.path.toLowerCase()));\n\n return finish(\n analyseSources(sources, {\n // Consulted only for references that failed to resolve, so the cost is\n // bounded by the number of findings - and it buys the difference between\n // \"that document does not exist\" and \"that document exists but your\n // include patterns did not reach it\", which are different fixes.\n fileExists: (path) => present.has(path.toLowerCase()) || existsSync(`${root}/${path}`),\n // A stray entry in the empty fallback changes a run only for a reference\n // whose target it matches, so the mutant that plants one survives every\n // corpus but one written for it.\n isIgnoredReference: createReferenceFilter(options.ignoreReferences ?? []),\n isIgnoredFamily: createFamilyFilter(options.families, options.ignoreFamilies),\n isRecord: createHistoryMatcher(options.historyPatterns),\n severities: options.severities,\n projectRules: options.projectRules,\n maxRelated: options.maxRelated,\n }),\n walked.map((file) => file.path),\n started,\n );\n}\n\nexport interface Source {\n /** Repository-relative POSIX path. */\n readonly path: string;\n readonly text: string;\n}\n\nexport interface AnalyseSourcesOptions extends RuleOptions {\n readonly fileExists?: ((path: string) => boolean) | undefined;\n readonly isIgnoredReference?: ((target: string) => boolean) | undefined;\n readonly isIgnoredFamily?: ((family: string) => boolean) | undefined;\n /** Whether a path holds a historical record rather than a specification. */\n readonly isRecord?: ((path: string) => boolean) | undefined;\n}\n\n/**\n * Builds the record predicate, or nothing when no patterns were given.\n *\n * Returning `undefined` rather than a predicate that always says no keeps the\n * common case free of a call per file, and makes \"no history patterns\" visible\n * as an absent option rather than as a function nobody can read.\n */\nexport function createHistoryMatcher(patterns: readonly string[] | undefined): ((path: string) => boolean) | undefined {\n if (patterns === undefined || patterns.length === 0) return undefined;\n return createGlobMatcher(patterns);\n}\n\n/**\n * Builds the family predicate from an allowlist and a denylist.\n *\n * An allowlist is the stronger statement - \"these are the families this\n * repository has\" - and turns every other noun-number construct back into\n * prose. A denylist handles the narrower case where the corpus genuinely owns a\n * family but some of its numbers belong to somebody else, which is exactly\n * `RFC 2119` in a repository of local RFCs.\n */\nexport function createFamilyFilter(\n families: readonly string[] | undefined,\n ignoreFamilies: readonly string[] | undefined,\n): ((family: string) => boolean) | undefined {\n const allowed = families && families.length > 0 ? new Set(families.map((f) => f.trim().toUpperCase())) : null;\n const denied = new Set((ignoreFamilies ?? []).map((f) => f.trim().toUpperCase()));\n if (allowed === null && denied.size === 0) return undefined;\n return (family: string): boolean => {\n const key = family.toUpperCase();\n if (denied.has(key)) return true;\n return allowed !== null && !allowed.has(key);\n };\n}\n\ninterface Analysed {\n readonly graph: SpecGraph;\n readonly corpus: ResolvedCorpus;\n readonly diagnostics: readonly Diagnostic[];\n}\n\n/**\n * The whole engine, without a filesystem.\n *\n * Exported because it is the honest unit of composition: a monorepo tool that\n * already has the file contents in memory should not have to write them out to\n * a temporary directory to use spec-graph.\n */\nexport function analyseSources(sources: readonly Source[], options: AnalyseSourcesOptions = {}): Analysed {\n const extracted: ExtractedDocument[] = [];\n for (const source of sources) {\n extracted.push(\n ...extractSpecifications({\n path: source.path,\n text: source.text,\n record: options.isRecord?.(source.path) === true,\n }),\n );\n }\n\n const corpus = resolveCorpus(extracted, {\n fileExists: options.fileExists,\n isIgnoredReference: options.isIgnoredReference,\n isIgnoredFamily: options.isIgnoredFamily,\n });\n const graph = buildGraph({ nodes: corpus.nodes, edges: corpus.edges });\n const diagnostics = runRules(graph, corpus, options);\n return { graph, corpus, diagnostics };\n}\n\nfunction finish(analysed: Analysed, files: readonly string[], started: number): AnalysisResult {\n const { graph, corpus, diagnostics } = analysed;\n const counts = { error: 0, warn: 0, info: 0 };\n for (const diagnostic of diagnostics) counts[diagnostic.severity] += 1;\n const records = new Set(corpus.documents.filter((node) => node.phase === 'record').map((node) => node.id));\n\n const summary: AnalysisSummary = {\n documents: corpus.documents.length,\n items: corpus.items.length,\n edges: graph.edges.length,\n // A record's unchecked boxes are minutes, not work. Counting them would\n // make the headline number the one thing in the report nobody can act on.\n openObligations: corpus.items.filter((item) => item.openness !== 'closed' && !records.has(item.document)).length,\n errors: counts.error,\n warnings: counts.warn,\n infos: counts.info,\n durationMs: Math.round((performance.now() - started) * 100) / 100,\n };\n\n return {\n graph,\n corpus,\n diagnostics,\n problems: corpus.problems,\n files,\n summary,\n ok: counts.error === 0,\n };\n}\n\n/** Reads files with bounded concurrency, skipping ones that cannot be read. */\nasync function readAll(files: readonly WalkedFile[], concurrency: number): Promise<Source[]> {\n // Pre-sized for throughput only: the length is set to what was read below,\n // so an array that starts empty ends the same. That mutant is equivalent.\n const out: Source[] = new Array(files.length);\n let cursor = 0;\n let written = 0;\n\n const worker = async (): Promise<void> => {\n for (;;) {\n const index = cursor;\n cursor += 1;\n // `>` for `>=` lets one worker take the index past the end, whose read\n // throws inside the `try` below and is skipped like any unreadable file:\n // an equivalent mutant.\n if (index >= files.length) return;\n const file = files[index] as WalkedFile;\n try {\n const text = await readFile(file.absolute, 'utf8');\n out[written] = { path: file.path, text };\n written += 1;\n } catch {\n // A file that vanished or cannot be decoded is skipped, not fatal.\n }\n }\n };\n\n // How many workers there are, once there is one, changes throughput and\n // nothing a caller sees: one reads every file, and each worker past the last\n // file returns at once. The mutants that only move the count are equivalent,\n // and no test counts workers.\n const workers = Array.from({ length: Math.max(1, Math.min(concurrency, files.length || 1)) }, worker);\n await Promise.all(workers);\n out.length = written;\n // Deterministic order regardless of which worker finished first. The walk\n // hands over each path once, and the sort acts only on whether a comparison\n // is negative, so the mutants that change its answer for two equal paths, or\n // give 0 rather than 1 for a later one, sort the same.\n return out.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n}\n\n/**\n * Rebuilds a result around a narrower set of findings.\n *\n * The corpus, the graph and the counts of what is in them are unchanged - a\n * baseline suppresses findings, never facts. Only the tallies that describe the\n * findings, and the verdict that follows from them, move.\n */\nexport function withDiagnostics(result: AnalysisResult, diagnostics: readonly Diagnostic[]): AnalysisResult {\n const counts = { error: 0, warn: 0, info: 0 };\n for (const diagnostic of diagnostics) counts[diagnostic.severity] += 1;\n return {\n ...result,\n diagnostics,\n summary: { ...result.summary, errors: counts.error, warnings: counts.warn, infos: counts.info },\n ok: counts.error === 0,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Orchestration: files in, graph and findings out.\n *\n * The only part of spec-graph that touches the filesystem. Everything below it\n * is a pure function of text, which is why the test suite can build pathological\n * corpora in memory and why a build script can feed spec-graph documents that\n * never existed on disk.\n */\n\nimport { existsSync, statSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\n\nimport { extractSpecifications, type ExtractedDocument } from './extract.js';\nimport { createGlobMatcher, createReferenceFilter, walkFiles, type WalkedFile } from './glob.js';\nimport { buildGraph, type SpecGraph } from './graph.js';\nimport { toPosix } from './paths.js';\nimport { resolveCorpus, type ResolvedCorpus } from './resolve.js';\nimport type { ProjectRule } from './project-rules.js';\nimport { runRules, type RuleOptions } from './rules.js';\nimport type { AnalysisSummary, AnyRuleId, Diagnostic, ParseProblem, Severity } from './types.js';\n\n/** Patterns used when the caller names none. */\nexport const DEFAULT_PATTERNS: readonly string[] = Object.freeze([\n 'docs/**/*.md',\n 'doc/**/*.md',\n 'adr/**/*.md',\n 'rfcs/**/*.md',\n 'specs/**/*.md',\n '*.md',\n]);\n\n/** How many files are read at once. */\nexport const DEFAULT_CONCURRENCY = 16;\n\nexport interface AnalyseOptions {\n /** Absolute or relative directory the patterns are resolved against. */\n readonly root: string;\n readonly patterns?: readonly string[] | undefined;\n readonly ignore?: readonly string[] | undefined;\n /**\n * Reference targets to leave unreported when they do not resolve.\n *\n * For repositories where `[[...]]` tags concepts rather than naming files.\n * Suppresses findings only; a reference that resolves is still an edge.\n */\n readonly ignoreReferences?: readonly string[] | undefined;\n /** Families a bare identifier may name. Unset means any family in the corpus. */\n readonly families?: readonly string[] | undefined;\n /** Families that are never citations, whatever the corpus contains. */\n readonly ignoreFamilies?: readonly string[] | undefined;\n /**\n * Files that are logs of what was decided rather than decisions.\n *\n * Journals, changelogs, meeting minutes, sprint summaries. Their references\n * are still checked; their obligations and lifecycle are not. See ADR-0011.\n */\n readonly historyPatterns?: readonly string[] | undefined;\n readonly severities?: Partial<Record<AnyRuleId, Severity>> | undefined;\n /** Conventions the repository wrote for itself. See ADR-0016. */\n readonly projectRules?: readonly ProjectRule[] | undefined;\n readonly concurrency?: number | undefined;\n readonly maxRelated?: number | undefined;\n readonly followSymlinks?: boolean | undefined;\n}\n\nexport interface AnalysisResult {\n readonly graph: SpecGraph;\n readonly corpus: ResolvedCorpus;\n readonly diagnostics: readonly Diagnostic[];\n readonly problems: readonly ParseProblem[];\n readonly files: readonly string[];\n readonly summary: AnalysisSummary;\n /** True when nothing at error severity was found. */\n readonly ok: boolean;\n}\n\n/** Reads, extracts, resolves, and checks a corpus of specifications. */\nexport async function analyse(options: AnalyseOptions): Promise<AnalysisResult> {\n const started = performance.now();\n // The walk strips the root again, and past it the root only prefixes an\n // existence check, where `docs//x.md` names what `docs/x.md` does - so a\n // mutant that strips one separator rather than all of them is equivalent.\n const root = toPosix(options.root).replace(/\\/+$/, '');\n const patterns = options.patterns && options.patterns.length > 0 ? options.patterns : DEFAULT_PATTERNS;\n\n const walked = await walkFiles({\n root,\n patterns,\n ignore: options.ignore,\n followSymlinks: options.followSymlinks,\n });\n\n const sources = await readAll(walked, options.concurrency ?? DEFAULT_CONCURRENCY);\n const present = new Set(walked.map((file) => file.path.toLowerCase()));\n\n return finish(\n analyseSources(sources, {\n // Consulted only for references that failed to resolve, so the cost is\n // bounded by the number of findings - and it buys the difference between\n // \"that document does not exist\" and \"that document exists but your\n // include patterns did not reach it\", which are different fixes.\n fileExists: (path) => present.has(path.toLowerCase()) || existsSync(`${root}/${path}`),\n isDirectory: (path) => statSync(`${root}/${path}`, { throwIfNoEntry: false })?.isDirectory() === true,\n // A stray entry in the empty fallback changes a run only for a reference\n // whose target it matches, so the mutant that plants one survives every\n // corpus but one written for it.\n isIgnoredReference: createReferenceFilter(options.ignoreReferences ?? []),\n isIgnoredFamily: createFamilyFilter(options.families, options.ignoreFamilies),\n isRecord: createHistoryMatcher(options.historyPatterns),\n severities: options.severities,\n projectRules: options.projectRules,\n maxRelated: options.maxRelated,\n }),\n walked.map((file) => file.path),\n started,\n );\n}\n\nexport interface Source {\n /** Repository-relative POSIX path. */\n readonly path: string;\n readonly text: string;\n}\n\nexport interface AnalyseSourcesOptions extends RuleOptions {\n readonly fileExists?: ((path: string) => boolean) | undefined;\n readonly isDirectory?: ((path: string) => boolean) | undefined;\n readonly isIgnoredReference?: ((target: string) => boolean) | undefined;\n readonly isIgnoredFamily?: ((family: string) => boolean) | undefined;\n /** Whether a path holds a historical record rather than a specification. */\n readonly isRecord?: ((path: string) => boolean) | undefined;\n}\n\n/**\n * Builds the record predicate, or nothing when no patterns were given.\n *\n * Returning `undefined` rather than a predicate that always says no keeps the\n * common case free of a call per file, and makes \"no history patterns\" visible\n * as an absent option rather than as a function nobody can read.\n */\nexport function createHistoryMatcher(patterns: readonly string[] | undefined): ((path: string) => boolean) | undefined {\n if (patterns === undefined || patterns.length === 0) return undefined;\n return createGlobMatcher(patterns);\n}\n\n/**\n * Builds the family predicate from an allowlist and a denylist.\n *\n * An allowlist is the stronger statement - \"these are the families this\n * repository has\" - and turns every other noun-number construct back into\n * prose. A denylist handles the narrower case where the corpus genuinely owns a\n * family but some of its numbers belong to somebody else, which is exactly\n * `RFC 2119` in a repository of local RFCs.\n */\nexport function createFamilyFilter(\n families: readonly string[] | undefined,\n ignoreFamilies: readonly string[] | undefined,\n): ((family: string) => boolean) | undefined {\n const allowed = families && families.length > 0 ? new Set(families.map((f) => f.trim().toUpperCase())) : null;\n const denied = new Set((ignoreFamilies ?? []).map((f) => f.trim().toUpperCase()));\n if (allowed === null && denied.size === 0) return undefined;\n return (family: string): boolean => {\n const key = family.toUpperCase();\n if (denied.has(key)) return true;\n return allowed !== null && !allowed.has(key);\n };\n}\n\ninterface Analysed {\n readonly graph: SpecGraph;\n readonly corpus: ResolvedCorpus;\n readonly diagnostics: readonly Diagnostic[];\n}\n\n/**\n * The whole engine, without a filesystem.\n *\n * Exported because it is the honest unit of composition: a monorepo tool that\n * already has the file contents in memory should not have to write them out to\n * a temporary directory to use spec-graph.\n */\nexport function analyseSources(sources: readonly Source[], options: AnalyseSourcesOptions = {}): Analysed {\n const extracted: ExtractedDocument[] = [];\n for (const source of sources) {\n extracted.push(\n ...extractSpecifications({\n path: source.path,\n text: source.text,\n record: options.isRecord?.(source.path) === true,\n }),\n );\n }\n\n const corpus = resolveCorpus(extracted, {\n fileExists: options.fileExists,\n isDirectory: options.isDirectory,\n isIgnoredReference: options.isIgnoredReference,\n isIgnoredFamily: options.isIgnoredFamily,\n });\n const graph = buildGraph({ nodes: corpus.nodes, edges: corpus.edges });\n const diagnostics = runRules(graph, corpus, options);\n return { graph, corpus, diagnostics };\n}\n\nfunction finish(analysed: Analysed, files: readonly string[], started: number): AnalysisResult {\n const { graph, corpus, diagnostics } = analysed;\n const counts = { error: 0, warn: 0, info: 0 };\n for (const diagnostic of diagnostics) counts[diagnostic.severity] += 1;\n const records = new Set(corpus.documents.filter((node) => node.phase === 'record').map((node) => node.id));\n\n const summary: AnalysisSummary = {\n documents: corpus.documents.length,\n items: corpus.items.length,\n edges: graph.edges.length,\n // A record's unchecked boxes are minutes, not work. Counting them would\n // make the headline number the one thing in the report nobody can act on.\n openObligations: corpus.items.filter((item) => item.openness !== 'closed' && !records.has(item.document)).length,\n errors: counts.error,\n warnings: counts.warn,\n infos: counts.info,\n durationMs: Math.round((performance.now() - started) * 100) / 100,\n };\n\n return {\n graph,\n corpus,\n diagnostics,\n problems: corpus.problems,\n files,\n summary,\n ok: counts.error === 0,\n };\n}\n\n/** Reads files with bounded concurrency, skipping ones that cannot be read. */\nasync function readAll(files: readonly WalkedFile[], concurrency: number): Promise<Source[]> {\n // Pre-sized for throughput only: the length is set to what was read below,\n // so an array that starts empty ends the same. That mutant is equivalent.\n const out: Source[] = new Array(files.length);\n let cursor = 0;\n let written = 0;\n\n const worker = async (): Promise<void> => {\n for (;;) {\n const index = cursor;\n cursor += 1;\n // `>` for `>=` lets one worker take the index past the end, whose read\n // throws inside the `try` below and is skipped like any unreadable file:\n // an equivalent mutant.\n if (index >= files.length) return;\n const file = files[index] as WalkedFile;\n try {\n const text = await readFile(file.absolute, 'utf8');\n out[written] = { path: file.path, text };\n written += 1;\n } catch {\n // A file that vanished or cannot be decoded is skipped, not fatal.\n }\n }\n };\n\n // How many workers there are, once there is one, changes throughput and\n // nothing a caller sees: one reads every file, and each worker past the last\n // file returns at once. The mutants that only move the count are equivalent,\n // and no test counts workers.\n const workers = Array.from({ length: Math.max(1, Math.min(concurrency, files.length || 1)) }, worker);\n await Promise.all(workers);\n out.length = written;\n // Deterministic order regardless of which worker finished first. The walk\n // hands over each path once, and the sort acts only on whether a comparison\n // is negative, so the mutants that change its answer for two equal paths, or\n // give 0 rather than 1 for a later one, sort the same.\n return out.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n}\n\n/**\n * Rebuilds a result around a narrower set of findings.\n *\n * The corpus, the graph and the counts of what is in them are unchanged - a\n * baseline suppresses findings, never facts. Only the tallies that describe the\n * findings, and the verdict that follows from them, move.\n */\nexport function withDiagnostics(result: AnalysisResult, diagnostics: readonly Diagnostic[]): AnalysisResult {\n const counts = { error: 0, warn: 0, info: 0 };\n for (const diagnostic of diagnostics) counts[diagnostic.severity] += 1;\n return {\n ...result,\n diagnostics,\n summary: { ...result.summary, errors: counts.error, warnings: counts.warn, infos: counts.info },\n ok: counts.error === 0,\n };\n}\n"]}
|
package/package.json
CHANGED