@descent-vtt/spec-graph 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/resolve.js CHANGED
@@ -17,8 +17,8 @@
17
17
  * documents is reported as ambiguous rather than silently bound to whichever
18
18
  * one happened to be indexed first.
19
19
  */
20
- import { isDocumentTarget, isExternal, looksLikePath, normaliseRef, parseBareRef, parsePrefixedRef, splitAnchor, } from './identity.js';
21
- import { basenamePosix, extnamePosix, resolveFrom } from './paths.js';
20
+ import { isDocumentTarget, isExternal, looksLikePath, normaliseRef, parseBareRef, parsePrefixedRef, splitAnchor, withinOneEdit, } from './identity.js';
21
+ import { basenamePosix, dirnamePosix, extnamePosix, resolveFrom } from './paths.js';
22
22
  /** Resolves every extracted reference against the whole corpus. */
23
23
  export function resolveCorpus(extracted, options = {}) {
24
24
  const index = buildIndex(extracted);
@@ -47,6 +47,7 @@ export function resolveCorpus(extracted, options = {}) {
47
47
  }
48
48
  const edges = [];
49
49
  const dangling = [];
50
+ const suppressed = [];
50
51
  const byKey = new Map();
51
52
  // Structural edges: a register contains the specifications written inside it.
52
53
  for (const entry of extracted) {
@@ -85,6 +86,10 @@ export function resolveCorpus(extracted, options = {}) {
85
86
  const outcome = resolveOne(candidate, entry, index, nodes, options);
86
87
  if (outcome === null)
87
88
  continue;
89
+ if ('by' in outcome) {
90
+ suppressed.push(outcome);
91
+ continue;
92
+ }
88
93
  if ('reason' in outcome) {
89
94
  dangling.push(outcome);
90
95
  continue;
@@ -105,7 +110,7 @@ export function resolveCorpus(extracted, options = {}) {
105
110
  edges.push(outcome);
106
111
  }
107
112
  }
108
- return { nodes, documents, items, edges, dangling, problems, misreadKeys };
113
+ return { nodes, documents, items, edges, dangling, problems, misreadKeys, suppressed };
109
114
  }
110
115
  /* -------------------------------------------------------------------------- */
111
116
  /* Indexing */
@@ -118,6 +123,8 @@ function buildIndex(extracted) {
118
123
  byPathAlias: new Map(),
119
124
  byFamilyNumber: new Map(),
120
125
  families: new Set(),
126
+ byDirectory: new Map(),
127
+ aliasesByLength: new Map(),
121
128
  anchors: new Map(),
122
129
  itemIds: new Set(),
123
130
  familyOf: new Map(),
@@ -142,12 +149,17 @@ function buildIndex(extracted) {
142
149
  // ADR-0009 said a region does not claim the file's path; this is the index
143
150
  // where that had to be true.
144
151
  if (entry.containerId === null) {
145
- index.byPath.set(entry.document.path.toLowerCase(), id);
152
+ const path = entry.document.path.toLowerCase();
153
+ index.byPath.set(path, id);
146
154
  for (const spelling of pathAliases(entry.document.path)) {
147
155
  const set = index.byPathAlias.get(spelling) ?? new Set();
148
156
  set.add(id);
149
157
  index.byPathAlias.set(spelling, set);
150
158
  }
159
+ const directory = dirnamePosix(path);
160
+ const siblings = index.byDirectory.get(directory) ?? [];
161
+ siblings.push({ stem: stemOf(path), id });
162
+ index.byDirectory.set(directory, siblings);
151
163
  }
152
164
  if (entry.identity.family !== null && entry.identity.number !== null) {
153
165
  index.families.add(entry.identity.family);
@@ -159,8 +171,18 @@ function buildIndex(extracted) {
159
171
  for (const item of entry.items)
160
172
  index.itemIds.add(item.id);
161
173
  }
174
+ for (const alias of index.byAlias.keys()) {
175
+ const bucket = index.aliasesByLength.get(alias.length) ?? [];
176
+ bucket.push(alias);
177
+ index.aliasesByLength.set(alias.length, bucket);
178
+ }
162
179
  return index;
163
180
  }
181
+ /** A file name with its extension taken off: `docs/a/0004-sharding.md` -> `0004-sharding`. */
182
+ function stemOf(path) {
183
+ const base = basenamePosix(path);
184
+ return base.slice(0, base.length - extnamePosix(base).length);
185
+ }
164
186
  /** Every spelling of a path that addresses a file without naming it exactly. */
165
187
  function pathAliases(path) {
166
188
  const keys = new Set();
@@ -200,8 +222,9 @@ function resolveOne(candidate, entry, index, nodes, options) {
200
222
  if (found.ids.length === 0) {
201
223
  if (candidate.opportunistic && !worthReporting(bare, index))
202
224
  return null;
203
- if (isFiltered(candidate.target, bare, options))
204
- return null;
225
+ const silenced = filteredBy(candidate.target, bare, options);
226
+ if (silenced !== null)
227
+ return suppression(candidate, silenced);
205
228
  // A trailing slash names a directory. Linking to one is ordinary - "the
206
229
  // decisions live in [archive/](archive/)" - and is not a citation of any
207
230
  // document. Resolution is still attempted first, so a directory-style
@@ -214,8 +237,9 @@ function resolveOne(candidate, entry, index, nodes, options) {
214
237
  if (found.ids.length > 1) {
215
238
  if (candidate.opportunistic)
216
239
  return null;
217
- if (isFiltered(candidate.target, bare, options))
218
- return null;
240
+ const silenced = filteredBy(candidate.target, bare, options);
241
+ if (silenced !== null)
242
+ return suppression(candidate, silenced);
219
243
  return dangle(candidate, 'ambiguous', found.ids);
220
244
  }
221
245
  const documentId = found.ids[0];
@@ -249,13 +273,18 @@ function percentDecoded(target) {
249
273
  }
250
274
  }
251
275
  function lookup(target, entry, index) {
252
- const found = lookupExact(target, entry, index);
253
- if (found.ids.length > 0)
254
- return found;
276
+ const exact = lookupExact(target, entry, index);
277
+ if (exact.length > 0)
278
+ return { ids: exact, near: [] };
255
279
  // Tried second and never first, so a repository holding a file whose name
256
280
  // genuinely contains a percent escape keeps resolving by its written spelling.
257
281
  const decoded = percentDecoded(target);
258
- return decoded === null ? found : lookupExact(decoded, entry, index);
282
+ const relaxed = decoded === null ? [] : lookupExact(decoded, entry, index);
283
+ if (relaxed.length > 0)
284
+ return { ids: relaxed, near: [] };
285
+ // Suggestions are computed only once resolution has failed outright, so their
286
+ // cost is bounded by the number of findings rather than by the corpus.
287
+ return { ids: [], near: nearMisses(target, entry, index) };
259
288
  }
260
289
  function lookupExact(target, entry, index) {
261
290
  if (looksLikePath(target)) {
@@ -264,7 +293,7 @@ function lookupExact(target, entry, index) {
264
293
  // same way.
265
294
  const byPath = index.byPath.get(resolved);
266
295
  if (byPath)
267
- return { ids: [byPath], near: [] };
296
+ return [byPath];
268
297
  // An inexact spelling can belong to more than one file: `docs/A` is both
269
298
  // `docs/A.md` with its extension dropped and `docs/A/README.md` standing
270
299
  // for its directory. Handing back every claimant makes that an
@@ -272,29 +301,29 @@ function lookupExact(target, entry, index) {
272
301
  // whichever happened to be indexed last.
273
302
  const spelled = index.byPathAlias.get(resolved);
274
303
  if (spelled && spelled.size > 0)
275
- return { ids: [...spelled], near: [] };
304
+ return [...spelled];
276
305
  // A path may still be spelled as an identifier in a nested folder layout.
277
306
  const stem = basenamePosix(resolved);
278
307
  const byStem = index.byAlias.get(normaliseRef(stem.replace(/\.[^.]+$/, '')));
279
308
  if (byStem && byStem.size > 0)
280
- return { ids: [...byStem], near: [] };
281
- return { ids: [], near: [] };
309
+ return [...byStem];
310
+ return [];
282
311
  }
283
312
  const key = normaliseRef(target);
284
313
  if (key.length === 0)
285
- return { ids: [], near: [] };
314
+ return [];
286
315
  const byId = index.byId.get(key);
287
316
  if (byId)
288
- return { ids: [byId], near: [] };
317
+ return [byId];
289
318
  const byAlias = index.byAlias.get(key);
290
319
  if (byAlias && byAlias.size > 0)
291
- return { ids: [...byAlias], near: [] };
320
+ return [...byAlias];
292
321
  const prefixed = parsePrefixedRef(target);
293
322
  if (prefixed) {
294
323
  const byFamily = index.byFamilyNumber.get(`${prefixed.family}:${prefixed.number}`);
295
324
  if (byFamily && byFamily.size > 0)
296
- return { ids: [...byFamily], near: [] };
297
- return { ids: [], near: [] };
325
+ return [...byFamily];
326
+ return [];
298
327
  }
299
328
  // A bare number resolves only inside the citing document's own family. A
300
329
  // repository with both `adr/0007` and `rfc/0007` is ordinary, and guessing
@@ -303,19 +332,110 @@ function lookupExact(target, entry, index) {
303
332
  if (number !== null) {
304
333
  const family = index.familyOf.get(entry.document.id) ?? null;
305
334
  if (family === null)
306
- return { ids: [], near: [] };
335
+ return [];
307
336
  const byFamily = index.byFamilyNumber.get(`${family}:${number}`);
308
337
  if (byFamily && byFamily.size > 0)
309
- return { ids: [...byFamily], near: [] };
338
+ return [...byFamily];
339
+ }
340
+ return [];
341
+ }
342
+ /* -------------------------------------------------------------------------- */
343
+ /* Near misses */
344
+ /* -------------------------------------------------------------------------- */
345
+ /**
346
+ * The one document a failed reference was probably meant to name.
347
+ *
348
+ * A suggestion is cheap to read and expensive to get wrong: "did you mean
349
+ * ADR-0008?" against a citation that meant ADR-0012 sends a reader to the wrong
350
+ * decision with a confidence nobody earned. So the bar is not closeness, it is
351
+ * closeness *in the part of the spelling that is not the identity*. A family
352
+ * name is a word people misremember. A number is the identity itself, and
353
+ * `ADR-0009` sits one edit from every neighbour it has - so numbers are never
354
+ * guessed at, and a repository of fifteen ADRs citing a sixteenth is told the
355
+ * plain truth instead.
356
+ *
357
+ * Two gates, plus a fallback:
358
+ *
359
+ * - a path in the right directory whose basename is one edit out - a typo;
360
+ * - a family one edit out with its number intact - `ARD-0009` for `ADR-0009`.
361
+ *
362
+ * Anything else falls through to a one-edit match against the folded spellings
363
+ * documents already answer to, which is where a mistyped slug or wiki name
364
+ * lands.
365
+ *
366
+ * There is deliberately no gate for a file that moved. Resolution already binds
367
+ * a path by its basename, so `../guides/onboarding.md` finds the document that
368
+ * is now in `handbook/` without anybody being asked to confirm a guess - and a
369
+ * suggestion nobody needs is a suggestion that can only be wrong.
370
+ *
371
+ * Every gate ends the same way: **exactly one** candidate, or nothing. Two
372
+ * suggestions is the ambiguity this module refuses to resolve anywhere else,
373
+ * and the tail of a hint is no place to start.
374
+ */
375
+ function nearMisses(target, entry, index) {
376
+ if (looksLikePath(target)) {
377
+ const resolved = resolveFrom(entry.document.path, target).toLowerCase();
378
+ const stem = stemOf(resolved);
379
+ const siblings = index.byDirectory.get(dirnamePosix(resolved)) ?? [];
380
+ return sole(siblings.filter((file) => withinOneEdit(file.stem, stem)).map((file) => file.id));
381
+ }
382
+ const prefixed = parsePrefixedRef(target);
383
+ if (prefixed !== null) {
384
+ const hits = [];
385
+ for (const family of index.families) {
386
+ // The family as written needs no exclusion: one edit is never zero edits.
387
+ if (!withinOneEdit(family.toLowerCase(), prefixed.family.toLowerCase()))
388
+ continue;
389
+ for (const id of index.byFamilyNumber.get(`${family}:${prefixed.number}`) ?? [])
390
+ hits.push(id);
391
+ }
392
+ return sole(hits);
393
+ }
394
+ // A bare `12` carries nothing but a number, which is the one thing this
395
+ // refuses to guess at.
396
+ if (parseBareRef(target) !== null)
397
+ return [];
398
+ const key = normaliseRef(target);
399
+ if (key.length < MIN_SUGGESTIBLE)
400
+ return [];
401
+ const hits = [];
402
+ for (const length of [key.length - 1, key.length, key.length + 1]) {
403
+ for (const alias of index.aliasesByLength.get(length) ?? []) {
404
+ if (!withinOneEdit(alias, key))
405
+ continue;
406
+ for (const id of index.byAlias.get(alias) ?? [])
407
+ hits.push(id);
408
+ }
310
409
  }
311
- return { ids: [], near: [] };
410
+ return sole(hits);
312
411
  }
313
- /** Whether a repository has declared this reference none of its business. */
314
- function isFiltered(target, bare, options) {
412
+ /**
413
+ * How long a folded spelling has to be before one edit means anything.
414
+ *
415
+ * Below six characters an edit is most of the word, and every corpus is full of
416
+ * short names that differ by exactly that much.
417
+ */
418
+ const MIN_SUGGESTIBLE = 6;
419
+ /** The candidates, if there is exactly one of them. Otherwise nothing. */
420
+ function sole(ids) {
421
+ const unique = new Set(ids);
422
+ return unique.size === 1 ? [...unique] : [];
423
+ }
424
+ /**
425
+ * Which setting, if any, declared this reference none of the repository's
426
+ * business.
427
+ *
428
+ * Returns the mechanism rather than a boolean so the audit trail can say which
429
+ * line of configuration to go and look at. A team that silenced eight things
430
+ * with one glob and one with a family needs to be told them apart.
431
+ */
432
+ function filteredBy(target, bare, options) {
315
433
  if (options.isIgnoredReference?.(target))
316
- return true;
434
+ return 'reference';
317
435
  const prefixed = parsePrefixedRef(bare);
318
- return prefixed !== null && options.isIgnoredFamily?.(prefixed.family) === true;
436
+ if (prefixed !== null && options.isIgnoredFamily?.(prefixed.family) === true)
437
+ return 'family';
438
+ return null;
319
439
  }
320
440
  /**
321
441
  * Whether an unresolved bare identifier deserves a report.
@@ -353,8 +473,9 @@ function bindAnchor(candidate, documentId, anchor, index, nodes, options) {
353
473
  const loose = anchor.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, '-').replace(/^-|-$/g, '');
354
474
  if (anchors?.has(loose))
355
475
  return makeEdge(candidate, documentId);
356
- if (isFiltered(candidate.target, splitAnchor(candidate.target).target, options))
357
- return null;
476
+ const silenced = filteredBy(candidate.target, splitAnchor(candidate.target).target, options);
477
+ if (silenced !== null)
478
+ return suppression(candidate, silenced);
358
479
  const candidates = [...nodes.keys()].filter((id) => id.startsWith(`${documentId}#`) && id.toLowerCase().includes(loose));
359
480
  return {
360
481
  kind: candidate.kind,
@@ -381,6 +502,9 @@ function makeEdge(candidate, resolved) {
381
502
  reflexive: documentOf(from) === documentOf(to),
382
503
  };
383
504
  }
505
+ function suppression(candidate, by) {
506
+ return { target: candidate.target, from: candidate.from, at: candidate.declaredAt, by };
507
+ }
384
508
  function dangle(candidate, reason, candidates) {
385
509
  return {
386
510
  kind: candidate.kind,
@@ -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} from './identity.js';\nimport { basenamePosix, extnamePosix, resolveFrom } from './paths.js';\nimport type { DanglingRef, DocumentNode, Edge, ItemNode, MisreadKey, ParseProblem, SpecNode } 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\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 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.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 byKey = new Map<string, number>();\n\n // Structural edges: a register contains the specifications written inside it.\n for (const entry of extracted) {\n if (entry.containerId === null) continue;\n if (!nodes.has(entry.containerId) || !nodes.has(entry.document.id)) continue;\n edges.push({\n kind: 'contains',\n from: entry.containerId,\n to: entry.document.id,\n origin: 'structural',\n declaredAt: entry.document.at,\n raw: entry.document.title,\n declaredIn: [entry.document.at.file],\n reflexive: false,\n });\n }\n\n // Structural edges: a document contains its items.\n for (const item of items) {\n edges.push({\n kind: 'contains',\n from: item.document,\n to: item.id,\n origin: 'structural',\n declaredAt: item.at,\n raw: item.text,\n declaredIn: [item.at.file],\n reflexive: false,\n });\n }\n\n for (const entry of extracted) {\n for (const candidate of entry.references) {\n if (!nodes.has(candidate.from)) continue;\n const outcome = resolveOne(candidate, entry, index, nodes, options);\n if (outcome === null) continue;\n if ('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 };\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 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 index.byPath.set(entry.document.path.toLowerCase(), 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 }\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 return index;\n}\n\n/** Every spelling of a path that addresses a file without naming it exactly. */\nfunction pathAliases(path: string): string[] {\n const keys = new Set<string>();\n const extension = extnamePosix(path);\n if (extension.length > 0) keys.add(path.slice(0, path.length - extension.length).toLowerCase());\n // `docs/adr/0007/README.md` is also addressed as `docs/adr/0007`.\n const base = basenamePosix(path);\n if (/^(readme|index)\\./i.test(base)) {\n const parent = path.slice(0, path.length - base.length - 1);\n if (parent.length > 0) keys.add(parent.toLowerCase());\n }\n // A file never addresses itself inexactly. Leaving this in would let a\n // contrived name - `docs/a.md.md`, whose stem is another file's whole path -\n // make an exact link look ambiguous.\n keys.delete(path.toLowerCase());\n return [...keys];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Resolution */\n/* -------------------------------------------------------------------------- */\n\nfunction resolveOne(\n candidate: ReferenceCandidate,\n entry: ExtractedDocument,\n index: Index,\n nodes: ReadonlyMap<string, SpecNode>,\n options: ResolveOptions,\n): Edge | DanglingRef | null {\n const { target: bare, anchor } = splitAnchor(candidate.target);\n\n // A pure `#anchor` points inside the citing document.\n if (bare.length === 0 && anchor !== null) {\n return bindAnchor(candidate, entry.document.id, anchor, index, nodes, options);\n }\n\n if (isExternal(bare)) return null;\n // A link to source code, an image or a config file is not a specification\n // reference. Validating it would report an error on the most ordinary thing a\n // design document does.\n if (!isDocumentTarget(bare)) return null;\n\n const found = lookup(bare, entry, index);\n\n if (found.ids.length === 0) {\n if (candidate.opportunistic && !worthReporting(bare, index)) return null;\n if (isFiltered(candidate.target, bare, options)) return null;\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 if (isFiltered(candidate.target, bare, options)) return null;\n return dangle(candidate, 'ambiguous', found.ids);\n }\n\n const documentId = found.ids[0] as string;\n\n // A document naming itself in prose - almost always its own H1, `# ADR-0007:\n // Sharding` - is not a citation. Deliberate self-links are kept, because a\n // link that points at its own document is a real mistake worth reporting.\n if (candidate.opportunistic && documentId === entry.document.id) return null;\n\n if (anchor !== null) return bindAnchor(candidate, documentId, anchor, index, nodes, options);\n\n return makeEdge(candidate, documentId);\n}\n\ninterface Lookup {\n readonly ids: readonly string[];\n /** Near-misses worth suggesting in the report. */\n readonly near: readonly string[];\n}\n\n/**\n * The same path with its percent-escapes read.\n *\n * `[Design](docs/my%20design.md)` is what a renderer, a documentation site and\n * GitHub's own \"copy link\" all produce for a file with a space in its name, and\n * it names a real file. Returns `null` when there was nothing to decode or when\n * the escapes are malformed - `100%` in a path is not an escape sequence, and\n * `decodeURIComponent` throws on it.\n */\nfunction percentDecoded(target: string): string | null {\n if (!target.includes('%')) return null;\n try {\n const decoded = decodeURIComponent(target);\n return decoded === target ? null : decoded;\n } catch {\n return null;\n }\n}\n\nfunction lookup(target: string, entry: ExtractedDocument, index: Index): Lookup {\n const found = lookupExact(target, entry, index);\n if (found.ids.length > 0) return found;\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 return decoded === null ? found : lookupExact(decoded, entry, index);\n}\n\nfunction lookupExact(target: string, entry: ExtractedDocument, index: Index): Lookup {\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 { ids: [byPath], near: [] };\n // An inexact spelling can belong to more than one file: `docs/A` is both\n // `docs/A.md` with its extension dropped and `docs/A/README.md` standing\n // for its directory. Handing back every claimant makes that an\n // ambiguous-reference the author can settle, rather than a silent pick of\n // whichever happened to be indexed last.\n const spelled = index.byPathAlias.get(resolved);\n if (spelled && spelled.size > 0) return { ids: [...spelled], near: [] };\n // A path may still be spelled as an identifier in a nested folder layout.\n const stem = basenamePosix(resolved);\n const byStem = index.byAlias.get(normaliseRef(stem.replace(/\\.[^.]+$/, '')));\n if (byStem && byStem.size > 0) return { ids: [...byStem], near: [] };\n return { ids: [], near: [] };\n }\n\n const key = normaliseRef(target);\n if (key.length === 0) return { ids: [], near: [] };\n\n const byId = index.byId.get(key);\n if (byId) return { ids: [byId], near: [] };\n\n const byAlias = index.byAlias.get(key);\n if (byAlias && byAlias.size > 0) return { ids: [...byAlias], near: [] };\n\n const prefixed = parsePrefixedRef(target);\n if (prefixed) {\n const byFamily = index.byFamilyNumber.get(`${prefixed.family}:${prefixed.number}`);\n if (byFamily && byFamily.size > 0) return { ids: [...byFamily], near: [] };\n return { ids: [], near: [] };\n }\n\n // A bare number resolves only inside the citing document's own family. A\n // repository with both `adr/0007` and `rfc/0007` is ordinary, and guessing\n // between them would be worse than reporting nothing.\n const number = parseBareRef(target);\n if (number !== null) {\n const family = index.familyOf.get(entry.document.id) ?? null;\n if (family === null) return { ids: [], near: [] };\n const byFamily = index.byFamilyNumber.get(`${family}:${number}`);\n if (byFamily && byFamily.size > 0) return { ids: [...byFamily], near: [] };\n }\n\n return { ids: [], near: [] };\n}\n\n/** Whether a repository has declared this reference none of its business. */\nfunction isFiltered(target: string, bare: string, options: ResolveOptions): boolean {\n if (options.isIgnoredReference?.(target)) return true;\n const prefixed = parsePrefixedRef(bare);\n return prefixed !== null && options.isIgnoredFamily?.(prefixed.family) === true;\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 | null {\n const itemId = `${documentId}#${anchor}`;\n if (index.itemIds.has(itemId)) return makeEdge(candidate, itemId);\n\n const anchors = index.anchors.get(documentId);\n if (anchors?.has(anchor.toLowerCase())) return makeEdge(candidate, documentId);\n\n // Some renderers slugify differently; try the loosest reasonable match before\n // calling it broken.\n const loose = anchor.toLowerCase().replace(/[^\\p{L}\\p{N}]+/gu, '-').replace(/^-|-$/g, '');\n if (anchors?.has(loose)) return makeEdge(candidate, documentId);\n\n if (isFiltered(candidate.target, splitAnchor(candidate.target).target, options)) return null;\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 dangle(\n candidate: ReferenceCandidate,\n reason: DanglingRef['reason'],\n candidates: readonly string[],\n): DanglingRef {\n return {\n kind: candidate.kind,\n from: candidate.from,\n target: candidate.target,\n reason,\n origin: candidate.origin,\n declaredAt: candidate.declaredAt,\n raw: candidate.raw,\n candidates,\n };\n}\n\n/** The document part of a node id: `ADR-7#q.1` -> `ADR-7`. */\nexport function documentOf(nodeId: string): string {\n const hash = nodeId.indexOf('#');\n return hash === -1 ? nodeId : nodeId.slice(0, hash);\n}\n"]}
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Corpus-level reference resolution - the foreign-key check.\n *\n * Extraction produces relations whose targets are still just text. This module\n * turns each into an edge or into an explicit, reportable failure. The whole\n * value of the exercise is in refusing to do either job carelessly:\n *\n * - **A deliberate reference that does not resolve is an error.** A link, a\n * front-matter field or a directive naming a document that does not exist is\n * a broken foreign key, and silence about it is how reference graphs rot.\n * - **An opportunistic reference that does not resolve is nothing.** `ADR-0007`\n * written in prose is a citation; `SHA-256` is not. Since the two are\n * indistinguishable in isolation, bare identifiers are only reported when\n * their family already exists in the corpus - so `ADR-0099` in a repository\n * full of ADRs is a finding, and `T-1000` in the same repository is not.\n * - **Two candidates is worse than none.** A reference that matches several\n * documents is reported as ambiguous rather than silently bound to whichever\n * one happened to be indexed first.\n */\n\nimport type { ExtractedDocument, ReferenceCandidate } from './extract.js';\nimport {\n isDocumentTarget,\n isExternal,\n looksLikePath,\n normaliseRef,\n parseBareRef,\n parsePrefixedRef,\n splitAnchor,\n withinOneEdit,\n} from './identity.js';\nimport { basenamePosix, dirnamePosix, extnamePosix, resolveFrom } from './paths.js';\nimport type {\n DanglingRef,\n DocumentNode,\n Edge,\n ItemNode,\n MisreadKey,\n ParseProblem,\n SpecNode,\n SuppressedRef,\n} from './types.js';\n\nexport interface ResolveOptions {\n /**\n * Whether a repository-relative path exists on disk.\n *\n * Used only to tell \"you linked to a file that is not a specification\" apart\n * from \"you linked to nothing at all\", which are different mistakes with\n * different fixes.\n */\n readonly fileExists?: ((path: string) => boolean) | undefined;\n /**\n * Targets whose failure to resolve should not be reported.\n *\n * This is a *suppression* filter, not a resolution one: it is consulted only\n * when a reference has already failed to resolve. A reference that resolves\n * still becomes an edge no matter what this says, so no amount of\n * configuration can silently delete a relation from the graph.\n */\n readonly isIgnoredReference?: ((target: string) => boolean) | undefined;\n /**\n * Families that are never citations in this repository.\n *\n * `RFC 2119` is the canonical case: every specification cites it, and a\n * repository that keeps its own `RFC-*` documents will otherwise read that\n * sentence as a dangling reference to a local RFC 2119 it does not have.\n * Consulted only after resolution has failed, like every other filter here.\n */\n readonly isIgnoredFamily?: ((family: string) => boolean) | undefined;\n}\n\nexport interface ResolvedCorpus {\n readonly nodes: ReadonlyMap<string, SpecNode>;\n readonly documents: readonly DocumentNode[];\n readonly items: readonly ItemNode[];\n readonly edges: readonly Edge[];\n readonly dangling: readonly DanglingRef[];\n readonly problems: readonly ParseProblem[];\n readonly misreadKeys: readonly MisreadKey[];\n /**\n * References this repository declared none of its business.\n *\n * Not findings, and never reported as such - they are the audit trail for\n * the two settings that can silence one. See ADR-0008.\n */\n readonly suppressed: readonly SuppressedRef[];\n}\n\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.set(item.id, item);\n items.push(item);\n }\n problems.push(...entry.problems);\n misreadKeys.push(...entry.misreadKeys);\n }\n\n const edges: Edge[] = [];\n const dangling: DanglingRef[] = [];\n const suppressed: SuppressedRef[] = [];\n const byKey = new Map<string, number>();\n\n // Structural edges: a register contains the specifications written inside it.\n for (const entry of extracted) {\n if (entry.containerId === null) continue;\n if (!nodes.has(entry.containerId) || !nodes.has(entry.document.id)) continue;\n edges.push({\n kind: 'contains',\n from: entry.containerId,\n to: entry.document.id,\n origin: 'structural',\n declaredAt: entry.document.at,\n raw: entry.document.title,\n declaredIn: [entry.document.at.file],\n reflexive: false,\n });\n }\n\n // Structural edges: a document contains its items.\n for (const item of items) {\n edges.push({\n kind: 'contains',\n from: item.document,\n to: item.id,\n origin: 'structural',\n declaredAt: item.at,\n raw: item.text,\n declaredIn: [item.at.file],\n reflexive: false,\n });\n }\n\n for (const entry of extracted) {\n for (const candidate of entry.references) {\n if (!nodes.has(candidate.from)) continue;\n const outcome = resolveOne(candidate, entry, index, nodes, options);\n if (outcome === null) continue;\n if ('by' in outcome) {\n suppressed.push(outcome);\n continue;\n }\n if ('reason' in outcome) {\n dangling.push(outcome);\n continue;\n }\n // Collapse duplicates: the same relation stated twice is one edge - but\n // every declaration site is kept, because a supersession recorded in only\n // one of the two documents is itself a finding.\n const key = `${outcome.kind} ${outcome.from} ${outcome.to}`;\n const existing = byKey.get(key);\n if (existing !== undefined) {\n const previous = edges[existing] as Edge;\n if (!previous.declaredIn.includes(outcome.declaredAt.file)) {\n edges[existing] = { ...previous, declaredIn: [...previous.declaredIn, outcome.declaredAt.file] };\n }\n continue;\n }\n byKey.set(key, edges.length);\n edges.push(outcome);\n }\n }\n\n return { nodes, documents, items, edges, dangling, problems, misreadKeys, suppressed };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Indexing */\n/* -------------------------------------------------------------------------- */\n\nfunction buildIndex(extracted: readonly ExtractedDocument[]): Index {\n const index: Index = {\n byId: new Map(),\n byAlias: new Map(),\n byPath: new Map(),\n byPathAlias: new Map(),\n byFamilyNumber: new Map(),\n families: new Set(),\n byDirectory: new Map(),\n aliasesByLength: new Map(),\n anchors: new Map(),\n itemIds: new Set(),\n familyOf: new Map(),\n };\n\n for (const entry of extracted) {\n const id = entry.document.id;\n index.byId.set(normaliseRef(id), id);\n index.anchors.set(id, entry.anchors);\n index.familyOf.set(id, entry.identity.family);\n\n for (const alias of entry.document.aliases) {\n const set = index.byAlias.get(alias) ?? new Set<string>();\n set.add(id);\n index.byAlias.set(alias, set);\n }\n\n // Only a file answers to its path. A region keeps the path on its node, so a\n // finding can say where it lives and `document[path=...]` can select it, but\n // it must not be reachable *by* that path: two nodes answering to one path\n // makes every link to the file ambiguous, and this index resolves a\n // collision by keeping whichever was written last. A link to a file holding\n // a register bound to its final row, quietly and with no diagnostic, and\n // anchors were then checked against that row's span rather than the file's.\n // ADR-0009 said a region does not claim the file's path; this is the index\n // where that had to be true.\n if (entry.containerId === null) {\n const path = entry.document.path.toLowerCase();\n index.byPath.set(path, id);\n for (const spelling of pathAliases(entry.document.path)) {\n const set = index.byPathAlias.get(spelling) ?? new Set<string>();\n set.add(id);\n index.byPathAlias.set(spelling, set);\n }\n const directory = dirnamePosix(path);\n const siblings = index.byDirectory.get(directory) ?? [];\n siblings.push({ stem: stemOf(path), id });\n index.byDirectory.set(directory, siblings);\n }\n\n if (entry.identity.family !== null && entry.identity.number !== null) {\n index.families.add(entry.identity.family);\n const key = `${entry.identity.family}:${entry.identity.number}`;\n const set = index.byFamilyNumber.get(key) ?? new Set<string>();\n set.add(id);\n index.byFamilyNumber.set(key, set);\n }\n\n for (const item of entry.items) index.itemIds.add(item.id);\n }\n\n for (const alias of index.byAlias.keys()) {\n const bucket = index.aliasesByLength.get(alias.length) ?? [];\n bucket.push(alias);\n index.aliasesByLength.set(alias.length, bucket);\n }\n\n return index;\n}\n\n/** A file name with its extension taken off: `docs/a/0004-sharding.md` -> `0004-sharding`. */\nfunction stemOf(path: string): string {\n const base = basenamePosix(path);\n return base.slice(0, base.length - extnamePosix(base).length);\n}\n\n/** Every spelling of a path that addresses a file without naming it exactly. */\nfunction pathAliases(path: string): string[] {\n const keys = new Set<string>();\n const extension = extnamePosix(path);\n if (extension.length > 0) keys.add(path.slice(0, path.length - extension.length).toLowerCase());\n // `docs/adr/0007/README.md` is also addressed as `docs/adr/0007`.\n const base = basenamePosix(path);\n if (/^(readme|index)\\./i.test(base)) {\n const parent = path.slice(0, path.length - base.length - 1);\n if (parent.length > 0) keys.add(parent.toLowerCase());\n }\n // A file never addresses itself inexactly. Leaving this in would let a\n // contrived name - `docs/a.md.md`, whose stem is another file's whole path -\n // make an exact link look ambiguous.\n keys.delete(path.toLowerCase());\n return [...keys];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Resolution */\n/* -------------------------------------------------------------------------- */\n\nfunction resolveOne(\n candidate: ReferenceCandidate,\n entry: ExtractedDocument,\n index: Index,\n nodes: ReadonlyMap<string, SpecNode>,\n options: ResolveOptions,\n): Edge | DanglingRef | SuppressedRef | null {\n const { target: bare, anchor } = splitAnchor(candidate.target);\n\n // A pure `#anchor` points inside the citing document.\n if (bare.length === 0 && anchor !== null) {\n return bindAnchor(candidate, entry.document.id, anchor, index, nodes, options);\n }\n\n if (isExternal(bare)) return null;\n // A link to source code, an image or a config file is not a specification\n // reference. Validating it would report an error on the most ordinary thing a\n // design document does.\n if (!isDocumentTarget(bare)) return null;\n\n const found = lookup(bare, entry, index);\n\n if (found.ids.length === 0) {\n if (candidate.opportunistic && !worthReporting(bare, index)) return null;\n const silenced = filteredBy(candidate.target, bare, options);\n if (silenced !== null) return suppression(candidate, silenced);\n // A trailing slash names a directory. Linking to one is ordinary - \"the\n // decisions live in [archive/](archive/)\" - and is not a citation of any\n // document. Resolution is still attempted first, so a directory-style\n // document written as `0007-sharding/` continues to resolve.\n if (bare.endsWith('/')) return null;\n const reason = pathMissingReason(bare, entry, options);\n return dangle(candidate, reason, found.near);\n }\n\n if (found.ids.length > 1) {\n if (candidate.opportunistic) return null;\n const silenced = filteredBy(candidate.target, bare, options);\n if (silenced !== null) return suppression(candidate, silenced);\n return dangle(candidate, 'ambiguous', found.ids);\n }\n\n const documentId = found.ids[0] as string;\n\n // A document naming itself in prose - almost always its own H1, `# ADR-0007:\n // Sharding` - is not a citation. Deliberate self-links are kept, because a\n // link that points at its own document is a real mistake worth reporting.\n if (candidate.opportunistic && documentId === entry.document.id) return null;\n\n if (anchor !== null) return bindAnchor(candidate, documentId, anchor, index, nodes, options);\n\n return makeEdge(candidate, documentId);\n}\n\ninterface Lookup {\n readonly ids: readonly string[];\n /** Near-misses worth suggesting in the report. */\n readonly near: readonly string[];\n}\n\n/**\n * The same path with its percent-escapes read.\n *\n * `[Design](docs/my%20design.md)` is what a renderer, a documentation site and\n * GitHub's own \"copy link\" all produce for a file with a space in its name, and\n * it names a real file. Returns `null` when there was nothing to decode or when\n * the escapes are malformed - `100%` in a path is not an escape sequence, and\n * `decodeURIComponent` throws on it.\n */\nfunction percentDecoded(target: string): string | null {\n if (!target.includes('%')) return null;\n try {\n const decoded = decodeURIComponent(target);\n return decoded === target ? null : decoded;\n } catch {\n return null;\n }\n}\n\nfunction lookup(target: string, entry: ExtractedDocument, index: Index): Lookup {\n const exact = lookupExact(target, entry, index);\n if (exact.length > 0) return { ids: exact, near: [] };\n // Tried second and never first, so a repository holding a file whose name\n // genuinely contains a percent escape keeps resolving by its written spelling.\n const decoded = percentDecoded(target);\n const relaxed = decoded === null ? [] : lookupExact(decoded, entry, index);\n if (relaxed.length > 0) return { ids: relaxed, near: [] };\n // Suggestions are computed only once resolution has failed outright, so their\n // cost is bounded by the number of findings rather than by the corpus.\n return { ids: [], near: nearMisses(target, entry, index) };\n}\n\nfunction lookupExact(target: string, entry: ExtractedDocument, index: Index): readonly string[] {\n if (looksLikePath(target)) {\n const resolved = resolveFrom(entry.document.path, target).toLowerCase();\n // Naming the file exactly is never ambiguous, whatever else is spelled the\n // same way.\n const byPath = index.byPath.get(resolved);\n if (byPath) return [byPath];\n // An inexact spelling can belong to more than one file: `docs/A` is both\n // `docs/A.md` with its extension dropped and `docs/A/README.md` standing\n // for its directory. Handing back every claimant makes that an\n // ambiguous-reference the author can settle, rather than a silent pick of\n // whichever happened to be indexed last.\n const spelled = index.byPathAlias.get(resolved);\n if (spelled && spelled.size > 0) return [...spelled];\n // A path may still be spelled as an identifier in a nested folder layout.\n const stem = basenamePosix(resolved);\n const byStem = index.byAlias.get(normaliseRef(stem.replace(/\\.[^.]+$/, '')));\n if (byStem && byStem.size > 0) return [...byStem];\n return [];\n }\n\n const key = normaliseRef(target);\n if (key.length === 0) return [];\n\n const byId = index.byId.get(key);\n if (byId) return [byId];\n\n const byAlias = index.byAlias.get(key);\n if (byAlias && byAlias.size > 0) return [...byAlias];\n\n const prefixed = parsePrefixedRef(target);\n if (prefixed) {\n const byFamily = index.byFamilyNumber.get(`${prefixed.family}:${prefixed.number}`);\n if (byFamily && byFamily.size > 0) return [...byFamily];\n return [];\n }\n\n // A bare number resolves only inside the citing document's own family. A\n // repository with both `adr/0007` and `rfc/0007` is ordinary, and guessing\n // between them would be worse than reporting nothing.\n const number = parseBareRef(target);\n if (number !== null) {\n const family = index.familyOf.get(entry.document.id) ?? null;\n if (family === null) return [];\n const byFamily = index.byFamilyNumber.get(`${family}:${number}`);\n if (byFamily && byFamily.size > 0) return [...byFamily];\n }\n\n return [];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Near misses */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The one document a failed reference was probably meant to name.\n *\n * A suggestion is cheap to read and expensive to get wrong: \"did you mean\n * ADR-0008?\" against a citation that meant ADR-0012 sends a reader to the wrong\n * decision with a confidence nobody earned. So the bar is not closeness, it is\n * closeness *in the part of the spelling that is not the identity*. A family\n * name is a word people misremember. A number is the identity itself, and\n * `ADR-0009` sits one edit from every neighbour it has - so numbers are never\n * guessed at, and a repository of fifteen ADRs citing a sixteenth is told the\n * plain truth instead.\n *\n * Two gates, plus a fallback:\n *\n * - a path in the right directory whose basename is one edit out - a typo;\n * - a family one edit out with its number intact - `ARD-0009` for `ADR-0009`.\n *\n * Anything else falls through to a one-edit match against the folded spellings\n * documents already answer to, which is where a mistyped slug or wiki name\n * lands.\n *\n * There is deliberately no gate for a file that moved. Resolution already binds\n * a path by its basename, so `../guides/onboarding.md` finds the document that\n * is now in `handbook/` without anybody being asked to confirm a guess - and a\n * suggestion nobody needs is a suggestion that can only be wrong.\n *\n * Every gate ends the same way: **exactly one** candidate, or nothing. Two\n * suggestions is the ambiguity this module refuses to resolve anywhere else,\n * and the tail of a hint is no place to start.\n */\nfunction nearMisses(target: string, entry: ExtractedDocument, index: Index): string[] {\n if (looksLikePath(target)) {\n const resolved = resolveFrom(entry.document.path, target).toLowerCase();\n const stem = stemOf(resolved);\n const siblings = index.byDirectory.get(dirnamePosix(resolved)) ?? [];\n return sole(siblings.filter((file) => withinOneEdit(file.stem, stem)).map((file) => file.id));\n }\n\n const prefixed = parsePrefixedRef(target);\n if (prefixed !== null) {\n const hits: string[] = [];\n for (const family of index.families) {\n // The family as written needs no exclusion: one edit is never zero edits.\n if (!withinOneEdit(family.toLowerCase(), prefixed.family.toLowerCase())) continue;\n for (const id of index.byFamilyNumber.get(`${family}:${prefixed.number}`) ?? []) hits.push(id);\n }\n return sole(hits);\n }\n\n // A bare `12` carries nothing but a number, which is the one thing this\n // refuses to guess at.\n if (parseBareRef(target) !== null) return [];\n\n const key = normaliseRef(target);\n if (key.length < MIN_SUGGESTIBLE) return [];\n const hits: string[] = [];\n for (const length of [key.length - 1, key.length, key.length + 1]) {\n for (const alias of index.aliasesByLength.get(length) ?? []) {\n if (!withinOneEdit(alias, key)) continue;\n for (const id of index.byAlias.get(alias) ?? []) hits.push(id);\n }\n }\n return sole(hits);\n}\n\n/**\n * How long a folded spelling has to be before one edit means anything.\n *\n * Below six characters an edit is most of the word, and every corpus is full of\n * short names that differ by exactly that much.\n */\nconst MIN_SUGGESTIBLE = 6;\n\n/** The candidates, if there is exactly one of them. Otherwise nothing. */\nfunction sole(ids: Iterable<string>): string[] {\n const unique = new Set(ids);\n return unique.size === 1 ? [...unique] : [];\n}\n\n/**\n * Which setting, if any, declared this reference none of the repository's\n * business.\n *\n * Returns the mechanism rather than a boolean so the audit trail can say which\n * line of configuration to go and look at. A team that silenced eight things\n * with one glob and one with a family needs to be told them apart.\n */\nfunction filteredBy(target: string, bare: string, options: ResolveOptions): SuppressedRef['by'] | null {\n if (options.isIgnoredReference?.(target)) return 'reference';\n const prefixed = parsePrefixedRef(bare);\n if (prefixed !== null && options.isIgnoredFamily?.(prefixed.family) === true) return 'family';\n return null;\n}\n\n/**\n * Whether an unresolved bare identifier deserves a report.\n *\n * Only when the corpus already contains that family. `ADR-0099` in a repository\n * of ADRs is a real dangling citation; `T-1000` in the same repository is a\n * sentence about a robot.\n */\nfunction worthReporting(target: string, index: Index): boolean {\n const prefixed = parsePrefixedRef(target);\n return prefixed !== null && index.families.has(prefixed.family);\n}\n\nfunction pathMissingReason(\n target: string,\n entry: ExtractedDocument,\n options: ResolveOptions,\n): DanglingRef['reason'] {\n if (!looksLikePath(target) || !options.fileExists) return 'unknown-target';\n const resolved = resolveFrom(entry.document.path, target);\n return options.fileExists(resolved) ? 'not-a-spec' : 'unknown-target';\n}\n\n/**\n * Binds a `#anchor` to an item or a section.\n *\n * Anchors are how a delegation names a specific obligation rather than a whole\n * document, so resolving them precisely is what lets a ghost-handover finding\n * say *which* question was handed into the archive.\n */\nfunction bindAnchor(\n candidate: ReferenceCandidate,\n documentId: string,\n anchor: string,\n index: Index,\n nodes: ReadonlyMap<string, SpecNode>,\n options: ResolveOptions,\n): Edge | DanglingRef | SuppressedRef | null {\n const itemId = `${documentId}#${anchor}`;\n if (index.itemIds.has(itemId)) return makeEdge(candidate, itemId);\n\n const anchors = index.anchors.get(documentId);\n if (anchors?.has(anchor.toLowerCase())) return makeEdge(candidate, documentId);\n\n // Some renderers slugify differently; try the loosest reasonable match before\n // calling it broken.\n const loose = anchor.toLowerCase().replace(/[^\\p{L}\\p{N}]+/gu, '-').replace(/^-|-$/g, '');\n if (anchors?.has(loose)) return makeEdge(candidate, documentId);\n\n const silenced = filteredBy(candidate.target, splitAnchor(candidate.target).target, options);\n if (silenced !== null) return suppression(candidate, silenced);\n\n const candidates = [...(nodes.keys() as Iterable<string>)].filter(\n (id) => id.startsWith(`${documentId}#`) && id.toLowerCase().includes(loose),\n );\n return {\n kind: candidate.kind,\n from: candidate.from,\n target: candidate.target,\n reason: 'unknown-anchor',\n origin: candidate.origin,\n declaredAt: candidate.declaredAt,\n raw: candidate.raw,\n candidates,\n };\n}\n\nfunction makeEdge(candidate: ReferenceCandidate, resolved: string): Edge {\n const from = candidate.inverted ? resolved : candidate.from;\n const to = candidate.inverted ? candidate.from : resolved;\n return {\n kind: candidate.kind,\n from,\n to,\n origin: candidate.origin,\n declaredAt: candidate.declaredAt,\n raw: candidate.raw,\n declaredIn: [candidate.declaredAt.file],\n reflexive: documentOf(from) === documentOf(to),\n };\n}\n\nfunction suppression(candidate: ReferenceCandidate, by: SuppressedRef['by']): SuppressedRef {\n return { target: candidate.target, from: candidate.from, at: candidate.declaredAt, by };\n}\n\nfunction dangle(\n candidate: ReferenceCandidate,\n reason: DanglingRef['reason'],\n candidates: readonly string[],\n): DanglingRef {\n return {\n kind: candidate.kind,\n from: candidate.from,\n target: candidate.target,\n reason,\n origin: candidate.origin,\n declaredAt: candidate.declaredAt,\n raw: candidate.raw,\n candidates,\n };\n}\n\n/** The document part of a node id: `ADR-7#q.1` -> `ADR-7`. */\nexport function documentOf(nodeId: string): string {\n const hash = nodeId.indexOf('#');\n return hash === -1 ? nodeId : nodeId.slice(0, hash);\n}\n"]}
package/dist/rules.d.ts CHANGED
@@ -20,8 +20,9 @@
20
20
  * the superseded document, not the one that declared the relation.
21
21
  */
22
22
  import { type SpecGraph } from './graph.js';
23
+ import { type ProjectRule } from './project-rules.js';
23
24
  import type { ResolvedCorpus } from './resolve.js';
24
- import type { Diagnostic, RuleId, Severity } from './types.js';
25
+ import type { AnyRuleId, Diagnostic, RuleId, Severity } from './types.js';
25
26
  export declare const DEFAULT_SEVERITIES: Readonly<Record<RuleId, Severity>>;
26
27
  export declare const RULE_IDS: readonly RuleId[];
27
28
  /** One-line descriptions, printed by `spec-graph rules`. */
@@ -39,14 +40,16 @@ export declare const RULE_DESCRIPTIONS: Readonly<Record<RuleId, string>>;
39
40
  * and exempt the one rule their repository disagrees with, rather than choosing
40
41
  * between all of it and none of it.
41
42
  */
42
- export declare function resolveStrict(overrides?: Partial<Record<RuleId, Severity>>, strict?: boolean): {
43
- severities: Partial<Record<RuleId, Severity>>;
44
- escalated: ReadonlySet<RuleId>;
43
+ export declare function resolveStrict(overrides?: Partial<Record<AnyRuleId, Severity>>, strict?: boolean, projectRules?: readonly ProjectRule[]): {
44
+ severities: Partial<Record<AnyRuleId, Severity>>;
45
+ escalated: ReadonlySet<AnyRuleId>;
45
46
  };
46
47
  export interface RuleOptions {
47
- readonly severities?: Partial<Record<RuleId, Severity>> | undefined;
48
+ readonly severities?: Partial<Record<AnyRuleId, Severity>> | undefined;
48
49
  /** Cap on related locations attached to one finding. */
49
50
  readonly maxRelated?: number | undefined;
51
+ /** Conventions the repository wrote for itself. See ADR-0016. */
52
+ readonly projectRules?: readonly ProjectRule[] | undefined;
50
53
  }
51
54
  /** Runs every enabled rule and returns findings in report order. */
52
55
  export declare function runRules(graph: SpecGraph, corpus: ResolvedCorpus, options?: RuleOptions): Diagnostic[];