@descent-vtt/spec-graph 0.7.0 → 0.8.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/CHANGELOG.md CHANGED
@@ -3,6 +3,73 @@
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.8.0
7
+
8
+ A register row took its identifier from the first thing its *title* said, not
9
+ from its ID column. Found on the same 885-document repository as 0.7.0: 27 of
10
+ 232 rows of one issues register were filed under the wrong entity.
11
+
12
+ A minor rather than a patch, and the baseline is the reason. A baseline is
13
+ fingerprinted on document ids, and the whole of this fix is that those ids move.
14
+ Measured on an unchanged corpus against a baseline recorded by 0.7.0: the
15
+ accepted finding came back as new, its entry was reported as no longer
16
+ occurring, and `check --baseline` went from exit `0` to exit `1`. One
17
+ `--record-baseline` settles it, and the entries it writes are the ones that were
18
+ meant all along. The rule at the top of this file says a patch asks nothing of a
19
+ repository that upgrades, and this asks for that run.
20
+
21
+ ### Fixed
22
+
23
+ **A title names nothing.** `| OI-V-05 | ADR-040's enforcement point has no
24
+ browser test |` was read as `ADR-040`. Eighteen rows collided with the ADR they
25
+ cite that way, and the collision does not merely mislabel a row: run against a
26
+ corpus holding both, the ADR keeps the id and the issue has no node in the graph
27
+ at all. Nine more opened with a noun phrase, inventing a `STAGE-1`, a
28
+ `GUARDRAIL-5` and a `CLOSED-2026` that nobody had written, each of them open -
29
+ which is what drew the false `ghost-handover` findings the register was reported
30
+ for; two rows collapsed onto one `STAGE-1`.
31
+
32
+ The identifier a title opens with almost always belongs to the document the
33
+ title is *about*, so a title is read as a name only where nothing else has given
34
+ one - and where it is not the name it registers no alias either, because an
35
+ alias sends every citation of the real document to whatever quoted it.
36
+ `# ADR-0040 considered harmful` at the top of `0007-sharding.md` was registering
37
+ `adr0040` against `ADR-0007`.
38
+
39
+ Two things count as having given one: a number, from a declaration or a file
40
+ name, and for a register row its id column, whatever shape the id is in. A whole
41
+ file keeps the looser reading and its H1 can still name it, because there
42
+ nothing separates a title about another document from a title about this one:
43
+ `slug:` holds a URL segment rather than an id, so `slug: sharding` in
44
+ `sharding.md` under `# ADR-0007: Sharding` has to stay `ADR-0007`. A declaration
45
+ and the file name are still weighed together rather than ranked, so
46
+ `slug: sharding-the-write-path` in `0007-sharding.md` is `ADR-0007` as before.
47
+
48
+ **A region no longer reads a number out of the file it sits in.** Every row of
49
+ a register kept in `0042-open-issues.md` answered to `ADR-0042`, and so did the
50
+ file. ADR-0009 already said a region does not claim the file's *path*, for the
51
+ reason that two nodes answering to one name make every link to it ambiguous; the
52
+ file's name is that same claim spelled differently, and the rule was written
53
+ down as two.
54
+
55
+ **A file's own title heading is matched by its number, not its spelling.**
56
+ `# ADR-040` at the top of `0040-enforce.md` was read as a region *inside* the
57
+ file it names: one decision, two nodes, a split lifecycle, and a citation
58
+ arriving at whichever padding it happened to use. Padding is not part of an
59
+ identity anywhere else - `ADR-40`, `ADR-040` and `ADR-0040` all resolve to one
60
+ document - and now it is not part of this comparison either.
61
+
62
+ ### Verified
63
+
64
+ `npm run lint`, 950 tests and `npm run selfcheck`. Thirteen of the fifteen new
65
+ tests fail against the unfixed source; the other two are the guards on the
66
+ layouts that must not move, which have to pass both ways. `identity.ts` and
67
+ `sections.ts` were measured on their own before and after: 72.77% and 66.89%
68
+ became 75.18% and 71.93%, and no mutant on the new lines survived, so nothing
69
+ was masked. ADR-0004 carries the rule as a
70
+ fifth asymmetry, with the hyphenated-prefix question declined under it, and
71
+ ADR-0009 is amended in place.
72
+
6
73
  ## 0.7.0
7
74
 
8
75
  Three ways a run handed back something other than what it said it was: a
package/README.md CHANGED
@@ -227,6 +227,13 @@ header you wrote:
227
227
 
228
228
  Findings point at the declaring cell, not at the file.
229
229
 
230
+ A row is named by its ID column, and a title is only a title. An identifier a
231
+ title opens with belongs to the document the row is *about* — an issue reading
232
+ `| OI-V-05 | ADR-040's enforcement point has no browser test |` is filed under
233
+ `OI-V-05`, and citations of `ADR-040` still reach `ADR-040`. The same holds for
234
+ a whole file: an explicit `id:`, then the file name, then the H1, and the H1 is
235
+ read as a name only when neither of the others gave one.
236
+
230
237
  The register itself stays in the graph as the thing that holds them, so
231
238
  `spec-graph query 'document[id=register] -contains-> document'` lists what is
232
239
  inside it.
@@ -35,11 +35,14 @@ export interface IdentityInput {
35
35
  /** The document H1, if any. */
36
36
  readonly heading: string | null;
37
37
  /**
38
- * Register the file path and stem as aliases. Default true.
38
+ * Whether this identity is the file's own. Default true.
39
39
  *
40
40
  * A specification that is a *region* of a file must not claim the file's
41
41
  * path: the file already answers to it, and two nodes answering to one path
42
- * would make every link to that file ambiguous.
42
+ * would make every link to that file ambiguous. The file's *name* is the same
43
+ * claim spelled differently, so a region does not read a number out of it
44
+ * either: every row of a register kept in `0042-open-issues.md` answered to
45
+ * ADR-0042, and so did the file.
43
46
  */
44
47
  readonly includePathAliases?: boolean | undefined;
45
48
  }
@@ -47,8 +50,13 @@ export interface IdentityInput {
47
50
  * Derives a document's canonical id and every alias that should reach it.
48
51
  *
49
52
  * Evidence is used in order of how deliberate it is: an explicit declaration,
50
- * then the file name, then the H1. Whatever loses still contributes aliases, so
51
- * a reference written in any of those forms still resolves.
53
+ * then the file name, then the H1. The first two both *name* the document, so a
54
+ * declaration that carries no number still takes the number from the file name
55
+ * beside it - `slug: sharding-the-write-path` in `0007-sharding.md` is ADR-0007.
56
+ * A title only *describes* it, so it is read as a name only where nothing else
57
+ * has given one. A losing declaration or file name still contributes aliases,
58
+ * so a reference written in either form resolves; a title that lost does not,
59
+ * because the identifier it opens with belongs to the document it is about.
52
60
  */
53
61
  export declare function identify(input: IdentityInput): DocumentIdentity;
54
62
  /**
package/dist/identity.js CHANGED
@@ -46,8 +46,13 @@ const HAS_LETTER_OR_DIGIT = /[\p{L}\p{N}]/u;
46
46
  * Derives a document's canonical id and every alias that should reach it.
47
47
  *
48
48
  * Evidence is used in order of how deliberate it is: an explicit declaration,
49
- * then the file name, then the H1. Whatever loses still contributes aliases, so
50
- * a reference written in any of those forms still resolves.
49
+ * then the file name, then the H1. The first two both *name* the document, so a
50
+ * declaration that carries no number still takes the number from the file name
51
+ * beside it - `slug: sharding-the-write-path` in `0007-sharding.md` is ADR-0007.
52
+ * A title only *describes* it, so it is read as a name only where nothing else
53
+ * has given one. A losing declaration or file name still contributes aliases,
54
+ * so a reference written in either form resolves; a title that lost does not,
55
+ * because the identifier it opens with belongs to the document it is about.
51
56
  */
52
57
  export function identify(input) {
53
58
  // A declaration carrying no letter or digit is discarded rather than honoured.
@@ -57,6 +62,9 @@ export function identify(input) {
57
62
  // exports as invalid Mermaid, and names itself in findings a reader cannot act
58
63
  // on. There is nothing to gain by taking such a value seriously.
59
64
  const declaredId = input.declaredId !== null && HAS_LETTER_OR_DIGIT.test(input.declaredId) ? input.declaredId : null;
65
+ // What the flag decides, named for what it means: the file's path and its
66
+ // name are one claim, and a region makes neither of them.
67
+ const ownsFile = input.includePathAliases !== false;
60
68
  const stem = fileStem(input.path);
61
69
  const directoryFamily = familyFromPath(input.path);
62
70
  const aliases = new Set();
@@ -96,9 +104,26 @@ export function identify(input) {
96
104
  };
97
105
  consider(declaredId, true);
98
106
  // `0007-sharding-the-write-path.md` and `kep-1234-foo.md` both start with the
99
- // identifier; take the leading token rather than the whole stem.
100
- consider(leadingToken(stem), true);
101
- consider(headingId(input.heading), false);
107
+ // identifier; take the leading token rather than the whole stem. Only for the
108
+ // file's own identity: the name names the file, and a region reading its
109
+ // number is the collision ADR-0009 forbids, reached by a second route.
110
+ if (ownsFile)
111
+ consider(leadingToken(stem), true);
112
+ // A title is prose *about* a document, so it names one only where nothing has
113
+ // named it yet, and where it does not it registers no alias either - an alias
114
+ // is the same mistake one step removed, landing on a document that is real.
115
+ // A number disqualifies it, because a different number in a title is about a
116
+ // different document; so does being a region, because a row's id column
117
+ // exists to hold an identifier while its title cell is prose about the rest
118
+ // of the corpus. `| OI-V-05 | ADR-040's enforcement point has no browser
119
+ // test |` became ADR-040, collided with the real one and left the issue with
120
+ // no node at all: 27 of 232 rows of one register, nine inventing a `STAGE-1`
121
+ // nobody had written. A *file* keeps the looser reading deliberately - there
122
+ // the two cases are one shape, `slug:` holds a URL segment rather than an id,
123
+ // and `slug: sharding` under `# ADR-0007` has to stay ADR-0007. See the fifth
124
+ // asymmetry in ADR-0004 for what that costs and why it is not guessed at.
125
+ const titleId = ownsFile && !candidates.some((c) => c.number !== null) ? headingId(input.heading) : null;
126
+ consider(titleId, false);
102
127
  const numbered = candidates.find((c) => c.number !== null);
103
128
  const family = numbered?.family ?? directoryFamily;
104
129
  const number = numbered?.number ?? null;
@@ -123,7 +148,7 @@ export function identify(input) {
123
148
  id = stem.length > 0 ? stem : input.path;
124
149
  }
125
150
  add(id);
126
- if (input.includePathAliases !== false) {
151
+ if (ownsFile) {
127
152
  add(stem);
128
153
  add(input.path);
129
154
  add(stripExtension(input.path));
@@ -131,8 +156,7 @@ export function identify(input) {
131
156
  add(declaredId);
132
157
  for (const alias of input.declaredAliases)
133
158
  add(alias);
134
- if (input.heading)
135
- add(headingId(input.heading));
159
+ add(titleId);
136
160
  if (number !== null) {
137
161
  const spellings = numberSpellings(number, witnesses);
138
162
  if (family !== null) {
@@ -1 +1 @@
1
- {"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,sFAAsF;AACtF,MAAM,kBAAkB,GAAqC;IAC3D,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,KAAK;IACX,wBAAwB,EAAE,KAAK;IAC/B,kBAAkB,EAAE,KAAK;IACzB,SAAS,EAAE,KAAK;IAChB,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,KAAK;IACX,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,KAAK;IACX,YAAY,EAAE,KAAK;IACnB,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,UAAU;IACrB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,QAAQ;IACjB,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,KAAK;IACX,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,MAAM;CACd,CAAC;AAEF,oFAAoF;AACpF,MAAM,CAAC,MAAM,OAAO,GAAsB,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAEnH,+EAA+E;AAC/E,MAAM,WAAW,GAAG,kDAAkD,CAAC;AACvE,6DAA6D;AAC7D,MAAM,WAAW,GAAG,eAAe,CAAC;AAgCpC,mFAAmF;AACnF,MAAM,mBAAmB,GAAG,eAAe,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAoB;IAC3C,+EAA+E;IAC/E,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,+EAA+E;IAC/E,iEAAiE;IACjE,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,KAAK,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;IACrH,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAElC,MAAM,GAAG,GAAG,CAAC,KAAgC,EAAQ,EAAE;QACrD,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC,CAAC;IAEF,MAAM,UAAU,GAAsE,EAAE,CAAC;IAEzF,MAAM,QAAQ,GAAG,CAAC,GAAkB,EAAE,SAAkB,EAAQ,EAAE;QAChE,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACjC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3C,IAAI,QAAQ,EAAE,CAAC;YACb,UAAU,CAAC,IAAI,CAAC;gBACd,MAAM,EAAG,QAAQ,CAAC,CAAC,CAAY,CAAC,WAAW,EAAE;gBAC7C,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC;gBAClD,KAAK,EAAE,OAAO;aACf,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,IAAI,IAAI,SAAS,EAAE,CAAC;YACtB,UAAU,CAAC,IAAI,CAAC;gBACd,MAAM,EAAE,eAAe;gBACvB,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC;gBAC9C,KAAK,EAAE,OAAO;aACf,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC;IAEF,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAC3B,8EAA8E;IAC9E,iEAAiE;IACjE,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;IAE1C,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,QAAQ,EAAE,MAAM,IAAI,eAAe,CAAC;IACnD,MAAM,MAAM,GAAG,QAAQ,EAAE,MAAM,IAAI,IAAI,CAAC;IAExC,4EAA4E;IAC5E,8EAA8E;IAC9E,oEAAoE;IACpE,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;IAEtD,IAAI,EAAU,CAAC;IACf,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACvC,EAAE,GAAG,GAAG,MAAM,IAAI,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,CAAC;IACnD,CAAC;SAAM,IAAI,UAAU,EAAE,CAAC;QACtB,EAAE,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;SAAM,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAC3B,4EAA4E;QAC5E,0EAA0E;QAC1E,mEAAmE;QACnE,EAAE,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,EAAE,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;IAC3C,CAAC;IAED,GAAG,CAAC,EAAE,CAAC,CAAC;IACR,IAAI,KAAK,CAAC,kBAAkB,KAAK,KAAK,EAAE,CAAC;QACvC,GAAG,CAAC,IAAI,CAAC,CAAC;QACV,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChB,GAAG,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,GAAG,CAAC,UAAU,CAAC,CAAC;IAChB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,eAAe;QAAE,GAAG,CAAC,KAAK,CAAC,CAAC;IACtD,IAAI,KAAK,CAAC,OAAO;QAAE,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAEjD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,SAAS,GAAG,eAAe,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACrD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,yEAAyE;YACzE,2EAA2E;YAC3E,mCAAmC;YACnC,KAAK,MAAM,MAAM,IAAI,SAAS;gBAAE,GAAG,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC;QAC7D,CAAC;aAAM,CAAC;YACN,wEAAwE;YACxE,kDAAkD;YAClD,KAAK,MAAM,MAAM,IAAI,SAAS;gBAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC;AACvD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,KAAK;SACT,IAAI,EAAE;SACN,WAAW,EAAE;SACb,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC;SACrC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;SACxB,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACjD,2DAA2D;IAC3D,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC;AAC3E,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;IACpE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,EAAG,KAAK,CAAC,CAAC,CAAY,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC,EAAE,CAAC;AACzG,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7C,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChE,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,kBAAkB,GAAG,6CAA6C,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,aAAa,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;IACjD,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACrE,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,2EAA2E;IAC3E,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,4EAA4E;IAC5E,uEAAuE;IACvE,IAAI,CAAC,8BAA8B,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IACjE,OAAO,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC9E,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpD,uEAAuE;IACvE,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAW,CAAC,CAAC;QACxD,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF,SAAS,QAAQ,CAAC,SAAiB;IACjC,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjD,4EAA4E;IAC5E,6EAA6E;IAC7E,qBAAqB;IACrB,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,KAAK,GAAG,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACzC,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,SAAiB;IACvC,MAAM,GAAG,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3D,CAAC;AAED,yFAAyF;AACzF,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,QAAQ,GAAG,+CAA+C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5E,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC,CAAC,CAAW,CAAC;IAC3C,MAAM,IAAI,GAAG,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC,CAAC,CAAW,CAAC;IACnC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2DAA2D;AAC3D,SAAS,SAAS,CAAC,OAAsB;IACvC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,KAAK,GAAG,uCAAuC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpE,OAAO,KAAK,CAAC,CAAC,CAAE,KAAK,CAAC,CAAC,CAAY,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,SAAS,SAAS,CAAC,MAAc,EAAE,SAAiD;IAClF,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;IACvE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,6EAA6E;AAC7E,SAAS,YAAY,CAAC,MAAc,EAAE,SAAiD;IACrF,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE,CAAC;QAChC,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9C,IAAI,MAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,MAAM;YAAE,OAAO,MAAM,CAAC,MAAM,CAAC;IAC7E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,6DAA6D;AAC7D,SAAS,eAAe,CAAC,MAAc,EAAE,SAAiD;IACxF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACjD,IAAI,QAAQ,KAAK,IAAI;QAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IACvE,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS,EAAE,CAAS;IAChD,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;QACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAAE,SAAS;YAC5B,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;gBACjB,KAAK,GAAG,CAAC,CAAC;gBACV,SAAS;YACX,CAAC;YACD,yEAAyE;YACzE,6BAA6B;YAC7B,OAAO,KAAK,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACxG,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5D,IAAI,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;YAAE,SAAS;QACnC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["/**\n * Document identity and reference normalisation.\n *\n * Foreign-key validation across a corpus of Markdown only works if `ADR-7`,\n * `ADR-0007`, `adr 7`, `[[0007-sharding]]` and `../adr/0007-sharding.md` are\n * understood to name the same document. Getting that wrong in either direction\n * is fatal: too strict and every real repository drowns in false \"broken\n * reference\" findings, too loose and `RFC 7` silently resolves to `ADR-7`.\n *\n * The resolution here is deliberately asymmetric. **Textual aliases** (`adr-7`)\n * are global, because a prefix already names the family. **Bare numbers** (`7`,\n * `#0007`) are resolved only within the citing document's own family, because a\n * repository with both `adr/0007` and `rfc/0007` is normal and guessing between\n * them would be worse than reporting nothing.\n */\n\n/** Directory names that name a specification family, mapped to a canonical prefix. */\nconst FAMILY_DIRECTORIES: Readonly<Record<string, string>> = {\n adr: 'ADR',\n adrs: 'ADR',\n 'architecture-decisions': 'ADR',\n 'decision-records': 'ADR',\n decisions: 'ADR',\n rfc: 'RFC',\n rfcs: 'RFC',\n kep: 'KEP',\n keps: 'KEP',\n enhancements: 'KEP',\n proposal: 'PROPOSAL',\n proposals: 'PROPOSAL',\n design: 'DESIGN',\n designs: 'DESIGN',\n prd: 'PRD',\n prds: 'PRD',\n spec: 'SPEC',\n specs: 'SPEC',\n};\n\n/** Front-matter keys that may carry an explicit identifier, in preference order. */\nexport const ID_KEYS: readonly string[] = ['id', 'adr', 'adr-id', 'rfc', 'rfc-id', 'kep-number', 'number', 'slug'];\n\n/** `ADR-0007`, `KEP 1234`, `rfc2119`: a family prefix followed by a number. */\nconst PREFIXED_ID = /^([A-Za-z][A-Za-z0-9_]{0,15}?)[\\s._-]*(\\d{1,6})$/;\n/** A bare number, optionally hash-prefixed: `7`, `#0007`. */\nconst BARE_NUMBER = /^#?(\\d{1,6})$/;\n\nexport interface DocumentIdentity {\n /** The canonical id: `ADR-0007`, or a path when nothing better exists. */\n readonly id: string;\n /** Family prefix, upper-cased. `null` when the document is not numbered. */\n readonly family: string | null;\n /** Sequence number without leading zeros. `null` when not numbered. */\n readonly number: number | null;\n /** Every lower-cased spelling that should resolve to this document. */\n readonly aliases: readonly string[];\n}\n\nexport interface IdentityInput {\n /** Repository-relative POSIX path. */\n readonly path: string;\n /** An id declared by a directive or front matter, if any. */\n readonly declaredId: string | null;\n /** Extra aliases declared by a directive or front matter. */\n readonly declaredAliases: readonly string[];\n /** The document H1, if any. */\n readonly heading: string | null;\n /**\n * Register the file path and stem as aliases. Default true.\n *\n * A specification that is a *region* of a file must not claim the file's\n * path: the file already answers to it, and two nodes answering to one path\n * would make every link to that file ambiguous.\n */\n readonly includePathAliases?: boolean | undefined;\n}\n\n/** An id has to be a name. Punctuation alone is what a bad parse leaves behind. */\nconst HAS_LETTER_OR_DIGIT = /[\\p{L}\\p{N}]/u;\n\n/**\n * Derives a document's canonical id and every alias that should reach it.\n *\n * Evidence is used in order of how deliberate it is: an explicit declaration,\n * then the file name, then the H1. Whatever loses still contributes aliases, so\n * a reference written in any of those forms still resolves.\n */\nexport function identify(input: IdentityInput): DocumentIdentity {\n // A declaration carrying no letter or digit is discarded rather than honoured.\n // `<!-- @spec-node id=\\\"ADR-9\\\" -->`, written inside a JavaScript string in a\n // worked example, parses its bare value as a lone backslash - and a document\n // whose id is `\\` collides with every other one that made the same mistake,\n // exports as invalid Mermaid, and names itself in findings a reader cannot act\n // on. There is nothing to gain by taking such a value seriously.\n const declaredId = input.declaredId !== null && HAS_LETTER_OR_DIGIT.test(input.declaredId) ? input.declaredId : null;\n const stem = fileStem(input.path);\n const directoryFamily = familyFromPath(input.path);\n const aliases = new Set<string>();\n\n const add = (value: string | null | undefined): void => {\n if (!value) return;\n const key = normaliseRef(value);\n if (key.length > 0) aliases.add(key);\n };\n\n const candidates: { family: string | null; number: number | null; label: string }[] = [];\n\n const consider = (raw: string | null, allowBare: boolean): void => {\n if (!raw) return;\n const cleaned = raw.trim();\n if (cleaned.length === 0) return;\n const prefixed = PREFIXED_ID.exec(cleaned);\n if (prefixed) {\n candidates.push({\n family: (prefixed[1] as string).toUpperCase(),\n number: Number.parseInt(prefixed[2] as string, 10),\n label: cleaned,\n });\n return;\n }\n const bare = BARE_NUMBER.exec(cleaned);\n if (bare && allowBare) {\n candidates.push({\n family: directoryFamily,\n number: Number.parseInt(bare[1] as string, 10),\n label: cleaned,\n });\n return;\n }\n candidates.push({ family: null, number: null, label: cleaned });\n };\n\n consider(declaredId, true);\n // `0007-sharding-the-write-path.md` and `kep-1234-foo.md` both start with the\n // identifier; take the leading token rather than the whole stem.\n consider(leadingToken(stem), true);\n consider(headingId(input.heading), false);\n\n const numbered = candidates.find((c) => c.number !== null);\n const family = numbered?.family ?? directoryFamily;\n const number = numbered?.number ?? null;\n\n // Witnesses to the local zero-padding convention, most authoritative first.\n // The spelling that actually produced the number comes first: an id read from\n // the H1 of `sharding.md` must still render as ADR-0007, not ADR-7.\n const witnesses = [numbered?.label, declaredId, stem];\n\n let id: string;\n if (number !== null && family !== null) {\n id = `${family}-${padNumber(number, witnesses)}`;\n } else if (declaredId) {\n id = declaredId.trim();\n } else if (number !== null) {\n // Numbered but with no family to prefix - a Rust RFC in `text/0001-foo.md`,\n // say. The file stem is a far better identity than a bare \"1\": it is what\n // people actually type, and it stays unique across the repository.\n id = stem.length > 0 ? stem : String(number);\n } else {\n id = stem.length > 0 ? stem : input.path;\n }\n\n add(id);\n if (input.includePathAliases !== false) {\n add(stem);\n add(input.path);\n add(stripExtension(input.path));\n }\n add(declaredId);\n for (const alias of input.declaredAliases) add(alias);\n if (input.heading) add(headingId(input.heading));\n\n if (number !== null) {\n const spellings = numberSpellings(number, witnesses);\n if (family !== null) {\n // Every spelling a human might type. `normaliseRef` folds separators, so\n // `adr-7`, `adr 7` and `adr7` collapse to one key; the padding variants do\n // not, and each needs registering.\n for (const digits of spellings) add(`${family}-${digits}`);\n } else {\n // With no family there is only one number space in the repository, so a\n // bare `0001` is unambiguous and worth resolving.\n for (const digits of spellings) add(digits);\n }\n }\n\n return { id, family, number, aliases: [...aliases] };\n}\n\n/**\n * Folds a reference to its lookup key.\n *\n * Case, separators and surrounding punctuation carry no meaning in a citation,\n * so `ADR-0007`, `adr 0007` and `Adr_0007` all become `adr0007`.\n */\nexport function normaliseRef(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/^[#<([]+|[)\\]>.,;:]+$/g, '')\n .replace(/[\\s._-]+/g, '')\n .trim();\n}\n\n/** Splits a `target#anchor` reference. */\nexport function splitAnchor(target: string): { target: string; anchor: string | null } {\n const hash = target.indexOf('#');\n if (hash === -1) return { target, anchor: null };\n // A leading `#` is an in-document anchor, not a separator.\n if (hash === 0) return { target: '', anchor: target.slice(1) };\n return { target: target.slice(0, hash), anchor: target.slice(hash + 1) };\n}\n\n/** Parses `ADR-0007` into its family and number, or returns `null`. */\nexport function parsePrefixedRef(value: string): { family: string; number: number } | null {\n const match = PREFIXED_ID.exec(value.trim().replace(/^[#([]+/, ''));\n if (!match) return null;\n return { family: (match[1] as string).toUpperCase(), number: Number.parseInt(match[2] as string, 10) };\n}\n\n/** Parses a bare `7` or `#0007`, or returns `null`. */\nexport function parseBareRef(value: string): number | null {\n const match = BARE_NUMBER.exec(value.trim());\n return match ? Number.parseInt(match[1] as string, 10) : null;\n}\n\n/** True when a reference target names a location rather than an identifier. */\nexport function looksLikePath(target: string): boolean {\n return target.includes('/') || DOCUMENT_EXTENSION.test(target);\n}\n\nconst DOCUMENT_EXTENSION = /\\.(md|markdown|mdx|txt|rst|adoc|asciidoc)$/i;\n\n/**\n * True when a path-like target could name a specification.\n *\n * A specification linking to `../../src/rules.ts` is citing source code, which\n * is a different tool's job. Treating it as a dangling specification reference\n * would report an error on one of the most ordinary things a design document\n * does, so targets carrying a non-document extension are not references at all.\n */\nexport function isDocumentTarget(target: string): boolean {\n const withoutAnchor = splitAnchor(target).target;\n const base = withoutAnchor.slice(withoutAnchor.lastIndexOf('/') + 1);\n const dot = base.lastIndexOf('.');\n // No extension: a directory, or an extensionless spec such as `rfcs/0007`.\n if (dot <= 0) return true;\n const extension = base.slice(dot);\n // Only a plausible file extension disqualifies a target. An identifier like\n // `v1.2` or `ADR-0007.1` keeps its dot and is still resolved as an id.\n if (!/^\\.[A-Za-z][A-Za-z0-9]{0,5}$/.test(extension)) return true;\n return DOCUMENT_EXTENSION.test(base);\n}\n\n/** True when a reference target points outside the repository. */\nexport function isExternal(target: string): boolean {\n return /^[a-z][a-z0-9+.-]*:/i.test(target) && !/^[a-z]:[\\\\/]/i.test(target);\n}\n\n/** The family prefix implied by a document's directory, if any. */\nexport function familyFromPath(posixPath: string): string | null {\n const segments = posixPath.toLowerCase().split('/');\n // Nearest enclosing directory wins: `docs/rfcs/adr/0007.md` is an ADR.\n for (let i = segments.length - 2; i >= 0; i -= 1) {\n const found = FAMILY_DIRECTORIES[segments[i] as string];\n if (found) return found;\n }\n return null;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Internals */\n/* -------------------------------------------------------------------------- */\n\nfunction fileStem(posixPath: string): string {\n const base = posixPath.slice(posixPath.lastIndexOf('/') + 1);\n const dot = base.lastIndexOf('.');\n const stem = dot > 0 ? base.slice(0, dot) : base;\n // `docs/adr/0007-sharding/README.md` is identified by its directory - but a\n // README at the repository root has no directory to be named after, and must\n // keep its own name.\n const slash = posixPath.lastIndexOf('/');\n if (slash > 0 && /^(readme|index)$/i.test(stem)) {\n const parent = posixPath.slice(0, slash);\n return parent.slice(parent.lastIndexOf('/') + 1);\n }\n return stem;\n}\n\nfunction stripExtension(posixPath: string): string {\n const dot = posixPath.lastIndexOf('.');\n const slash = posixPath.lastIndexOf('/');\n return dot > slash ? posixPath.slice(0, dot) : posixPath;\n}\n\n/** `kep-1234-foo` -> `kep-1234`; `0007-sharding` -> `0007`; `sharding` -> `sharding`. */\nfunction leadingToken(stem: string): string {\n const prefixed = /^([A-Za-z]{1,15}[\\s._-]?\\d{1,6})(?:[\\s._-]|$)/.exec(stem);\n if (prefixed) return prefixed[1] as string;\n const bare = /^(\\d{1,6})(?:[\\s._-]|$)/.exec(stem);\n if (bare) return bare[1] as string;\n return stem;\n}\n\n/** `# ADR-0007: Sharding the write path` -> `ADR-0007`. */\nfunction headingId(heading: string | null): string | null {\n if (!heading) return null;\n const match = /^\\s*([A-Za-z]{1,15}[\\s._-]?\\d{1,6})\\b/.exec(heading);\n return match ? (match[1] as string) : null;\n}\n\n/**\n * Chooses the digit width of the canonical id.\n *\n * A repository that writes `0007` everywhere should see `ADR-0007` in reports,\n * not `ADR-7`. The file name is the most reliable witness of the local\n * convention, so it decides.\n */\nfunction padNumber(number: number, witnesses: readonly (string | null | undefined)[]): string {\n const width = paddingWidth(number, witnesses) ?? String(number).length;\n return String(number).padStart(width, '0');\n}\n\n/** The digit width of the first witness that actually spells this number. */\nfunction paddingWidth(number: number, witnesses: readonly (string | null | undefined)[]): number | null {\n for (const witness of witnesses) {\n if (!witness) continue;\n const digits = /(\\d{1,6})/.exec(witness)?.[1];\n if (digits && Number.parseInt(digits, 10) === number) return digits.length;\n }\n return null;\n}\n\n/** Zero-padded spellings a citation might reasonably use. */\nfunction numberSpellings(number: number, witnesses: readonly (string | null | undefined)[]): string[] {\n const out = new Set<string>([String(number)]);\n const observed = paddingWidth(number, witnesses);\n if (observed !== null) out.add(String(number).padStart(observed, '0'));\n for (const width of [3, 4]) out.add(String(number).padStart(width, '0'));\n return [...out];\n}\n\n/**\n * True when two folded spellings are one edit apart.\n *\n * One edit, not two, and a transposition counts as one: `supercedesby` for\n * `supercededby` is a slip of the finger, `categories` for `dependencies` is a\n * different word. Written out rather than as a distance matrix because the\n * answer is a yes or a no and the bound is one - the general algorithm would be\n * more code, not less.\n */\nexport function withinOneEdit(a: string, b: string): boolean {\n if (a === b) return false;\n if (a.length === b.length) {\n let first = -1;\n for (let i = 0; i < a.length; i += 1) {\n if (a[i] === b[i]) continue;\n if (first === -1) {\n first = i;\n continue;\n }\n // A second difference is allowed only if the two are a swapped pair, and\n // only if there is no third.\n return first === i - 1 && a[first] === b[i] && a[i] === b[first] && a.slice(i + 1) === b.slice(i + 1);\n }\n return true;\n }\n const [short, long] = a.length < b.length ? [a, b] : [b, a];\n if (long.length - short.length !== 1) return false;\n for (let i = 0; i < short.length; i += 1) {\n if (short[i] === long[i]) continue;\n return short.slice(i) === long.slice(i + 1);\n }\n return true;\n}\n"]}
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Document identity and reference normalisation.\n *\n * Foreign-key validation across a corpus of Markdown only works if `ADR-7`,\n * `ADR-0007`, `adr 7`, `[[0007-sharding]]` and `../adr/0007-sharding.md` are\n * understood to name the same document. Getting that wrong in either direction\n * is fatal: too strict and every real repository drowns in false \"broken\n * reference\" findings, too loose and `RFC 7` silently resolves to `ADR-7`.\n *\n * The resolution here is deliberately asymmetric. **Textual aliases** (`adr-7`)\n * are global, because a prefix already names the family. **Bare numbers** (`7`,\n * `#0007`) are resolved only within the citing document's own family, because a\n * repository with both `adr/0007` and `rfc/0007` is normal and guessing between\n * them would be worse than reporting nothing.\n */\n\n/** Directory names that name a specification family, mapped to a canonical prefix. */\nconst FAMILY_DIRECTORIES: Readonly<Record<string, string>> = {\n adr: 'ADR',\n adrs: 'ADR',\n 'architecture-decisions': 'ADR',\n 'decision-records': 'ADR',\n decisions: 'ADR',\n rfc: 'RFC',\n rfcs: 'RFC',\n kep: 'KEP',\n keps: 'KEP',\n enhancements: 'KEP',\n proposal: 'PROPOSAL',\n proposals: 'PROPOSAL',\n design: 'DESIGN',\n designs: 'DESIGN',\n prd: 'PRD',\n prds: 'PRD',\n spec: 'SPEC',\n specs: 'SPEC',\n};\n\n/** Front-matter keys that may carry an explicit identifier, in preference order. */\nexport const ID_KEYS: readonly string[] = ['id', 'adr', 'adr-id', 'rfc', 'rfc-id', 'kep-number', 'number', 'slug'];\n\n/** `ADR-0007`, `KEP 1234`, `rfc2119`: a family prefix followed by a number. */\nconst PREFIXED_ID = /^([A-Za-z][A-Za-z0-9_]{0,15}?)[\\s._-]*(\\d{1,6})$/;\n/** A bare number, optionally hash-prefixed: `7`, `#0007`. */\nconst BARE_NUMBER = /^#?(\\d{1,6})$/;\n\nexport interface DocumentIdentity {\n /** The canonical id: `ADR-0007`, or a path when nothing better exists. */\n readonly id: string;\n /** Family prefix, upper-cased. `null` when the document is not numbered. */\n readonly family: string | null;\n /** Sequence number without leading zeros. `null` when not numbered. */\n readonly number: number | null;\n /** Every lower-cased spelling that should resolve to this document. */\n readonly aliases: readonly string[];\n}\n\nexport interface IdentityInput {\n /** Repository-relative POSIX path. */\n readonly path: string;\n /** An id declared by a directive or front matter, if any. */\n readonly declaredId: string | null;\n /** Extra aliases declared by a directive or front matter. */\n readonly declaredAliases: readonly string[];\n /** The document H1, if any. */\n readonly heading: string | null;\n /**\n * Whether this identity is the file's own. Default true.\n *\n * A specification that is a *region* of a file must not claim the file's\n * path: the file already answers to it, and two nodes answering to one path\n * would make every link to that file ambiguous. The file's *name* is the same\n * claim spelled differently, so a region does not read a number out of it\n * either: every row of a register kept in `0042-open-issues.md` answered to\n * ADR-0042, and so did the file.\n */\n readonly includePathAliases?: boolean | undefined;\n}\n\n/** An id has to be a name. Punctuation alone is what a bad parse leaves behind. */\nconst HAS_LETTER_OR_DIGIT = /[\\p{L}\\p{N}]/u;\n\n/**\n * Derives a document's canonical id and every alias that should reach it.\n *\n * Evidence is used in order of how deliberate it is: an explicit declaration,\n * then the file name, then the H1. The first two both *name* the document, so a\n * declaration that carries no number still takes the number from the file name\n * beside it - `slug: sharding-the-write-path` in `0007-sharding.md` is ADR-0007.\n * A title only *describes* it, so it is read as a name only where nothing else\n * has given one. A losing declaration or file name still contributes aliases,\n * so a reference written in either form resolves; a title that lost does not,\n * because the identifier it opens with belongs to the document it is about.\n */\nexport function identify(input: IdentityInput): DocumentIdentity {\n // A declaration carrying no letter or digit is discarded rather than honoured.\n // `<!-- @spec-node id=\\\"ADR-9\\\" -->`, written inside a JavaScript string in a\n // worked example, parses its bare value as a lone backslash - and a document\n // whose id is `\\` collides with every other one that made the same mistake,\n // exports as invalid Mermaid, and names itself in findings a reader cannot act\n // on. There is nothing to gain by taking such a value seriously.\n const declaredId = input.declaredId !== null && HAS_LETTER_OR_DIGIT.test(input.declaredId) ? input.declaredId : null;\n // What the flag decides, named for what it means: the file's path and its\n // name are one claim, and a region makes neither of them.\n const ownsFile = input.includePathAliases !== false;\n const stem = fileStem(input.path);\n const directoryFamily = familyFromPath(input.path);\n const aliases = new Set<string>();\n\n const add = (value: string | null | undefined): void => {\n if (!value) return;\n const key = normaliseRef(value);\n if (key.length > 0) aliases.add(key);\n };\n\n const candidates: { family: string | null; number: number | null; label: string }[] = [];\n\n const consider = (raw: string | null, allowBare: boolean): void => {\n if (!raw) return;\n const cleaned = raw.trim();\n if (cleaned.length === 0) return;\n const prefixed = PREFIXED_ID.exec(cleaned);\n if (prefixed) {\n candidates.push({\n family: (prefixed[1] as string).toUpperCase(),\n number: Number.parseInt(prefixed[2] as string, 10),\n label: cleaned,\n });\n return;\n }\n const bare = BARE_NUMBER.exec(cleaned);\n if (bare && allowBare) {\n candidates.push({\n family: directoryFamily,\n number: Number.parseInt(bare[1] as string, 10),\n label: cleaned,\n });\n return;\n }\n candidates.push({ family: null, number: null, label: cleaned });\n };\n\n consider(declaredId, true);\n // `0007-sharding-the-write-path.md` and `kep-1234-foo.md` both start with the\n // identifier; take the leading token rather than the whole stem. Only for the\n // file's own identity: the name names the file, and a region reading its\n // number is the collision ADR-0009 forbids, reached by a second route.\n if (ownsFile) consider(leadingToken(stem), true);\n\n // A title is prose *about* a document, so it names one only where nothing has\n // named it yet, and where it does not it registers no alias either - an alias\n // is the same mistake one step removed, landing on a document that is real.\n // A number disqualifies it, because a different number in a title is about a\n // different document; so does being a region, because a row's id column\n // exists to hold an identifier while its title cell is prose about the rest\n // of the corpus. `| OI-V-05 | ADR-040's enforcement point has no browser\n // test |` became ADR-040, collided with the real one and left the issue with\n // no node at all: 27 of 232 rows of one register, nine inventing a `STAGE-1`\n // nobody had written. A *file* keeps the looser reading deliberately - there\n // the two cases are one shape, `slug:` holds a URL segment rather than an id,\n // and `slug: sharding` under `# ADR-0007` has to stay ADR-0007. See the fifth\n // asymmetry in ADR-0004 for what that costs and why it is not guessed at.\n const titleId = ownsFile && !candidates.some((c) => c.number !== null) ? headingId(input.heading) : null;\n consider(titleId, false);\n\n const numbered = candidates.find((c) => c.number !== null);\n const family = numbered?.family ?? directoryFamily;\n const number = numbered?.number ?? null;\n\n // Witnesses to the local zero-padding convention, most authoritative first.\n // The spelling that actually produced the number comes first: an id read from\n // the H1 of `sharding.md` must still render as ADR-0007, not ADR-7.\n const witnesses = [numbered?.label, declaredId, stem];\n\n let id: string;\n if (number !== null && family !== null) {\n id = `${family}-${padNumber(number, witnesses)}`;\n } else if (declaredId) {\n id = declaredId.trim();\n } else if (number !== null) {\n // Numbered but with no family to prefix - a Rust RFC in `text/0001-foo.md`,\n // say. The file stem is a far better identity than a bare \"1\": it is what\n // people actually type, and it stays unique across the repository.\n id = stem.length > 0 ? stem : String(number);\n } else {\n id = stem.length > 0 ? stem : input.path;\n }\n\n add(id);\n if (ownsFile) {\n add(stem);\n add(input.path);\n add(stripExtension(input.path));\n }\n add(declaredId);\n for (const alias of input.declaredAliases) add(alias);\n add(titleId);\n\n if (number !== null) {\n const spellings = numberSpellings(number, witnesses);\n if (family !== null) {\n // Every spelling a human might type. `normaliseRef` folds separators, so\n // `adr-7`, `adr 7` and `adr7` collapse to one key; the padding variants do\n // not, and each needs registering.\n for (const digits of spellings) add(`${family}-${digits}`);\n } else {\n // With no family there is only one number space in the repository, so a\n // bare `0001` is unambiguous and worth resolving.\n for (const digits of spellings) add(digits);\n }\n }\n\n return { id, family, number, aliases: [...aliases] };\n}\n\n/**\n * Folds a reference to its lookup key.\n *\n * Case, separators and surrounding punctuation carry no meaning in a citation,\n * so `ADR-0007`, `adr 0007` and `Adr_0007` all become `adr0007`.\n */\nexport function normaliseRef(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/^[#<([]+|[)\\]>.,;:]+$/g, '')\n .replace(/[\\s._-]+/g, '')\n .trim();\n}\n\n/** Splits a `target#anchor` reference. */\nexport function splitAnchor(target: string): { target: string; anchor: string | null } {\n const hash = target.indexOf('#');\n if (hash === -1) return { target, anchor: null };\n // A leading `#` is an in-document anchor, not a separator.\n if (hash === 0) return { target: '', anchor: target.slice(1) };\n return { target: target.slice(0, hash), anchor: target.slice(hash + 1) };\n}\n\n/** Parses `ADR-0007` into its family and number, or returns `null`. */\nexport function parsePrefixedRef(value: string): { family: string; number: number } | null {\n const match = PREFIXED_ID.exec(value.trim().replace(/^[#([]+/, ''));\n if (!match) return null;\n return { family: (match[1] as string).toUpperCase(), number: Number.parseInt(match[2] as string, 10) };\n}\n\n/** Parses a bare `7` or `#0007`, or returns `null`. */\nexport function parseBareRef(value: string): number | null {\n const match = BARE_NUMBER.exec(value.trim());\n return match ? Number.parseInt(match[1] as string, 10) : null;\n}\n\n/** True when a reference target names a location rather than an identifier. */\nexport function looksLikePath(target: string): boolean {\n return target.includes('/') || DOCUMENT_EXTENSION.test(target);\n}\n\nconst DOCUMENT_EXTENSION = /\\.(md|markdown|mdx|txt|rst|adoc|asciidoc)$/i;\n\n/**\n * True when a path-like target could name a specification.\n *\n * A specification linking to `../../src/rules.ts` is citing source code, which\n * is a different tool's job. Treating it as a dangling specification reference\n * would report an error on one of the most ordinary things a design document\n * does, so targets carrying a non-document extension are not references at all.\n */\nexport function isDocumentTarget(target: string): boolean {\n const withoutAnchor = splitAnchor(target).target;\n const base = withoutAnchor.slice(withoutAnchor.lastIndexOf('/') + 1);\n const dot = base.lastIndexOf('.');\n // No extension: a directory, or an extensionless spec such as `rfcs/0007`.\n if (dot <= 0) return true;\n const extension = base.slice(dot);\n // Only a plausible file extension disqualifies a target. An identifier like\n // `v1.2` or `ADR-0007.1` keeps its dot and is still resolved as an id.\n if (!/^\\.[A-Za-z][A-Za-z0-9]{0,5}$/.test(extension)) return true;\n return DOCUMENT_EXTENSION.test(base);\n}\n\n/** True when a reference target points outside the repository. */\nexport function isExternal(target: string): boolean {\n return /^[a-z][a-z0-9+.-]*:/i.test(target) && !/^[a-z]:[\\\\/]/i.test(target);\n}\n\n/** The family prefix implied by a document's directory, if any. */\nexport function familyFromPath(posixPath: string): string | null {\n const segments = posixPath.toLowerCase().split('/');\n // Nearest enclosing directory wins: `docs/rfcs/adr/0007.md` is an ADR.\n for (let i = segments.length - 2; i >= 0; i -= 1) {\n const found = FAMILY_DIRECTORIES[segments[i] as string];\n if (found) return found;\n }\n return null;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Internals */\n/* -------------------------------------------------------------------------- */\n\nfunction fileStem(posixPath: string): string {\n const base = posixPath.slice(posixPath.lastIndexOf('/') + 1);\n const dot = base.lastIndexOf('.');\n const stem = dot > 0 ? base.slice(0, dot) : base;\n // `docs/adr/0007-sharding/README.md` is identified by its directory - but a\n // README at the repository root has no directory to be named after, and must\n // keep its own name.\n const slash = posixPath.lastIndexOf('/');\n if (slash > 0 && /^(readme|index)$/i.test(stem)) {\n const parent = posixPath.slice(0, slash);\n return parent.slice(parent.lastIndexOf('/') + 1);\n }\n return stem;\n}\n\nfunction stripExtension(posixPath: string): string {\n const dot = posixPath.lastIndexOf('.');\n const slash = posixPath.lastIndexOf('/');\n return dot > slash ? posixPath.slice(0, dot) : posixPath;\n}\n\n/** `kep-1234-foo` -> `kep-1234`; `0007-sharding` -> `0007`; `sharding` -> `sharding`. */\nfunction leadingToken(stem: string): string {\n const prefixed = /^([A-Za-z]{1,15}[\\s._-]?\\d{1,6})(?:[\\s._-]|$)/.exec(stem);\n if (prefixed) return prefixed[1] as string;\n const bare = /^(\\d{1,6})(?:[\\s._-]|$)/.exec(stem);\n if (bare) return bare[1] as string;\n return stem;\n}\n\n/** `# ADR-0007: Sharding the write path` -> `ADR-0007`. */\nfunction headingId(heading: string | null): string | null {\n if (!heading) return null;\n const match = /^\\s*([A-Za-z]{1,15}[\\s._-]?\\d{1,6})\\b/.exec(heading);\n return match ? (match[1] as string) : null;\n}\n\n/**\n * Chooses the digit width of the canonical id.\n *\n * A repository that writes `0007` everywhere should see `ADR-0007` in reports,\n * not `ADR-7`. The file name is the most reliable witness of the local\n * convention, so it decides.\n */\nfunction padNumber(number: number, witnesses: readonly (string | null | undefined)[]): string {\n const width = paddingWidth(number, witnesses) ?? String(number).length;\n return String(number).padStart(width, '0');\n}\n\n/** The digit width of the first witness that actually spells this number. */\nfunction paddingWidth(number: number, witnesses: readonly (string | null | undefined)[]): number | null {\n for (const witness of witnesses) {\n if (!witness) continue;\n const digits = /(\\d{1,6})/.exec(witness)?.[1];\n if (digits && Number.parseInt(digits, 10) === number) return digits.length;\n }\n return null;\n}\n\n/** Zero-padded spellings a citation might reasonably use. */\nfunction numberSpellings(number: number, witnesses: readonly (string | null | undefined)[]): string[] {\n const out = new Set<string>([String(number)]);\n const observed = paddingWidth(number, witnesses);\n if (observed !== null) out.add(String(number).padStart(observed, '0'));\n for (const width of [3, 4]) out.add(String(number).padStart(width, '0'));\n return [...out];\n}\n\n/**\n * True when two folded spellings are one edit apart.\n *\n * One edit, not two, and a transposition counts as one: `supercedesby` for\n * `supercededby` is a slip of the finger, `categories` for `dependencies` is a\n * different word. Written out rather than as a distance matrix because the\n * answer is a yes or a no and the bound is one - the general algorithm would be\n * more code, not less.\n */\nexport function withinOneEdit(a: string, b: string): boolean {\n if (a === b) return false;\n if (a.length === b.length) {\n let first = -1;\n for (let i = 0; i < a.length; i += 1) {\n if (a[i] === b[i]) continue;\n if (first === -1) {\n first = i;\n continue;\n }\n // A second difference is allowed only if the two are a swapped pair, and\n // only if there is no third.\n return first === i - 1 && a[first] === b[i] && a[i] === b[first] && a.slice(i + 1) === b.slice(i + 1);\n }\n return true;\n }\n const [short, long] = a.length < b.length ? [a, b] : [b, a];\n if (long.length - short.length !== 1) return false;\n for (let i = 0; i < short.length; i += 1) {\n if (short[i] === long[i]) continue;\n return short.slice(i) === long.slice(i + 1);\n }\n return true;\n}\n"]}
package/dist/sections.js CHANGED
@@ -21,7 +21,7 @@
21
21
  * neither declares a status.
22
22
  */
23
23
  import { attr } from './directives.js';
24
- import { isExternal } from './identity.js';
24
+ import { isExternal, parsePrefixedRef } from './identity.js';
25
25
  import { isStatusHeading } from './lifecycle.js';
26
26
  /** Column headers that name the row's identifier. */
27
27
  const ID_COLUMNS = /^(?:id|ids|ref|reference|key|no|number|#|adr|rfc|kep|oi|decision|spec|specification)$/;
@@ -89,6 +89,7 @@ fileId) {
89
89
  };
90
90
  const regions = [];
91
91
  const folded = fold(fileId);
92
+ const fileRef = parsePrefixedRef(fileId);
92
93
  for (let i = 0; i < scanned.headings.length; i += 1) {
93
94
  const heading = scanned.headings[i];
94
95
  const declaredId = HEADING_ID.exec(heading.text)?.[1];
@@ -97,6 +98,15 @@ fileId) {
97
98
  // The file's own title heading names the file, not a region inside it.
98
99
  if (fold(declaredId) === folded)
99
100
  continue;
101
+ // Padding is not part of an identity anywhere else - `ADR-40`, `ADR-040`
102
+ // and `ADR-0040` all resolve to one document - so a heading that spells the
103
+ // number differently from the file name is still that file's own title.
104
+ // Comparing the text alone made `# ADR-040` inside `0040-enforce.md` a
105
+ // region within the file it names: one decision, two nodes, and a citation
106
+ // arriving at whichever spelling it happened to use.
107
+ const ref = parsePrefixedRef(declaredId);
108
+ if (ref !== null && fileRef !== null && ref.family === fileRef.family && ref.number === fileRef.number)
109
+ continue;
100
110
  const end = regionEnd(scanned, i);
101
111
  const directive = directiveIn(directives, heading.end, end);
102
112
  const status = readRegionStatus(scanned, heading.end, end);
@@ -1 +1 @@
1
- {"version":3,"file":"sections.js","sourceRoot":"","sources":["../src/sections.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Specification regions: the part of a file that one specification occupies.\n *\n * `spec-graph` began by assuming one specification per file, because that is\n * how MADR, KEPs and Rust RFCs are laid out. Plenty of organisations do not\n * work that way. A register, a whitepaper or a living architecture document\n * holds dozens of decisions in one file, each with its own identifier, its own\n * status and its own dependencies - and to an engine that binds a document to a\n * file, every one of them is invisible.\n *\n * The fix is small once stated: **a specification is a region of a file, not a\n * file.** A file yields at least one region - itself - and may yield more.\n * Everything downstream is unchanged, because a region produces exactly the\n * same `DocumentNode` a whole file produced.\n *\n * What this module decides is where those regions begin and end, and it is\n * deliberately hard to convince. A heading has to carry an identifier *and* the\n * section has to declare a status before it counts. One signal is not enough:\n * `## Q3 2026 Roadmap` parses as family `Q`, number 3, and `## v1.2.0` in a\n * changelog parses as family `v`, number 1. Neither is a specification, and\n * neither declares a status.\n */\n\nimport { attr, type Directive } from './directives.js';\nimport { isExternal } from './identity.js';\nimport { isStatusHeading } from './lifecycle.js';\nimport type { Heading, Range, ScannedDocument, Table, TableCell, TableRow } from './markdown.js';\nimport type { EdgeKind } from './types.js';\n\n/** A status as written, and where it was written. */\nexport interface RegionStatus {\n readonly text: string;\n readonly start: number;\n readonly end: number;\n}\n\n/** A relation read from a table column rather than from prose. */\nexport interface TableRelation {\n readonly kind: EdgeKind;\n readonly inverted: boolean;\n readonly target: string;\n readonly start: number;\n readonly end: number;\n /** The column header that gave it its meaning, for the diagnostic. */\n readonly column: string;\n}\n\nexport interface SpecificationRegion {\n /** The heading that opens the region, or `null` for the file or a table row. */\n readonly heading: Heading | null;\n /** The table row that is the region, when it is one. */\n readonly row: TableRow | null;\n /** Offset the region starts at. */\n readonly start: number;\n /** Offset just past the region. */\n readonly end: number;\n /** Identifier read from the heading or the identifier column. */\n readonly declaredId: string | null;\n readonly title: string | null;\n readonly status: RegionStatus | null;\n /** A `@spec-node` directive inside the region, when there is one. */\n readonly directive: Directive | null;\n /** Relations declared by column, empty for a heading region. */\n readonly relations: readonly TableRelation[];\n /**\n * Cells whose content is already accounted for as a typed relation.\n *\n * Prose scanning skips these, so a link in a `Depends on` column produces one\n * typed edge rather than a typed edge and a neutral citation beside it.\n */\n readonly claimed: readonly Range[];\n}\n\n/** Column headers that name the row's identifier. */\nconst ID_COLUMNS = /^(?:id|ids|ref|reference|key|no|number|#|adr|rfc|kep|oi|decision|spec|specification)$/;\n\n/** Column headers that name the row's status. */\nconst STATUS_COLUMNS = /^(?:status|state|stage|lifecycle|phase)$/;\n\n/** Column headers that name the row's title. */\nconst TITLE_COLUMNS = /^(?:title|name|summary|description|subject|decision|topic)$/;\n\n/**\n * Column headers that declare a relation.\n *\n * Written the way a header is written - \"Depends on\", \"Superseded by\" - rather\n * than the way a front-matter key is, because that is what an author types at\n * the top of a column.\n */\nconst RELATION_COLUMNS: Readonly<Record<string, { kind: EdgeKind; inverted: boolean }>> = {\n 'depends on': { kind: 'depends-on', inverted: false },\n 'depends-on': { kind: 'depends-on', inverted: false },\n dependencies: { kind: 'depends-on', inverted: false },\n requires: { kind: 'depends-on', inverted: false },\n supersedes: { kind: 'supersedes', inverted: false },\n replaces: { kind: 'supersedes', inverted: false },\n 'superseded by': { kind: 'supersedes', inverted: true },\n 'replaced by': { kind: 'supersedes', inverted: true },\n 'blocked by': { kind: 'blocked-by', inverted: false },\n 'blocked on': { kind: 'blocked-by', inverted: false },\n blocks: { kind: 'blocked-by', inverted: true },\n amends: { kind: 'amends', inverted: false },\n extends: { kind: 'amends', inverted: false },\n assumes: { kind: 'assumes', inverted: false },\n 'delegates to': { kind: 'delegates-to', inverted: false },\n 'delegated to': { kind: 'delegates-to', inverted: false },\n 'tracked in': { kind: 'delegates-to', inverted: false },\n related: { kind: 'relates-to', inverted: false },\n 'related to': { kind: 'relates-to', inverted: false },\n 'see also': { kind: 'relates-to', inverted: false },\n references: { kind: 'references', inverted: false },\n};\n\n/** Cell values that mean \"nothing here\". */\nconst MARKDOWN_LINK = /!?\\[([^\\]]*)\\]\\(([^)]*)\\)/g;\nconst EMPTY_CELL = /^(?:-+|—|–|n\\/?a|none|nil|tbd|\\.|_+)$/i;\n\n/** `## ADR-0007: Sharding` - an identifier at the very start of a heading. */\nconst HEADING_ID = /^\\s*([A-Za-z]{1,15}[\\s._-]?\\d{1,6})\\b/;\n\n/** `**Status:** Accepted`, `Status: Accepted`, `*Status* : Accepted`. */\nconst INLINE_STATUS = /^[ \\t>]*[*_]{0,2}\\s*status\\s*[*_]{0,2}\\s*[::]\\s*(.+?)\\s*$/im;\n\n/**\n * Finds every specification in a file.\n *\n * The first entry is always the file itself, so a caller that ignores the rest\n * behaves exactly as it did before regions existed.\n */\nexport function findSpecificationRegions(\n scanned: ScannedDocument,\n directives: readonly Directive[],\n /** The file's own identifier, so its title heading is not read as a sub-region. */\n fileId: string,\n): SpecificationRegion[] {\n const whole: SpecificationRegion = {\n heading: null,\n row: null,\n start: scanned.bodyStart,\n end: scanned.text.length,\n declaredId: null,\n title: null,\n status: null,\n directive: null,\n relations: [],\n claimed: [],\n };\n\n const regions: SpecificationRegion[] = [];\n const folded = fold(fileId);\n\n for (let i = 0; i < scanned.headings.length; i += 1) {\n const heading = scanned.headings[i] as Heading;\n const declaredId = HEADING_ID.exec(heading.text)?.[1];\n if (declaredId === undefined) continue;\n // The file's own title heading names the file, not a region inside it.\n if (fold(declaredId) === folded) continue;\n\n const end = regionEnd(scanned, i);\n const directive = directiveIn(directives, heading.end, end);\n const status = readRegionStatus(scanned, heading.end, end);\n\n // Two independent signals are required. An identifier alone matches\n // `## Q3 2026 Roadmap` and `## v1.2.0`; a status alone matches every\n // ordinary `## Status` section in a normal ADR.\n if (status === null && directive === null) continue;\n\n regions.push({\n heading,\n row: null,\n start: heading.start,\n end,\n declaredId: (directive ? attr(directive, 'id')?.value : null) ?? declaredId,\n title: heading.text,\n status,\n directive,\n relations: [],\n claimed: [],\n });\n }\n\n return [whole, ...regions, ...findTableRegions(scanned)];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Registers kept as tables */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Reads a table whose columns describe specifications.\n *\n * A table qualifies when it has an identifier column *and* either a status\n * column or at least one relation column. Two signals again, for the same\n * reason: a table of identifiers and prose is a citation list, and promoting\n * every row of it to a specification would invent a register nobody wrote.\n */\nexport function findTableRegions(scanned: ScannedDocument): SpecificationRegion[] {\n const out: SpecificationRegion[] = [];\n\n for (const table of scanned.tables) {\n const schema = readSchema(table);\n if (schema === null) continue;\n\n for (const row of table.rows) {\n const idCell = row.cells[schema.id];\n if (!idCell) continue;\n const id = flatten(idCell.text);\n if (id.length === 0 || EMPTY_CELL.test(id)) continue;\n\n const statusCell = schema.status === null ? undefined : row.cells[schema.status];\n const statusText = statusCell ? flatten(statusCell.text) : '';\n const status: RegionStatus | null =\n statusCell && statusText.length > 0 && !EMPTY_CELL.test(statusText)\n ? { text: statusText, start: statusCell.start, end: statusCell.end }\n : null;\n\n const titleCell = schema.title === null ? undefined : row.cells[schema.title];\n const title = titleCell ? flatten(titleCell.text) : null;\n\n const relations: TableRelation[] = [];\n const claimed: Range[] = [{ start: idCell.start, end: idCell.end }];\n if (statusCell) claimed.push({ start: statusCell.start, end: statusCell.end });\n\n for (const [column, relation] of schema.relations) {\n const cell = row.cells[column];\n if (!cell) continue;\n claimed.push({ start: cell.start, end: cell.end });\n for (const target of splitTargets(cell)) {\n relations.push({\n kind: relation.kind,\n inverted: relation.inverted,\n target: target.text,\n start: target.start,\n end: target.end,\n column: (table.headers[column] as TableCell).text,\n });\n }\n }\n\n out.push({\n heading: null,\n row,\n start: row.start,\n end: row.end,\n declaredId: id,\n title: title !== null && title.length > 0 ? title : null,\n status,\n directive: null,\n relations,\n claimed,\n });\n }\n }\n\n return out;\n}\n\ninterface TableSchema {\n readonly id: number;\n readonly status: number | null;\n readonly title: number | null;\n readonly relations: readonly (readonly [number, { kind: EdgeKind; inverted: boolean }])[];\n}\n\nfunction readSchema(table: Table): TableSchema | null {\n let id: number | null = null;\n let status: number | null = null;\n let title: number | null = null;\n const relations: [number, { kind: EdgeKind; inverted: boolean }][] = [];\n\n table.headers.forEach((header, column) => {\n const name = normaliseHeader(header.text);\n if (id === null && ID_COLUMNS.test(name)) {\n id = column;\n return;\n }\n if (status === null && STATUS_COLUMNS.test(name)) {\n status = column;\n return;\n }\n const relation = RELATION_COLUMNS[name];\n if (relation) {\n relations.push([column, relation]);\n return;\n }\n if (title === null && TITLE_COLUMNS.test(name)) title = column;\n });\n\n if (id === null) return null;\n if (status === null && relations.length === 0) return null;\n return { id, status, title, relations };\n}\n\nfunction normaliseHeader(text: string): string {\n return flatten(text)\n .toLowerCase()\n .replace(/[^a-z0-9/#\\s-]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** Splits a cell into the identifiers it lists, keeping each one's offset. */\nfunction splitTargets(cell: TableCell): { text: string; start: number; end: number }[] {\n const out: { text: string; start: number; end: number }[] = [];\n // Every target is reported at the cell's span. A cell is short, and an offset\n // recovered back through a flattening is an offset that can drift.\n const push = (raw: string): void => {\n const target = raw.trim();\n if (target.length === 0 || EMPTY_CELL.test(target)) return;\n out.push({ text: target, start: cell.start, end: cell.end });\n };\n const prose = (segment: string): void => {\n for (const piece of flatten(segment).split(/\\s*(?:[,;]|\\band\\b)\\s*/i)) push(piece);\n };\n\n // Links are taken out whole rather than flattened with everything else: a\n // path would otherwise be cut in half by its own slash, and its underscores\n // stripped as emphasis.\n // A fresh matcher per cell. `prose` calls `flatten`, which drives the shared\n // pattern; one /g regex advanced from inside its own scan resets its cursor\n // and never terminates.\n const links = new RegExp(MARKDOWN_LINK.source, 'g');\n let last = 0;\n for (let match = links.exec(cell.text); match !== null; match = links.exec(cell.text)) {\n prose(cell.text.slice(last, match.index));\n const destination = (match[2] ?? '').trim();\n // A link states where its target lives; its label states only what the\n // target is called (ADR-0008). In a column the author has already typed,\n // the destination is the claim - `[see](adr/0050.md)` is a dependency on\n // that file, not on the word \"see\". An external or in-page destination\n // names no document here, so the label is what is left to try.\n const local = destination.length > 0 && !destination.startsWith('#') && !isExternal(destination);\n push(local ? destination : (match[1] ?? ''));\n last = match.index + match[0].length;\n }\n prose(cell.text.slice(last));\n return out;\n}\n\n/** Reduces a cell to its text: link labels rather than link syntax. */\nfunction flatten(text: string): string {\n return text\n .replace(MARKDOWN_LINK, '$1')\n .replace(/\\[\\[(?:[^\\]|]*\\|)?([^\\]]*)\\]\\]/g, '$1')\n .replace(/[`*_~]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** Where a heading's section ends: the next heading at the same level or above. */\nfunction regionEnd(scanned: ScannedDocument, index: number): number {\n const heading = scanned.headings[index] as Heading;\n for (let i = index + 1; i < scanned.headings.length; i += 1) {\n const next = scanned.headings[i] as Heading;\n if (next.level <= heading.level) return next.start;\n }\n return scanned.text.length;\n}\n\n/**\n * Reads a status declared inside a region.\n *\n * Two forms, both common: a labelled line (`**Status:** Accepted`) and a\n * sub-heading (`### Status` with the value beneath it). Only the region is\n * searched, so one decision in a register cannot inherit its neighbour's.\n */\nexport function readRegionStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n const labelled = readLabelledStatus(scanned, start, end);\n if (labelled) return labelled;\n return readHeadedStatus(scanned, start, end);\n}\n\nfunction readLabelledStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n for (const line of scanned.lines) {\n if (line.contentStart < start) continue;\n if (line.contentStart >= end) break;\n if (line.code || line.blank) continue;\n const match = INLINE_STATUS.exec(line.content);\n if (!match) continue;\n // `**Status:** Accepted` closes its emphasis *after* the colon, so the\n // closing marker lands in the captured value. Strip it rather than let it\n // reach the report as part of the status a reader is shown.\n const value = (match[1] as string).replace(/^[*_\\s]+/, '').replace(/[*_\\s]+$/, '');\n if (value.length === 0) continue;\n const offset = line.contentStart + line.content.lastIndexOf(value);\n return { text: value, start: offset, end: offset + value.length };\n }\n return null;\n}\n\nfunction readHeadedStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n const heading = scanned.headings.find((h) => h.start >= start && h.start < end && isStatusHeading(h.text));\n if (!heading) return null;\n\n for (const line of scanned.lines) {\n if (line.line <= heading.line) continue;\n if (line.contentStart >= end) break;\n if (line.blank) continue;\n if (line.code) return null;\n const trimmed = line.content.trim();\n if (trimmed.startsWith('#')) return null;\n // A bullet list under Status is a status history; the first entry is current.\n const cleaned = trimmed.replace(/^[-*+]\\s+/, '');\n const offset = line.contentStart + line.content.indexOf(trimmed) + (trimmed.length - cleaned.length);\n return { text: cleaned, start: offset, end: offset + cleaned.length };\n }\n return null;\n}\n\n/** The last `@spec-node` directive inside a region. */\nfunction directiveIn(directives: readonly Directive[], start: number, end: number): Directive | null {\n let found: Directive | null = null;\n for (const directive of directives) {\n if (directive.name !== 'spec-node') continue;\n if (directive.start < start || directive.end > end) continue;\n if (found === null || directive.start > found.start) found = directive;\n }\n return found;\n}\n\n/** Case- and separator-insensitive comparison, matching reference folding. */\nfunction fold(value: string): string {\n return value.trim().toLowerCase().replace(/[\\s._-]+/g, '');\n}\n\n/** The region containing an offset, innermost first. `null` when only the file does. */\nexport function regionAt(regions: readonly SpecificationRegion[], offset: number): SpecificationRegion | null {\n let best: SpecificationRegion | null = null;\n for (const region of regions) {\n // The whole-file region owns nothing in particular; it is the fallback.\n if (region.heading === null && region.row === null) continue;\n if (offset < region.start || offset >= region.end) continue;\n if (best === null || region.start > best.start) best = region;\n }\n return best;\n}\n"]}
1
+ {"version":3,"file":"sections.js","sourceRoot":"","sources":["../src/sections.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Specification regions: the part of a file that one specification occupies.\n *\n * `spec-graph` began by assuming one specification per file, because that is\n * how MADR, KEPs and Rust RFCs are laid out. Plenty of organisations do not\n * work that way. A register, a whitepaper or a living architecture document\n * holds dozens of decisions in one file, each with its own identifier, its own\n * status and its own dependencies - and to an engine that binds a document to a\n * file, every one of them is invisible.\n *\n * The fix is small once stated: **a specification is a region of a file, not a\n * file.** A file yields at least one region - itself - and may yield more.\n * Everything downstream is unchanged, because a region produces exactly the\n * same `DocumentNode` a whole file produced.\n *\n * What this module decides is where those regions begin and end, and it is\n * deliberately hard to convince. A heading has to carry an identifier *and* the\n * section has to declare a status before it counts. One signal is not enough:\n * `## Q3 2026 Roadmap` parses as family `Q`, number 3, and `## v1.2.0` in a\n * changelog parses as family `v`, number 1. Neither is a specification, and\n * neither declares a status.\n */\n\nimport { attr, type Directive } from './directives.js';\nimport { isExternal, parsePrefixedRef } from './identity.js';\nimport { isStatusHeading } from './lifecycle.js';\nimport type { Heading, Range, ScannedDocument, Table, TableCell, TableRow } from './markdown.js';\nimport type { EdgeKind } from './types.js';\n\n/** A status as written, and where it was written. */\nexport interface RegionStatus {\n readonly text: string;\n readonly start: number;\n readonly end: number;\n}\n\n/** A relation read from a table column rather than from prose. */\nexport interface TableRelation {\n readonly kind: EdgeKind;\n readonly inverted: boolean;\n readonly target: string;\n readonly start: number;\n readonly end: number;\n /** The column header that gave it its meaning, for the diagnostic. */\n readonly column: string;\n}\n\nexport interface SpecificationRegion {\n /** The heading that opens the region, or `null` for the file or a table row. */\n readonly heading: Heading | null;\n /** The table row that is the region, when it is one. */\n readonly row: TableRow | null;\n /** Offset the region starts at. */\n readonly start: number;\n /** Offset just past the region. */\n readonly end: number;\n /** Identifier read from the heading or the identifier column. */\n readonly declaredId: string | null;\n readonly title: string | null;\n readonly status: RegionStatus | null;\n /** A `@spec-node` directive inside the region, when there is one. */\n readonly directive: Directive | null;\n /** Relations declared by column, empty for a heading region. */\n readonly relations: readonly TableRelation[];\n /**\n * Cells whose content is already accounted for as a typed relation.\n *\n * Prose scanning skips these, so a link in a `Depends on` column produces one\n * typed edge rather than a typed edge and a neutral citation beside it.\n */\n readonly claimed: readonly Range[];\n}\n\n/** Column headers that name the row's identifier. */\nconst ID_COLUMNS = /^(?:id|ids|ref|reference|key|no|number|#|adr|rfc|kep|oi|decision|spec|specification)$/;\n\n/** Column headers that name the row's status. */\nconst STATUS_COLUMNS = /^(?:status|state|stage|lifecycle|phase)$/;\n\n/** Column headers that name the row's title. */\nconst TITLE_COLUMNS = /^(?:title|name|summary|description|subject|decision|topic)$/;\n\n/**\n * Column headers that declare a relation.\n *\n * Written the way a header is written - \"Depends on\", \"Superseded by\" - rather\n * than the way a front-matter key is, because that is what an author types at\n * the top of a column.\n */\nconst RELATION_COLUMNS: Readonly<Record<string, { kind: EdgeKind; inverted: boolean }>> = {\n 'depends on': { kind: 'depends-on', inverted: false },\n 'depends-on': { kind: 'depends-on', inverted: false },\n dependencies: { kind: 'depends-on', inverted: false },\n requires: { kind: 'depends-on', inverted: false },\n supersedes: { kind: 'supersedes', inverted: false },\n replaces: { kind: 'supersedes', inverted: false },\n 'superseded by': { kind: 'supersedes', inverted: true },\n 'replaced by': { kind: 'supersedes', inverted: true },\n 'blocked by': { kind: 'blocked-by', inverted: false },\n 'blocked on': { kind: 'blocked-by', inverted: false },\n blocks: { kind: 'blocked-by', inverted: true },\n amends: { kind: 'amends', inverted: false },\n extends: { kind: 'amends', inverted: false },\n assumes: { kind: 'assumes', inverted: false },\n 'delegates to': { kind: 'delegates-to', inverted: false },\n 'delegated to': { kind: 'delegates-to', inverted: false },\n 'tracked in': { kind: 'delegates-to', inverted: false },\n related: { kind: 'relates-to', inverted: false },\n 'related to': { kind: 'relates-to', inverted: false },\n 'see also': { kind: 'relates-to', inverted: false },\n references: { kind: 'references', inverted: false },\n};\n\n/** Cell values that mean \"nothing here\". */\nconst MARKDOWN_LINK = /!?\\[([^\\]]*)\\]\\(([^)]*)\\)/g;\nconst EMPTY_CELL = /^(?:-+|—|–|n\\/?a|none|nil|tbd|\\.|_+)$/i;\n\n/** `## ADR-0007: Sharding` - an identifier at the very start of a heading. */\nconst HEADING_ID = /^\\s*([A-Za-z]{1,15}[\\s._-]?\\d{1,6})\\b/;\n\n/** `**Status:** Accepted`, `Status: Accepted`, `*Status* : Accepted`. */\nconst INLINE_STATUS = /^[ \\t>]*[*_]{0,2}\\s*status\\s*[*_]{0,2}\\s*[::]\\s*(.+?)\\s*$/im;\n\n/**\n * Finds every specification in a file.\n *\n * The first entry is always the file itself, so a caller that ignores the rest\n * behaves exactly as it did before regions existed.\n */\nexport function findSpecificationRegions(\n scanned: ScannedDocument,\n directives: readonly Directive[],\n /** The file's own identifier, so its title heading is not read as a sub-region. */\n fileId: string,\n): SpecificationRegion[] {\n const whole: SpecificationRegion = {\n heading: null,\n row: null,\n start: scanned.bodyStart,\n end: scanned.text.length,\n declaredId: null,\n title: null,\n status: null,\n directive: null,\n relations: [],\n claimed: [],\n };\n\n const regions: SpecificationRegion[] = [];\n const folded = fold(fileId);\n const fileRef = parsePrefixedRef(fileId);\n\n for (let i = 0; i < scanned.headings.length; i += 1) {\n const heading = scanned.headings[i] as Heading;\n const declaredId = HEADING_ID.exec(heading.text)?.[1];\n if (declaredId === undefined) continue;\n // The file's own title heading names the file, not a region inside it.\n if (fold(declaredId) === folded) continue;\n // Padding is not part of an identity anywhere else - `ADR-40`, `ADR-040`\n // and `ADR-0040` all resolve to one document - so a heading that spells the\n // number differently from the file name is still that file's own title.\n // Comparing the text alone made `# ADR-040` inside `0040-enforce.md` a\n // region within the file it names: one decision, two nodes, and a citation\n // arriving at whichever spelling it happened to use.\n const ref = parsePrefixedRef(declaredId);\n if (ref !== null && fileRef !== null && ref.family === fileRef.family && ref.number === fileRef.number) continue;\n\n const end = regionEnd(scanned, i);\n const directive = directiveIn(directives, heading.end, end);\n const status = readRegionStatus(scanned, heading.end, end);\n\n // Two independent signals are required. An identifier alone matches\n // `## Q3 2026 Roadmap` and `## v1.2.0`; a status alone matches every\n // ordinary `## Status` section in a normal ADR.\n if (status === null && directive === null) continue;\n\n regions.push({\n heading,\n row: null,\n start: heading.start,\n end,\n declaredId: (directive ? attr(directive, 'id')?.value : null) ?? declaredId,\n title: heading.text,\n status,\n directive,\n relations: [],\n claimed: [],\n });\n }\n\n return [whole, ...regions, ...findTableRegions(scanned)];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Registers kept as tables */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Reads a table whose columns describe specifications.\n *\n * A table qualifies when it has an identifier column *and* either a status\n * column or at least one relation column. Two signals again, for the same\n * reason: a table of identifiers and prose is a citation list, and promoting\n * every row of it to a specification would invent a register nobody wrote.\n */\nexport function findTableRegions(scanned: ScannedDocument): SpecificationRegion[] {\n const out: SpecificationRegion[] = [];\n\n for (const table of scanned.tables) {\n const schema = readSchema(table);\n if (schema === null) continue;\n\n for (const row of table.rows) {\n const idCell = row.cells[schema.id];\n if (!idCell) continue;\n const id = flatten(idCell.text);\n if (id.length === 0 || EMPTY_CELL.test(id)) continue;\n\n const statusCell = schema.status === null ? undefined : row.cells[schema.status];\n const statusText = statusCell ? flatten(statusCell.text) : '';\n const status: RegionStatus | null =\n statusCell && statusText.length > 0 && !EMPTY_CELL.test(statusText)\n ? { text: statusText, start: statusCell.start, end: statusCell.end }\n : null;\n\n const titleCell = schema.title === null ? undefined : row.cells[schema.title];\n const title = titleCell ? flatten(titleCell.text) : null;\n\n const relations: TableRelation[] = [];\n const claimed: Range[] = [{ start: idCell.start, end: idCell.end }];\n if (statusCell) claimed.push({ start: statusCell.start, end: statusCell.end });\n\n for (const [column, relation] of schema.relations) {\n const cell = row.cells[column];\n if (!cell) continue;\n claimed.push({ start: cell.start, end: cell.end });\n for (const target of splitTargets(cell)) {\n relations.push({\n kind: relation.kind,\n inverted: relation.inverted,\n target: target.text,\n start: target.start,\n end: target.end,\n column: (table.headers[column] as TableCell).text,\n });\n }\n }\n\n out.push({\n heading: null,\n row,\n start: row.start,\n end: row.end,\n declaredId: id,\n title: title !== null && title.length > 0 ? title : null,\n status,\n directive: null,\n relations,\n claimed,\n });\n }\n }\n\n return out;\n}\n\ninterface TableSchema {\n readonly id: number;\n readonly status: number | null;\n readonly title: number | null;\n readonly relations: readonly (readonly [number, { kind: EdgeKind; inverted: boolean }])[];\n}\n\nfunction readSchema(table: Table): TableSchema | null {\n let id: number | null = null;\n let status: number | null = null;\n let title: number | null = null;\n const relations: [number, { kind: EdgeKind; inverted: boolean }][] = [];\n\n table.headers.forEach((header, column) => {\n const name = normaliseHeader(header.text);\n if (id === null && ID_COLUMNS.test(name)) {\n id = column;\n return;\n }\n if (status === null && STATUS_COLUMNS.test(name)) {\n status = column;\n return;\n }\n const relation = RELATION_COLUMNS[name];\n if (relation) {\n relations.push([column, relation]);\n return;\n }\n if (title === null && TITLE_COLUMNS.test(name)) title = column;\n });\n\n if (id === null) return null;\n if (status === null && relations.length === 0) return null;\n return { id, status, title, relations };\n}\n\nfunction normaliseHeader(text: string): string {\n return flatten(text)\n .toLowerCase()\n .replace(/[^a-z0-9/#\\s-]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** Splits a cell into the identifiers it lists, keeping each one's offset. */\nfunction splitTargets(cell: TableCell): { text: string; start: number; end: number }[] {\n const out: { text: string; start: number; end: number }[] = [];\n // Every target is reported at the cell's span. A cell is short, and an offset\n // recovered back through a flattening is an offset that can drift.\n const push = (raw: string): void => {\n const target = raw.trim();\n if (target.length === 0 || EMPTY_CELL.test(target)) return;\n out.push({ text: target, start: cell.start, end: cell.end });\n };\n const prose = (segment: string): void => {\n for (const piece of flatten(segment).split(/\\s*(?:[,;]|\\band\\b)\\s*/i)) push(piece);\n };\n\n // Links are taken out whole rather than flattened with everything else: a\n // path would otherwise be cut in half by its own slash, and its underscores\n // stripped as emphasis.\n // A fresh matcher per cell. `prose` calls `flatten`, which drives the shared\n // pattern; one /g regex advanced from inside its own scan resets its cursor\n // and never terminates.\n const links = new RegExp(MARKDOWN_LINK.source, 'g');\n let last = 0;\n for (let match = links.exec(cell.text); match !== null; match = links.exec(cell.text)) {\n prose(cell.text.slice(last, match.index));\n const destination = (match[2] ?? '').trim();\n // A link states where its target lives; its label states only what the\n // target is called (ADR-0008). In a column the author has already typed,\n // the destination is the claim - `[see](adr/0050.md)` is a dependency on\n // that file, not on the word \"see\". An external or in-page destination\n // names no document here, so the label is what is left to try.\n const local = destination.length > 0 && !destination.startsWith('#') && !isExternal(destination);\n push(local ? destination : (match[1] ?? ''));\n last = match.index + match[0].length;\n }\n prose(cell.text.slice(last));\n return out;\n}\n\n/** Reduces a cell to its text: link labels rather than link syntax. */\nfunction flatten(text: string): string {\n return text\n .replace(MARKDOWN_LINK, '$1')\n .replace(/\\[\\[(?:[^\\]|]*\\|)?([^\\]]*)\\]\\]/g, '$1')\n .replace(/[`*_~]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** Where a heading's section ends: the next heading at the same level or above. */\nfunction regionEnd(scanned: ScannedDocument, index: number): number {\n const heading = scanned.headings[index] as Heading;\n for (let i = index + 1; i < scanned.headings.length; i += 1) {\n const next = scanned.headings[i] as Heading;\n if (next.level <= heading.level) return next.start;\n }\n return scanned.text.length;\n}\n\n/**\n * Reads a status declared inside a region.\n *\n * Two forms, both common: a labelled line (`**Status:** Accepted`) and a\n * sub-heading (`### Status` with the value beneath it). Only the region is\n * searched, so one decision in a register cannot inherit its neighbour's.\n */\nexport function readRegionStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n const labelled = readLabelledStatus(scanned, start, end);\n if (labelled) return labelled;\n return readHeadedStatus(scanned, start, end);\n}\n\nfunction readLabelledStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n for (const line of scanned.lines) {\n if (line.contentStart < start) continue;\n if (line.contentStart >= end) break;\n if (line.code || line.blank) continue;\n const match = INLINE_STATUS.exec(line.content);\n if (!match) continue;\n // `**Status:** Accepted` closes its emphasis *after* the colon, so the\n // closing marker lands in the captured value. Strip it rather than let it\n // reach the report as part of the status a reader is shown.\n const value = (match[1] as string).replace(/^[*_\\s]+/, '').replace(/[*_\\s]+$/, '');\n if (value.length === 0) continue;\n const offset = line.contentStart + line.content.lastIndexOf(value);\n return { text: value, start: offset, end: offset + value.length };\n }\n return null;\n}\n\nfunction readHeadedStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n const heading = scanned.headings.find((h) => h.start >= start && h.start < end && isStatusHeading(h.text));\n if (!heading) return null;\n\n for (const line of scanned.lines) {\n if (line.line <= heading.line) continue;\n if (line.contentStart >= end) break;\n if (line.blank) continue;\n if (line.code) return null;\n const trimmed = line.content.trim();\n if (trimmed.startsWith('#')) return null;\n // A bullet list under Status is a status history; the first entry is current.\n const cleaned = trimmed.replace(/^[-*+]\\s+/, '');\n const offset = line.contentStart + line.content.indexOf(trimmed) + (trimmed.length - cleaned.length);\n return { text: cleaned, start: offset, end: offset + cleaned.length };\n }\n return null;\n}\n\n/** The last `@spec-node` directive inside a region. */\nfunction directiveIn(directives: readonly Directive[], start: number, end: number): Directive | null {\n let found: Directive | null = null;\n for (const directive of directives) {\n if (directive.name !== 'spec-node') continue;\n if (directive.start < start || directive.end > end) continue;\n if (found === null || directive.start > found.start) found = directive;\n }\n return found;\n}\n\n/** Case- and separator-insensitive comparison, matching reference folding. */\nfunction fold(value: string): string {\n return value.trim().toLowerCase().replace(/[\\s._-]+/g, '');\n}\n\n/** The region containing an offset, innermost first. `null` when only the file does. */\nexport function regionAt(regions: readonly SpecificationRegion[], offset: number): SpecificationRegion | null {\n let best: SpecificationRegion | null = null;\n for (const region of regions) {\n // The whole-file region owns nothing in particular; it is the fallback.\n if (region.heading === null && region.row === null) continue;\n if (offset < region.start || offset >= region.end) continue;\n if (best === null || region.start > best.start) best = region;\n }\n return best;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@descent-vtt/spec-graph",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Turn Markdown specs into a verifiable relational graph. Catches ghost handovers, stale premises and broken references in CI.",
5
5
  "keywords": [
6
6
  "adr",