@descent-vtt/spec-graph 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +483 -38
  2. package/README.md +134 -49
  3. package/dist/cli.d.ts +2 -2
  4. package/dist/cli.js +38 -19
  5. package/dist/cli.js.map +1 -1
  6. package/dist/directives.d.ts +9 -2
  7. package/dist/directives.js +15 -4
  8. package/dist/directives.js.map +1 -1
  9. package/dist/extract.d.ts +1 -1
  10. package/dist/extract.js +50 -16
  11. package/dist/extract.js.map +1 -1
  12. package/dist/glob.d.ts +44 -44
  13. package/dist/glob.js +152 -116
  14. package/dist/glob.js.map +1 -1
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.js +4 -2
  17. package/dist/index.js.map +1 -1
  18. package/dist/lifecycle.js +10 -2
  19. package/dist/lifecycle.js.map +1 -1
  20. package/dist/markdown.d.ts +53 -161
  21. package/dist/markdown.js +42 -864
  22. package/dist/markdown.js.map +1 -1
  23. package/dist/report.d.ts +23 -0
  24. package/dist/report.js +80 -0
  25. package/dist/report.js.map +1 -1
  26. package/dist/rules.js +48 -14
  27. package/dist/rules.js.map +1 -1
  28. package/dist/sections.d.ts +1 -1
  29. package/dist/sections.js +7 -3
  30. package/dist/sections.js.map +1 -1
  31. package/dist/select.d.ts +2 -2
  32. package/dist/select.js +3 -3
  33. package/dist/select.js.map +1 -1
  34. package/dist/source.d.ts +6 -19
  35. package/dist/source.js +4 -70
  36. package/dist/source.js.map +1 -1
  37. package/dist/types.d.ts +23 -3
  38. package/dist/types.js +1 -1
  39. package/dist/types.js.map +1 -1
  40. package/dist/vendor/spec-core/markdown/frontmatter.d.ts +103 -0
  41. package/dist/vendor/spec-core/markdown/frontmatter.js +459 -0
  42. package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -0
  43. package/dist/vendor/spec-core/markdown/headings.d.ts +45 -0
  44. package/dist/vendor/spec-core/markdown/headings.js +199 -0
  45. package/dist/vendor/spec-core/markdown/headings.js.map +1 -0
  46. package/dist/vendor/spec-core/markdown/index.d.ts +5 -0
  47. package/dist/vendor/spec-core/markdown/index.js +4 -0
  48. package/dist/vendor/spec-core/markdown/index.js.map +1 -0
  49. package/dist/vendor/spec-core/markdown/layout.d.ts +39 -0
  50. package/dist/vendor/spec-core/markdown/layout.js +28 -0
  51. package/dist/vendor/spec-core/markdown/layout.js.map +1 -0
  52. package/dist/vendor/spec-core/markdown/links.d.ts +25 -0
  53. package/dist/vendor/spec-core/markdown/links.js +394 -0
  54. package/dist/vendor/spec-core/markdown/links.js.map +1 -0
  55. package/dist/vendor/spec-core/markdown/lists.d.ts +22 -0
  56. package/dist/vendor/spec-core/markdown/lists.js +127 -0
  57. package/dist/vendor/spec-core/markdown/lists.js.map +1 -0
  58. package/dist/vendor/spec-core/markdown/scan.d.ts +36 -0
  59. package/dist/vendor/spec-core/markdown/scan.js +463 -0
  60. package/dist/vendor/spec-core/markdown/scan.js.map +1 -0
  61. package/dist/vendor/spec-core/markdown/syntax.d.ts +75 -0
  62. package/dist/vendor/spec-core/markdown/syntax.js +181 -0
  63. package/dist/vendor/spec-core/markdown/syntax.js.map +1 -0
  64. package/dist/vendor/spec-core/markdown/tables.d.ts +18 -0
  65. package/dist/vendor/spec-core/markdown/tables.js +86 -0
  66. package/dist/vendor/spec-core/markdown/tables.js.map +1 -0
  67. package/dist/vendor/spec-core/markdown/types.d.ts +290 -0
  68. package/dist/vendor/spec-core/markdown/types.js +9 -0
  69. package/dist/vendor/spec-core/markdown/types.js.map +1 -0
  70. package/dist/vendor/spec-core/path/index.d.ts +1 -0
  71. package/dist/vendor/spec-core/path/index.js +2 -0
  72. package/dist/vendor/spec-core/path/index.js.map +1 -0
  73. package/dist/vendor/spec-core/path/posix.d.ts +83 -0
  74. package/dist/vendor/spec-core/path/posix.js +150 -0
  75. package/dist/vendor/spec-core/path/posix.js.map +1 -0
  76. package/dist/vendor/spec-core/pattern/automaton.d.ts +129 -0
  77. package/dist/vendor/spec-core/pattern/automaton.js +340 -0
  78. package/dist/vendor/spec-core/pattern/automaton.js.map +1 -0
  79. package/dist/vendor/spec-core/pattern/charset.d.ts +58 -0
  80. package/dist/vendor/spec-core/pattern/charset.js +116 -0
  81. package/dist/vendor/spec-core/pattern/charset.js.map +1 -0
  82. package/dist/vendor/spec-core/pattern/glob.d.ts +121 -0
  83. package/dist/vendor/spec-core/pattern/glob.js +481 -0
  84. package/dist/vendor/spec-core/pattern/glob.js.map +1 -0
  85. package/dist/vendor/spec-core/pattern/index.d.ts +6 -0
  86. package/dist/vendor/spec-core/pattern/index.js +4 -0
  87. package/dist/vendor/spec-core/pattern/index.js.map +1 -0
  88. package/dist/{regex.d.ts → vendor/spec-core/pattern/regex.d.ts} +20 -29
  89. package/dist/{regex.js → vendor/spec-core/pattern/regex.js} +40 -49
  90. package/dist/vendor/spec-core/pattern/regex.js.map +1 -0
  91. package/dist/vendor/spec-core/text/index.d.ts +2 -0
  92. package/dist/vendor/spec-core/text/index.js +2 -0
  93. package/dist/vendor/spec-core/text/index.js.map +1 -0
  94. package/dist/vendor/spec-core/text/lines.d.ts +72 -0
  95. package/dist/vendor/spec-core/text/lines.js +156 -0
  96. package/dist/vendor/spec-core/text/lines.js.map +1 -0
  97. package/dist/yaml.d.ts +44 -12
  98. package/dist/yaml.js +115 -206
  99. package/dist/yaml.js.map +1 -1
  100. package/package.json +4 -3
  101. package/src/vendor/spec-core/LICENSE +21 -0
  102. package/src/vendor/spec-core/README.md +8 -0
  103. package/dist/regex.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,451 @@
3
3
  Notable changes, newest first. Versions follow [semver](https://semver.org):
4
4
  a patch fixes behaviour without asking anything of a repository that upgrades.
5
5
 
6
+ ## 0.9.1
7
+
8
+ Nothing to do on upgrading. The same documents give the same findings, in
9
+ every format, and the same graph exports as they did under 0.9.0; the one
10
+ difference a program that imports spec-graph can see is a badge wrapped in a
11
+ link, which `ScannedDocument.links` now lists. The scanner is spec-core
12
+ 8840d36, the README's and this changelog's links to the ADRs work on
13
+ npmjs.com and in `node_modules`, and `npm publish` refuses to run from a
14
+ checkout.
15
+
16
+ ### Changed
17
+
18
+ - **spec-core 8840d36.** An image inside a link's text, a badge wrapped in a
19
+ link such as `[![build](badge.svg)](actions)`, is listed in
20
+ `ScannedDocument.links` after the link around it, where CommonMark renders
21
+ it; the scan used to skip it. spec-graph reads no image as a reference but a
22
+ wiki embed, so no finding and no edge moves: run over the same documents,
23
+ this repository's, its fixtures' and the rest of the family's, every `check`
24
+ format and every graph export is byte for byte what 0.9.0 wrote. A program
25
+ that reads `links` for images sees the badges it missed. `links`,
26
+ `listItems` and `masks.directives` are made the first time they are read,
27
+ and kept, rather than for every document; they hold what they held.
28
+ - `npm publish` in a checkout refuses to run outside GitHub Actions, so a
29
+ version cannot reach npm from a workstation by mistake, without provenance;
30
+ spec-harness 0.1.0 did. The release never runs it: it stages a tarball it
31
+ packed.
32
+
33
+ ### Fixed
34
+
35
+ - The README's and this changelog's links to the ADRs and to `CLAUDE.md` were
36
+ relative, and the package ships neither, so on npmjs.com and inside
37
+ `node_modules` they led nowhere. They are GitHub URLs now. A test holds
38
+ every link in a shipped document to a file the package ships or to an
39
+ absolute URL, and every URL into this repository to a file it holds.
40
+
41
+ ## 0.9.0
42
+
43
+ Versions are staged by CI from a `v*` tag, through npm's trusted
44
+ publishing, and released by a maintainer with a second factor, no longer
45
+ published from a workstation
46
+ ([ADR-0021](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0021-releases-are-published-by-ci.md)). There is no
47
+ publish token anywhere, and every version from here on carries a provenance
48
+ attestation naming the repository, the commit and the run that built the
49
+ tarball. `npm audit signatures` checks it without taking anyone's word for
50
+ anything. 0.2.0 to 0.8.0 have none and never will.
51
+
52
+ **What to expect on upgrading.** More findings rather than fewer, and nearly
53
+ all of them from one change: a code span ends with its paragraph. Over a large
54
+ repository of hand-written decisions and logs that 0.8.0 checked, almost every
55
+ difference was a `broken-reference`. Bare identifiers that a lone backtick
56
+ earlier in a paragraph or a table had hidden are read now, and many name
57
+ nothing; a few that 0.8.0 read out of real code spans, its backticks out of
58
+ step, are code again and go. Text read for the first time can hold a relation,
59
+ and bring a `stale-premise` or a `ghost-handover` with it, and a relation read
60
+ out of code can take an `unreciprocated-supersession` away with it. A
61
+ paragraph indented four columns past its list item's text after a blank line
62
+ is code now, as CommonMark reads it, and as GitHub renders it: eleven of this
63
+ repository's ADRs had answers written that way, and they are re-indented as
64
+ paragraphs of their items. The glob, `archived` and front-matter
65
+ changes are narrower, and each is described below; a pattern the dialect
66
+ refuses stops the run with exit `2` and says how to write it. Under
67
+ `--ratchet`, expect to record the baseline once; "What a baseline sees", under
68
+ Changed, lists the entries that go stale.
69
+
70
+ ### Added
71
+
72
+ **`--format gitlab`**, the Code Quality report a GitLab merge request reads,
73
+ so the family is not GitHub's alone. `check` only, like SARIF. One issue per
74
+ finding: the rule as `check_name`, message and hint as `description`, the file
75
+ and line as `location`, and a `fingerprint` that is a SHA-256 of the rule,
76
+ the specification and what within it - the identity a baseline counts by. No
77
+ line, so moving a paragraph does not make a finding new, and no message, whose
78
+ counts move when one of three boxes is ticked; a second finding identical in
79
+ all three is told apart by its order.
80
+ `error` is `critical`, or `major` when only `--strict` made it an error;
81
+ `warn` is `minor` and `info` is `info`. The README has the job to add. See
82
+ [ADR-0015](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0015-feedback-goes-where-the-tools-already-look.md).
83
+
84
+ ### Breaking (library API)
85
+
86
+ The command line changes only as the sections below describe. A program that
87
+ imports spec-graph sees these as well:
88
+
89
+ - **`ScannedDocument.masked` is gone.** `ScannedDocument` is spec-core's
90
+ `MarkdownScan`, and the masked copy is `masks.structure`, beside
91
+ `masks.prose` and `masks.directives`.
92
+ - **`ScannedDocument.lines` holds the front matter's lines**, flagged
93
+ `frontMatter`, where it began at the body. Code that numbers or reads body
94
+ lines from it skips those.
95
+ - **`ScannedDocument.links` holds images**, flagged `Link.image`. Code that
96
+ reads it for citations filters them out, as the analysis does but for a wiki
97
+ embed.
98
+ - **Required fields are new**, so an object built by hand needs them:
99
+ `ScannedLine` has `frontMatter`, `html` and `comment`; `FrontMatter` has
100
+ `kind` and `closeLine`; `Link` has `image` and `targetEnd`; `Heading` has
101
+ `form`, `anchor`, `textStart`, `textEnd` and `endLine`; `ListItem` has
102
+ `endLine` and `quoteDepth`; `HtmlComment` has `closed`; `DocumentNode` has
103
+ `history`.
104
+ - **An error from `compilePattern` is named `RegexError`.** `PatternError` is
105
+ the same class, so `instanceof` holds; a check of `error.name` against
106
+ `'PatternError'` does not.
107
+ - **`createReferenceFilter` throws on a blank pattern**, which it used to drop,
108
+ and `compileGlob`, `createGlobMatcher` and `walkFiles` throw on every
109
+ pattern the dialect refuses.
110
+
111
+ `ParseProblem` has `unread`, and it is optional.
112
+
113
+ ### Changed
114
+
115
+ **The matcher behind `~=` is spec-core's.** It moved, unchanged, to spec-core -
116
+ the library the spec-* tools share - and is copied back into
117
+ `src/vendor/spec-core/`, byte for byte and checked against the SHA-256 it was
118
+ copied with, so spec-graph still installs nothing. The package carries the
119
+ copies compiled, and spec-core's MIT notice with them, as
120
+ `src/vendor/spec-core/LICENSE`. A selector reads exactly as
121
+ before. `compilePattern` and `PatternError` are still exported and are the same
122
+ function and class; a caught error's `name` is now `RegexError`. The tests that
123
+ hold the matcher to `RegExp` moved with it, and the copy is left out of this
124
+ repository's mutation sweep and coverage, which spec-core's measure instead.
125
+ See [ADR-0017](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0017-a-predicate-must-finish.md).
126
+
127
+ **Globs are the dialect every spec-* tool reads.** The patterns to check,
128
+ `--ignore`, `--history` and `historyPatterns` are compiled by spec-core's glob
129
+ engine, in the `path` dialect spec-brief and spec-guard read too, so a scope
130
+ means the same thing to each of them. Where that reading differs from
131
+ spec-graph's old one, a repository will see it:
132
+
133
+ - **Case is respected on every host.** `Docs/**` matched `docs/a.md` on Windows
134
+ and nowhere else, so one repository gave one answer on a Windows checkout and
135
+ another in CI. And a pattern with no glob syntax was compared lower-cased on
136
+ every host, Linux included: `README.md` also took `readme.md`. Neither folds
137
+ now, and `compileGlob`'s `ignoreCase` is off unless asked for on any host.
138
+ - **`**` inside a name is refused.** `docs/**.md`, `**.ts` and `a**b` crossed
139
+ directories wherever the `**` was written. `**` means any number of
140
+ directories only as a whole segment now, and the error says so: write
141
+ `docs/**/*.md` for any depth, as it always read, or `*.md` for one level.
142
+ - **An extended glob is refused.** `+(a|b)` was the literal text `+(a|b)`, a
143
+ scope that matched nothing; the error says to write `{a,b}`, and `[(]` for a
144
+ parenthesis. A group is an extended glob only when it holds a `|`, so
145
+ `C++(notes).md` and `books/*(2017).md` are names with parentheses in them,
146
+ as they were.
147
+ - **An unclosed `[` is an error**, like an unclosed `{` always was. It was a
148
+ literal `[`, which made the pattern a scope that matched nothing. The run
149
+ stops with exit `2` and names the pattern, from the command line or from the
150
+ configuration file.
151
+ - **A trailing `/` means the directory's contents.** It was normalised away, so
152
+ `drafts*/` named a file called `drafts`, and `docs/` took `docs` itself.
153
+ - **Braces expand to literals.** `{docs,specs}` was a glob matching two names
154
+ exactly; it is two literals now, each a directory and what it holds.
155
+ - **A class never matches `/`**, and **`[^a]` negates**, as `[!a]` does. `a[!b]c`
156
+ took `a/c`, and `[^a]` was the class of `^` and `a`.
157
+ - **A pattern must name a path under the root.** `.` and `./` are refused
158
+ rather than read as nothing, and so is a `..` anywhere in a pattern:
159
+ `docs/../specs` was resolved to `specs`, and `../other/**` walked outside
160
+ the root. A `.` segment inside a pattern is dropped, as it was. A `..` typed
161
+ below the root is resolved against where it was typed, so
162
+ `spec-graph "../*.md"` in `docs/deep` still means `docs/*.md`.
163
+ - **An empty pattern is refused**, in every list: the patterns to check,
164
+ `--ignore`, `--history`, `--ignore-ref` and their keys in the configuration
165
+ file. Each was read as nothing, or dropped - an unset variable in
166
+ `--history "$LOGS"` checked the logs as specifications, and nothing said
167
+ why. A pattern of spaces is empty to `--ignore-ref`, which trims what it
168
+ reads, and a name to the others.
169
+ - **The walk finds a directory only as it is spelled on disk.** It starts at a
170
+ pattern's literal prefix, and on a filesystem that ignores case `Docs/` found
171
+ `docs/` and reported its files as `Docs/...`. A pattern rooted at `/` is not
172
+ walked under the root either, where it reported paths like `/docs/a.md`.
173
+
174
+ `--ignore-ref` runs on the same engine and keeps its own reading, since a
175
+ reference target is not a path: a bare pattern matches one target exactly, case
176
+ is ignored on every host, `.` and `..` are the text of the link, and a `\`
177
+ escapes the next character rather than being matched as itself. The glob
178
+ changes above apply to it otherwise, and three more come with the syntax: a `\`
179
+ before a letter or digit, or at the end, is refused, as is a leading `!` - both
180
+ were literals, and `--ignore-ref` has no list to take a pattern back out of -
181
+ and `a//b` reads as `a/b`.
182
+
183
+ `globToRegExp` is deprecated, and stays as the old reading that the test of
184
+ every difference above runs against; it no longer folds case on Windows. See
185
+ [ADR-0022](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0022-globs-are-the-family-path-dialect.md).
186
+
187
+ **Documents are read by spec-core's scanner.** It moved to spec-core, built on
188
+ this repository's scanner as its model, and is copied back into
189
+ `src/vendor/spec-core/` and checked by hash, so spec-graph still installs
190
+ nothing. It follows CommonMark wherever CommonMark decides what is code or a
191
+ comment, and where that reads a document differently from before, spec-graph
192
+ follows it. Over this repository, its test corpora and spec-core's documents,
193
+ the graph and the findings are what they were. A repository sees a difference
194
+ where it wrote one of these:
195
+
196
+ - **A code span ends with its paragraph.** A lone backtick hid everything up to
197
+ the next run of its length, however many paragraphs on, citations included.
198
+ - **A heading reads as a renderer shows it.** A comment on its line is not part
199
+ of its text, and `# C#` is about `C#`: a closing run of `#` needs a space
200
+ before it. That text is a document's title, a section's name and the slug in
201
+ the id of every item under it, so an item under `## Notes <!-- x -->` moves
202
+ from `ADR-0001#notes----x---.1` to `ADR-0001#notes.1`, and
203
+ `## Open Questions <!-- short -->` holds obligations at last. A baseline does
204
+ not notice the move: an entry names the specification and what within it,
205
+ never an item.
206
+ - **A link to GitHub's anchor for a repeated heading resolves.** GitHub renders
207
+ the second `## Notes` as `#notes-1`, and a link to it was a
208
+ `broken-reference`. The slug still names the first heading, and `#notes-2`
209
+ beside two headings is still broken.
210
+ - **Nothing inside `<pre>`, `<script>`, `<style>` or `<textarea>` is
211
+ structure.** Their links were masked already; now no heading, list item, table
212
+ or `Status:` line is read from one either, so `- [ ] example` in a `<pre>`
213
+ block is not an open obligation, and a status section that opens with one
214
+ declares no status. A `@spec-item` above one does not reach an item below it,
215
+ as it never reached across a fence.
216
+ - **Nothing inside a comment opens a block.** A fence inside a comment opened
217
+ one, and with no closer everything after it was code; a list marker inside one
218
+ decided whether an indented block after it was code. Neither does now. A
219
+ comment that opens a line and never closes runs to the end of the document, as
220
+ it renders, and a parse problem says where it opened; no directive is read from
221
+ it, so a stray `<!-- @spec-ignore` does not drop the file.
222
+ - **Headings, rules and list items as CommonMark reads them.** A setext underline
223
+ is not used twice - `Title`, `===`, `---` is one heading and a rule, not a
224
+ second heading called `===` - and does not underline a line in a block quote.
225
+ `* * *` is a rule, not a list item. A list item ends at a fence or a block
226
+ quote no deeper than its marker, so a relation written after an unindented
227
+ fence is the document's and not the item's. And a heading closes every list,
228
+ so an item indented under a list a heading closed is at depth 0, and an
229
+ obligation when its section holds them.
230
+ - **Links as CommonMark reads them.** `[a](b c)` is text, not a link to `b`.
231
+ `[foo](not a link)` is the shortcut `[foo]` when `foo` is defined, not a link
232
+ to `not`. A footnote `[^1]: text` and a line `[Note]: prose` are not
233
+ definitions, so `[^1]` and `[Note]` are no longer links to the first word
234
+ after them, which were broken references. A link inside brackets that are not
235
+ one, `[see [ADR-7](0007.md) here]`, is read. Labels match with case and
236
+ whitespace folded, so `[Two Words][]` finds `[TWO WORDS]:`. A finding on
237
+ `[a](<b c.md>)` points inside the angle brackets, a column later than it did.
238
+ - **A table needs a delimiter row with as many cells as its header.** One
239
+ without was read as a table, and as a register when its columns said so.
240
+ - **A fence ends with its block quote.** One opened in a quote and never closed
241
+ made the rest of the document code.
242
+ - **Indented code is four columns past its container**: the margin, or the text
243
+ of the list item the line is in. Inside a list four columns were never code,
244
+ so a link or a checkbox in an example indented under an item was read as one.
245
+ Under `- item`, six columns after a blank line are code now, and four are
246
+ still the item's text. A fence line that deep opens no fence - after a blank
247
+ line it is indented code, and under a paragraph it is text - where it used to
248
+ open one at any depth and, unclosed, run to the end of the document.
249
+ - **Front matter opens on exactly `---` or `+++`, and closes on exactly
250
+ `---`, `...` or `+++`.** `----` is a rule. And `----` or `....` under an
251
+ opening `---` closed the block, and now leaves it open: a block that never
252
+ closes is read as a rule and Markdown, none of its keys are read, and a parse
253
+ problem says where it opened.
254
+
255
+ **What a baseline sees.** A finding the scan no longer produces leaves its
256
+ entry no longer occurring, which fails a run under `--ratchet`. These go: the
257
+ first word of a footnote `[^1]: See ...` or a line `[Note]: prose`, which was
258
+ a broken reference; the `b` of `[a](b c)` and the `not` of
259
+ `[foo](not a link)`; a link to GitHub's `#notes-1`, which was an unknown
260
+ anchor; an `orphaned-obligation` made of boxes held in `<pre>` or in a
261
+ comment; a link inside a code span that a lone backtick earlier in the document
262
+ had knocked out of step, so that it read as text; and a link in an example
263
+ indented under a list item. One `--record-baseline` settles them. What the scan
264
+ sees now and did not - a citation a lone backtick hid, an obligation under a
265
+ heading with a comment on its line, what followed a fence indented under a
266
+ paragraph, the shortcut `[foo]` - is a new finding, as any other is.
267
+
268
+ For the library API, the scanner's types are spec-core's, exported under the
269
+ names they had, and what that breaks is listed under
270
+ [Breaking (library API)](#breaking-library-api). Beside it, `blocks`,
271
+ `codeSpans` and `bom` are new on `ScannedDocument`, and
272
+ `isMasked(offset, mask?)` asks any of its three masks. The analysis leaves
273
+ images out but for wiki embeds. `parseDirectives` skips a comment whose
274
+ `closed` is `false`. `createLineIndex` is spec-core's, the same table. See
275
+ [ADR-0023](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0023-the-scanner-is-the-familys.md).
276
+
277
+ **Front matter is read by spec-core's reader, and what it does not read says
278
+ why.** Keys are compared in lower case and one level of nesting is read as
279
+ `parent.child`, as before. Where the reader and YAML part from the old one:
280
+
281
+ - **A value YAML does not say is not read.** A plain value holding `: ` -
282
+ `title: ADR-7: Sharding`, which YAML reads as an error - a value continued onto
283
+ the next line, which was read as its first line, a block scalar, an anchor and
284
+ a tag are left out rather than guessed at. Quote the value and it is read. An
285
+ inline list on the line under its key, `translators:` over an indented `[]`,
286
+ is left out too, with the advice to write it after the colon, since quoting it
287
+ would make it a string. Each is a parse problem at the value, under
288
+ `--verbose` and in `--format json`, naming the reason; so are a line that is
289
+ not `key: value`, a key written twice, of which the last still wins, and TOML
290
+ front matter, which was read as nothing in silence. A status left out this way
291
+ is looked for in a `## Status` section, as a missing one always was.
292
+ - **What the graph lost is counted without `--verbose`.** A status, an id, an
293
+ alias or a relation left out - or a block never closed, or a line that belongs
294
+ to no key - can turn a failing check into a passing one:
295
+ `status: Superseded by ADR-3: see notes` reads as unknown, and the
296
+ supersession finding 0.8.0 reported is gone. So a run without `--verbose` prints one
297
+ line above its verdict, `! 2 front-matter values not read - see --verbose`, in
298
+ the terminal and in `--format markdown`. It is not a finding and does not
299
+ change the exit code. A title or a description left out is not counted.
300
+ `ParseProblem` says which problems count, as `unread`.
301
+ - **A key indented under nothing is not read.** `status: accepted` indented on
302
+ the first line of front matter is valid YAML, and 0.8.0 read it. It is a line
303
+ that belongs to no key now, so the document reads as `unknown`, and the count
304
+ of values not read includes it. Write the key at the margin.
305
+ - **A list written at its key's own indentation is a list.** `deps:` over
306
+ `- ADR-1` was an empty value.
307
+ - **Quoted values are YAML's.** `'it''s'` is `it's`, and `"a\tb"` holds a tab.
308
+
309
+ `parseFrontMatter` keeps its signature and reads with spec-core's reader, so it
310
+ leaves out what the reader does not read. See
311
+ [ADR-0023](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0023-the-scanner-is-the-familys.md).
312
+
313
+ **`archived` is a record, not a retired decision.** spec-brief closes a round
314
+ of work by writing `status: archived` and moving the brief to an archive
315
+ directory, and every spec-* tool reads that word as a finished round which it
316
+ is normal to depend on. spec-graph read it as retired: a live brief that
317
+ depended on an archived one was reported as a `stale-premise`, and an archived
318
+ brief with a task left unticked as an `orphaned-obligation`. It is the `record`
319
+ phase now, as a file declared by `historyPatterns` is - its links are still
320
+ checked, its unticked boxes are nobody's work and leave the open count, and
321
+ live work handed to it is still a `ghost-handover`, which says it is archived.
322
+ So is the status word `archive`.
323
+
324
+ It is a closed round and not a log, so that is all that changes. The claims an
325
+ archived document makes are still checked as they were when it read as
326
+ retired: a supersession it declares (`live-supersession` on what it
327
+ supersedes), a cycle it closes (`circular-delegation`) and a link to itself
328
+ (`self-reference`). What it hands on and what it rests on are still not held
329
+ against it. An archived document that something supersedes and that never
330
+ says so is an `unreciprocated-supersession`, whose hint is now to make its
331
+ status `archived, superseded by X` - which retires it, so what depends on it
332
+ is a `stale-premise` again. A document declared by `historyPatterns` or
333
+ `<!-- @spec-history -->` is exempt from all of it, as before; `DocumentNode`
334
+ says which kind of record a document is, as `history`.
335
+
336
+ A document still retires by saying `superseded`, `deprecated` or `retired`,
337
+ and `archived, superseded by B-7` is retired, since a retirement word wins
338
+ wherever it is written. A document under an `archive/` directory that declares
339
+ no status is retired as before. A repository that wrote `archived` for a
340
+ retired decision loses the `stale-premise` findings on documents that depend
341
+ on it, and the `orphaned-obligation` findings on its open items, and a baseline
342
+ that accepted one reports that entry as no longer occurring, which fails a run
343
+ under `--ratchet`. See
344
+ [ADR-0011](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0011-a-record-is-not-a-specification.md).
345
+
346
+ ### Fixed
347
+
348
+ **A code span that mentions a comment is code.** ``Use `<!--` to open one``
349
+ opened an HTML comment at the `<!--`, and it ran to the next `-->` anywhere in
350
+ the document: every heading, citation and link in between went into it, and a
351
+ link to a file that does not exist went unreported. Comments were found first
352
+ and code spans after, so the span never had a say. Both are now found in one
353
+ pass, left to right, and whichever opens first wins, which is how CommonMark
354
+ reads them: a `<!--` inside a span is text, and so is a backtick inside a
355
+ comment. A span still closes only on a run of its own length, and an escaped
356
+ backtick still opens none.
357
+
358
+ The same defect read a directive quoted in inline code as a directive. This
359
+ repository's README and ADR-0011 both write `` `<!-- @spec-history -->` `` in a
360
+ sentence, and both were being checked as historical records. They are a
361
+ document and an accepted decision again, and `selfcheck` counts one more open
362
+ obligation than it did, because ADR-0011's open question is no longer exempt. A document quoting `<!-- @spec-ignore -->` that way was skipped whole,
363
+ and is checked now.
364
+
365
+ **Unclosed backtick runs cost the document's length once, not once each.** Each
366
+ searched the rest of the document for its closer, so a line of runs of different
367
+ lengths made the scan grow as the length to the power 1.5: 2 MB took fifteen
368
+ seconds. The first search that fails now keeps what it saw, and the same 2 MB
369
+ takes about sixty milliseconds.
370
+
371
+ **What a comment holds is not structure.** Headings, list items and tables are
372
+ read from lines, and none of them asked whether the line was inside a comment.
373
+ `## Hidden` in `<!-- -->` was a heading and started a section, a checklist
374
+ commented out was a list of open obligations nobody could see, and
375
+ `<!-- note -->` over `---` was a heading called "<!-- note -->". A line whose
376
+ first character is inside a comment is now none of these, and cannot end a list
377
+ item either; `ScannedLine` says so as `comment`. Directives are read out of
378
+ comments exactly as before.
379
+
380
+ Nor is a status read out of one. Under `## Status`, a template's
381
+ `<!-- proposed | accepted -->` was taken as the status - which no vocabulary
382
+ knows, so the phase was `unknown` with `Accepted` on the next line - and a
383
+ commented-out `Status: proposed` gave a register's section the second signal it
384
+ needs to become a specification.
385
+
386
+ An obligation that was only ever inside a comment is gone on upgrade, with any
387
+ finding about it, so a baseline that accepted one reports that entry as no
388
+ longer occurring under `--ratchet`. An item under a heading that was only inside
389
+ a comment is numbered under the heading above it instead.
390
+
391
+ ### Verified
392
+
393
+ `npm run lint`, 987 tests, `npm run build` and `npm run selfcheck`. Fifteen of
394
+ the 25 new tests fail against the unfixed source; the other ten hold the one pass
395
+ to what the two it replaced already got right - fences, escapes, runs that never
396
+ close, a `<` that is not `<!--`. Measured on their own with
397
+ `--coverageAnalysis all`, before and after: `markdown.ts` went from 69.30% to
398
+ 72.97%, the status readers in `sections.ts` from 52.63% to 59.49%, and
399
+ `statusSectionBody` in `extract.ts` from 60.00% to 73.08%. The mutants left alive
400
+ on the new lines change how long an answer takes, or compare offsets that cannot
401
+ be equal. 600,000 generated documents read the same through the one pass as
402
+ through a naive left-to-right reading of the same rule, and this repository's
403
+ documents scan no slower than before.
404
+
405
+ Then spec-core, the record phase and GitLab: `npm run lint`, 1,010 tests,
406
+ `npm run build` and `npm run selfcheck`. The 30 tests of `tests/regex.test.ts`
407
+ went to spec-core with the matcher, where they run with only its names
408
+ changed. Every glob change listed above has a test that fails against the
409
+ 0.8.0 matcher - the host-dependent ones on Windows, where the defect was - and
410
+ the differential in `tests/glob.test.ts` requires each of them to occur and
411
+ nothing else to; five of the eight tests for `archived` fail without its table
412
+ entry, and the other three hold what must not move. Measured on their own with
413
+ `--coverageAnalysis all`, before and after: `glob.ts` from 73.74% to 77.22%,
414
+ `cli.ts` from 81.11% to 83.45%, `report.ts` from 69.03% to 70.17% and
415
+ `lifecycle.ts` from 53.10% to 53.26%. `select.ts`, whose only change is an
416
+ import, read 74.01% and then 71.08%: sixteen selector-parsing mutants that no
417
+ assertion kills in either run timed out in the first and survived in the
418
+ second. Of the mutants left alive on new lines, seven are equivalent and say
419
+ so where they are; the rest are on the ignore filter, whose reading did not
420
+ change, and on the symlink branch of the walk, which no test reaches, as
421
+ before.
422
+
423
+ Then spec-core's scanner and front-matter reader: `npm run lint`, 1,041 tests,
424
+ `npm run build` and `npm run selfcheck`. Of the 27 new tests, 23 fail against
425
+ the source before the move; the other four hold what must not move - an embed's
426
+ edge, the last of a key written twice, no directive from a comment that never
427
+ closes, and no suffix for a heading that is not repeated. The binaries before
428
+ and after read this repository, its seven fixture corpora and spec-core's
429
+ documents into identical graph exports and identical findings. Over a corpus
430
+ written to hit each difference above, every difference in their output was one
431
+ of them. The scanner's own mutants, 72% to 77% here, are spec-core's sweep's
432
+ now, at 98% there; what the move left here is measured by CI's sweep.
433
+
434
+ Then the review of those rounds: `npm run lint`, 1,053 tests, `npm run build`
435
+ and `npm run selfcheck`, with spec-core copied again at cbe2223. Every new test
436
+ fails against the source the review read but one, which holds what an archived
437
+ document is still exempt from. They cover spec-core's licence in the package,
438
+ `**` inside a name and an extended glob, code past a list item's text, an empty
439
+ `--ignore` and `--ignore-ref`, what an archived document still answers for, the
440
+ count of front-matter values not read, front matter that never closes, and a
441
+ GitLab fingerprint across a ticked box. A baseline recorded by 0.8.0 over a
442
+ corpus holding each scanner difference, run under `--ratchet`, accepted the
443
+ finding on a moved item and went stale only on findings the scan no longer
444
+ produces. Against 0.8.0 over the review's corpora for `archived`, the difference
445
+ is the stale premise and the orphaned obligation the change is for. Over the
446
+ large repository above, this branch and the one reviewed report identical
447
+ findings. spec-core's documents and the seven fixture corpora read into the same
448
+ graph export as before the copy; this repository's lost the seven relations
449
+ above.
450
+
6
451
  ## 0.8.0
7
452
 
8
453
  A register row took its identifier from the first thing its *title* said, not
@@ -107,7 +552,7 @@ the file - `--verbose` now says when it read nothing. Unreadable, as opposed to
107
552
  absent, is an error wherever the path was written. A baseline that cannot be
108
553
  *parsed* is still reported and ignored: it only ever suppresses, so a row that
109
554
  is dropped is a finding reported. See
110
- [ADR-0012](docs/adr/0012-a-baseline-is-a-ratchet.md).
555
+ [ADR-0012](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0012-a-baseline-is-a-ratchet.md).
111
556
 
112
557
  ### Fixed
113
558
 
@@ -117,7 +562,7 @@ selector under `query`, went to stdout above the report. With `--format json`
117
562
  that made the output unparseable; with `--format sarif` it made a file the
118
563
  code-scanning uploader rejects over a schema rather than over the command that
119
564
  was run. Both now go to stderr, with everything else a run says about itself.
120
- [ADR-0015](docs/adr/0015-feedback-goes-where-the-tools-already-look.md) already
565
+ [ADR-0015](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0015-feedback-goes-where-the-tools-already-look.md) already
121
566
  had the rule - rows "cannot go on stdout beside a JSON document without
122
567
  breaking the parse" - and these two lines were the places that did not follow
123
568
  it.
@@ -179,7 +624,7 @@ matched against targets read out of documents, which nothing limits the length
179
624
  of. Every glob - the patterns to check, `--ignore`, `--ignore-ref`, `--history` -
180
625
  now runs on the same automaton as `~=`, verified against the `RegExp` it
181
626
  replaces on every glob and path in a differential corpus. See
182
- [ADR-0017](docs/adr/0017-a-predicate-must-finish.md).
627
+ [ADR-0017](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0017-a-predicate-must-finish.md).
183
628
 
184
629
  - **An invalid glob names itself.** `docs/{a` used to report
185
630
  `Invalid regular expression: /^docs\/(?:a$/i: Unterminated group`, about a
@@ -210,7 +655,7 @@ report a closed question as reopened when nobody touched it. An obligation is
210
655
  paired only by a declared id, or by its document, section and title when nothing
211
656
  else shares them; anything unpaired is reported as having appeared or
212
657
  disappeared. Human, `--format json` or `--format markdown`, and exit 0 either
213
- way. See [ADR-0020](docs/adr/0020-a-diff-names-what-it-can-tell-apart.md).
658
+ way. See [ADR-0020](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0020-a-diff-names-what-it-can-tell-apart.md).
214
659
 
215
660
  - The JSON graph export names its `generator`, so a diff can warn when two
216
661
  exports came from different versions, and each item says whether its id was
@@ -244,8 +689,8 @@ kills no assertion made: tests writing to fixed paths deleted each other's files
244
689
  under Stryker's parallel workers. Race-free, the sweep read 73.42% before
245
690
  `spec-graph diff` and 75.25% with this release: `diff.ts` at 98.35%, and
246
691
  `directives.ts` from 66.88% to 78.01%. Lose every timeout and it reads 71.89%.
247
- See [ADR-0007](docs/adr/0007-mutation-testing.md) and
248
- [ADR-0019](docs/adr/0019-the-sweep-runs-in-shards.md).
692
+ See [ADR-0007](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0007-mutation-testing.md) and
693
+ [ADR-0019](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0019-the-sweep-runs-in-shards.md).
249
694
 
250
695
  The sweep after the directive fix moved `extract.ts` down 0.18 points, among the
251
696
  usual movement in files nobody touched, and this one was real. Four mutants
@@ -282,7 +727,7 @@ A region now carries its file's front matter minus two kinds of key. A
282
727
  on behalf of the register, not of each decision in it. So does a key the region
283
728
  **answers for itself** — its identifier, status, title and aliases, which are
284
729
  precisely the things a register exists to vary row by row.
285
- [ADR-0009](docs/adr/0009-a-specification-is-a-region.md) carried this as a
730
+ [ADR-0009](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0009-a-specification-is-a-region.md) carried this as a
286
731
  narrowed open question and called it a gap. It was a defect, and it was found by
287
732
  pointing the binary at a register rather than by reading the code.
288
733
 
@@ -295,7 +740,7 @@ that keeps registers and writes rules about front matter.
295
740
  ### Added
296
741
 
297
742
  **`~=` is matched by an automaton that cannot backtrack.**
298
- [ADR-0016](docs/adr/0016-a-query-needs-a-sentence.md) documented this hazard
743
+ [ADR-0016](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0016-a-query-needs-a-sentence.md) documented this hazard
299
744
  with `(a+)+$`, which nobody writes, and priced the fix at "worth it only if a
300
745
  real repository hangs". Both were wrong. `^([A-Za-z0-9_]+[ ]?)+$` is what
301
746
  somebody writes to check that a title is words separated by single spaces:
@@ -326,8 +771,8 @@ over, on one clause of the language specification read backwards in two
326
771
  different places - the word characters `\b` recognises, and whether a character
327
772
  whose upper case is ASCII may match `[A-Z]`. The second was found by the
328
773
  mutation score pointing at a corpus with a blind spot in it rather than at a
329
- test. See [ADR-0017](docs/adr/0017-a-predicate-must-finish.md) and
330
- [ADR-0007](docs/adr/0007-mutation-testing.md).
774
+ test. See [ADR-0017](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0017-a-predicate-must-finish.md) and
775
+ [ADR-0007](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0007-mutation-testing.md).
331
776
 
332
777
  **Configuration is discovered upward, and the file holding it is the root.**
333
778
 
@@ -337,7 +782,7 @@ cd packages/auth && spec-graph check # the repository's rules, the
337
782
  # repository's paths
338
783
  ```
339
784
 
340
- [ADR-0010](docs/adr/0010-configuration-belongs-to-the-repository.md) worried
785
+ [ADR-0010](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0010-configuration-belongs-to-the-repository.md) worried
341
786
  that discovery would make a run depend on where it started. Reading the working
342
787
  directory already did, and silently: a check from a package directory found no
343
788
  configuration, ran no project rules, used the default include patterns, and
@@ -348,7 +793,7 @@ to a run from the top. The walk stops at the repository — a directory holding
348
793
  `.git` — so a stray file in a home directory cannot reach it. `--root` names the
349
794
  root yourself and turns discovery off, and a path typed on the command line
350
795
  stays relative to where you typed it. See
351
- [ADR-0018](docs/adr/0018-the-configuration-file-is-the-root.md).
796
+ [ADR-0018](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0018-the-configuration-file-is-the-root.md).
352
797
 
353
798
  **`spec-graph query project:<rule>` runs a registered rule by name.** The rule
354
799
  is already compiled by the time the command runs, and the alternative was
@@ -453,7 +898,7 @@ re-litigated: a **relational diff** between two states of the graph, taken
453
898
  between two `--graph-format json` exports rather than between two git revisions,
454
899
  because revisions mean spawning a binary underneath a pipeline that is a pure
455
900
  function of text. See
456
- [ADR-0015](docs/adr/0015-feedback-goes-where-the-tools-already-look.md).
901
+ [ADR-0015](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0015-feedback-goes-where-the-tools-already-look.md).
457
902
 
458
903
  ### Verified
459
904
 
@@ -485,7 +930,7 @@ mostly the older formatters.
485
930
  Between those two runs `rules.ts` moved from 79.42% to 76.02% and
486
931
  `directives.ts` from 77.92% to 74.03%, with neither file touched: the tests added
487
932
  in between changed which tests `perTest` believes cover which mutants.
488
- [ADR-0007](docs/adr/0007-mutation-testing.md) has recorded that shape for two
933
+ [ADR-0007](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0007-mutation-testing.md) has recorded that shape for two
489
934
  releases, and it is why a per-file drop is a question and not an answer.
490
935
 
491
936
  The hosted number is the one that governs, because it is where the build fails:
@@ -495,12 +940,12 @@ points.
495
940
 
496
941
  The incremental run on the same commit read 0.40 points low - the third
497
942
  observation after 3.18 high at 0.3.0 and 1.29 low at 0.4.0, and the reason
498
- [ADR-0007](docs/adr/0007-mutation-testing.md) calls it a signal rather than a
943
+ [ADR-0007](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0007-mutation-testing.md) calls it a signal rather than a
499
944
  figure. The tag's run also logged a cache save, which this entry first read as
500
945
  the cache-key fix from 0.4.0 doing its job. *Corrected 2026-09-13:* it saved a
501
946
  copy of the stale report it had restored, because the full step never wrote one,
502
947
  and an entry saved under a tag cannot be restored on `main` in any case. See
503
- [ADR-0007](docs/adr/0007-mutation-testing.md).
948
+ [ADR-0007](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0007-mutation-testing.md).
504
949
 
505
950
  **The rebuild took 168 of its 180 minutes.** 13% more mutants than 0.4.0, and 63%
506
951
  longer on the hosted runner against 15% longer locally. At 0.4.0's old cap of
@@ -531,7 +976,7 @@ selector, a message, a hint and a severity, and runs it beside the built-ins:
531
976
  }
532
977
  ```
533
978
 
534
- [ADR-0005](docs/adr/0005-rules-are-queries.md) claimed for three releases that
979
+ [ADR-0005](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0005-rules-are-queries.md) claimed for three releases that
535
980
  the selector language was expressive enough for a team to write the equivalent
536
981
  of a built-in rule. It was only true at a prompt: `spec-graph query` could find
537
982
  the thing, and nothing could make finding it fail. Three ADRs recorded the same
@@ -550,7 +995,7 @@ nothing answers to is reported when the file is read - a rule that cannot work
550
995
  has to say so, because silence from a linter is indistinguishable from health.
551
996
  `query` also takes a list, read as a union.
552
997
 
553
- See [ADR-0016](docs/adr/0016-a-query-needs-a-sentence.md).
998
+ See [ADR-0016](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0016-a-query-needs-a-sentence.md).
554
999
 
555
1000
  **A broken reference suggests the document it was probably meant to name.**
556
1001
  `ARD-0015` gets `did you mean ADR-0015?`; `0002-cacheing.md` gets its sibling
@@ -558,7 +1003,7 @@ one letter away. `ADR-0003` in a repository of two ADRs gets nothing, and that
558
1003
  restraint is the design rather than a limitation of it: a family name is a word
559
1004
  people misremember, a number *is* the identity, and every number sits one edit
560
1005
  from its neighbours. Each gate ends the same way - exactly one candidate, or
561
- silence. See [ADR-0004](docs/adr/0004-reference-resolution.md).
1006
+ silence. See [ADR-0004](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0004-reference-resolution.md).
562
1007
 
563
1008
  **A stale baseline entry says `paid` or `gone`.** `paid` is the ratchet working.
564
1009
  `gone` means the document was not in this corpus at all, so nothing is known
@@ -587,7 +1032,7 @@ UTF-16 read as UTF-8 rather than anybody's keystroke.
587
1032
  ### Fixed
588
1033
 
589
1034
  Nothing that a corpus had hit. Two findings came out of probing rather than out
590
- of the suite, which is the practice [CLAUDE.md](CLAUDE.md) asks for:
1035
+ of the suite, which is the practice [CLAUDE.md](https://github.com/DescentVTT/spec-graph/blob/main/CLAUDE.md) asks for:
591
1036
 
592
1037
  - A near-miss gate that suggested a file which had moved **never fired**.
593
1038
  Resolution already binds a path by its basename, so the suggestion had nothing
@@ -625,7 +1070,7 @@ of the suite, which is the practice [CLAUDE.md](CLAUDE.md) asks for:
625
1070
  first time anybody checked. Across 73 keys that Jekyll, Hugo, Docusaurus, Astro,
626
1071
  MADR, KEP, the IETF datatracker and Obsidian actually write, one edit reads
627
1072
  *none* of them as a near-miss and two edits reads exactly one: `rfc`, which it
628
- would tell an author to spell `refs`. [ADR-0014](docs/adr/0014-a-relation-is-spelled-both-ways.md)
1073
+ would tell an author to spell `refs`. [ADR-0014](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0014-a-relation-is-spelled-both-ways.md)
629
1074
  said two edits would catch `dependancies`; one edit already did, along with
630
1075
  every other typo anybody produced to argue the case. Two buys nothing and costs
631
1076
  a false positive on a key that carries citations in exactly the corpora this
@@ -658,7 +1103,7 @@ fails: **75.40%**, up from 75.14% at 0.3.0. Lose every timeout on a loaded runne
658
1103
  and it reads 71.76%, which is the most margin `break: 70` has ever had.
659
1104
 
660
1105
  That run also caught the other half of a claim
661
- [ADR-0007](docs/adr/0007-mutation-testing.md) narrowed at 0.3.0. The `main` push
1106
+ [ADR-0007](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0007-mutation-testing.md) narrowed at 0.3.0. The `main` push
662
1107
  and the `v0.4.0` tag were the same commit on the same runner, and the incremental
663
1108
  read **1.29 points low** against the rebuild - having read 3.18 points *high* at
664
1109
  0.3.0. Two observations, opposite signs. The direction is not a property, and the
@@ -667,7 +1112,7 @@ leaves it nothing to reuse.
667
1112
 
668
1113
  ### Withdrawn
669
1114
 
670
- [ADR-0013](docs/adr/0013-the-scanner-hands-back-prose.md) said a lone `\r` was
1115
+ [ADR-0013](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0013-the-scanner-hands-back-prose.md) said a lone `\r` was
671
1116
  not treated as a line ending. It always was - `createLineIndex` has recognised
672
1117
  all three terminators since 0.1.0 and says so in its own doc comment. The claim
673
1118
  came from reading the scanner and not the table underneath it, and nobody had
@@ -684,7 +1129,7 @@ built-ins needs a default arm. `BaselineOutcome.stale` carries `reason`, and
684
1129
  ## 0.3.0
685
1130
 
686
1131
  Found by writing a corpus designed to break the scanner and reading the edges it
687
- produced, which is the practice [CLAUDE.md](CLAUDE.md) asks for and the reason
1132
+ produced, which is the practice [CLAUDE.md](https://github.com/DescentVTT/spec-graph/blob/main/CLAUDE.md) asks for and the reason
688
1133
  it is there. The mutation score could not have found any of it: a mutant is a
689
1134
  change to code that exists, and a construct nobody parsed has no code to mutate.
690
1135
 
@@ -727,7 +1172,7 @@ GitHub's own copy-link all produce for a file with a space in its name. The
727
1172
  escapes are read as a fallback, so a filename that genuinely contains one keeps
728
1173
  resolving by the spelling it was written with, and a stray `100%` is left alone.
729
1174
 
730
- See [ADR-0013](docs/adr/0013-the-scanner-hands-back-prose.md).
1175
+ See [ADR-0013](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0013-the-scanner-hands-back-prose.md).
731
1176
 
732
1177
  **Front matter knew half of each relation.** `depends-on` was a key and
733
1178
  `depended-on-by` was not, so a repository whose filing convention records
@@ -738,7 +1183,7 @@ now - twenty-five keys became fifty-six - and a test holds the invariant.
738
1183
 
739
1184
  Keys also fold on separators and case, so `depends-on`, `depends_on` and
740
1185
  `dependsOn` are one key rather than the two that happened to be written out.
741
- See [ADR-0014](docs/adr/0014-a-relation-is-spelled-both-ways.md).
1186
+ See [ADR-0014](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0014-a-relation-is-spelled-both-ways.md).
742
1187
 
743
1188
  **`--verbose --format json` printed human lines onto stdout beside the JSON.**
744
1189
 
@@ -749,11 +1194,11 @@ See [ADR-0014](docs/adr/0014-a-relation-is-spelled-both-ways.md).
749
1194
  ignored. Both halves of that gate matter: `deprecated: true` is one edit from
750
1195
  `deprecates` and a boolean is not a citation. It says what is true - *"the key
751
1196
  declares no relation"* - and offers the near spellings rather than asserting
752
- one. See [ADR-0014](docs/adr/0014-a-relation-is-spelled-both-ways.md).
1197
+ one. See [ADR-0014](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0014-a-relation-is-spelled-both-ways.md).
753
1198
  - **`--ratchet`**, and `"ratchet": true` in the configuration. The other side of
754
1199
  a baseline: with it, a declared finding that no longer occurs fails the build,
755
1200
  named, with `--record-baseline` as the fix. Off by default, because
756
- [ADR-0012](docs/adr/0012-a-baseline-is-a-ratchet.md) is right that failing a
1201
+ [ADR-0012](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0012-a-baseline-is-a-ratchet.md) is right that failing a
757
1202
  build because somebody fixed something is a strange way to encourage them -
758
1203
  and on for a team that has decided its debt only moves one way, because a note
759
1204
  on a green build is a line that scrolls past.
@@ -763,7 +1208,7 @@ See [ADR-0014](docs/adr/0014-a-relation-is-spelled-both-ways.md).
763
1208
  same rule/specification/citation identity a baseline is keyed on, so an
764
1209
  annotation survives a reformat. Deterministic and timestamp-free like every
765
1210
  other output here. See
766
- [ADR-0015](docs/adr/0015-feedback-goes-where-the-tools-already-look.md).
1211
+ [ADR-0015](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0015-feedback-goes-where-the-tools-already-look.md).
767
1212
 
768
1213
  ### Not added
769
1214
 
@@ -773,14 +1218,14 @@ semantics that differ on all three platforms would be the one part of this
773
1218
  program that could not honour byte-determinism, inside a codebase whose whole
774
1219
  test strategy rests on everything between the edges being a pure function of
775
1220
  text. Reasoning in
776
- [ADR-0015](docs/adr/0015-feedback-goes-where-the-tools-already-look.md).
1221
+ [ADR-0015](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0015-feedback-goes-where-the-tools-already-look.md).
777
1222
 
778
1223
  ### Measured
779
1224
 
780
1225
  684 tests, and a mutation score of **75.14% on the hosted runner** - the figure
781
1226
  that governs, up from 73.32% at v0.2.0 - against 79.69% over 8,126 mutants on a
782
1227
  developer machine. The guard stays at `break: 70`. See
783
- [ADR-0007](docs/adr/0007-mutation-testing.md), which 0.3.0 also amended: an
1228
+ [ADR-0007](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0007-mutation-testing.md), which 0.3.0 also amended: an
784
1229
  incremental run read 3.18 points *high* against a full rebuild of the same
785
1230
  commit, so the claim that incremental errs low was wrong.
786
1231
 
@@ -795,7 +1240,7 @@ spellings held one id per key. A second claimant therefore did not make the link
795
1240
  ambiguous; it overwrote the first, and the link went to whichever file was
796
1241
  indexed last.
797
1242
 
798
- [ADR-0004](docs/adr/0004-reference-resolution.md) has always said two candidates
1243
+ [ADR-0004](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0004-reference-resolution.md) has always said two candidates
799
1244
  is worse than none, and a reference matching several documents is reported as
800
1245
  ambiguous rather than bound to whichever was indexed first. That now holds for
801
1246
  paths too.
@@ -820,7 +1265,7 @@ keeps no registers is unaffected, and one that does needs no change beyond the
820
1265
  upgrade.
821
1266
 
822
1267
  Since 0.1.3 a register has yielded one specification per row or per section
823
- ([ADR-0009](docs/adr/0009-a-specification-is-a-region.md)), and every one of
1268
+ ([ADR-0009](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0009-a-specification-is-a-region.md)), and every one of
824
1269
  those carried the file's path into the resolver's path index. That index keeps
825
1270
  whichever entry was written last, so the file's own entry was overwritten by its
826
1271
  final region, and:
@@ -866,12 +1311,12 @@ requirements table headed `Status` was enough to trigger it.
866
1311
  `historyPatterns` in the configuration or by `<!-- @spec-history -->`, and
867
1312
  never guessed from a filename. Its links are still checked and work handed
868
1313
  *into* it is still a ghost handover; its own obligations and lifecycle are
869
- not. See [ADR-0011](docs/adr/0011-a-record-is-not-a-specification.md).
1314
+ not. See [ADR-0011](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0011-a-record-is-not-a-specification.md).
870
1315
  - **Baselines.** `--record-baseline` writes today's findings as accepted debt;
871
1316
  `--baseline` reports only what is new since. Keyed on the specification and
872
1317
  the citation rather than on a line number, so it survives edits, reordering
873
1318
  and renames. Paid debt is reported, not failed. See
874
- [ADR-0012](docs/adr/0012-a-baseline-is-a-ratchet.md).
1319
+ [ADR-0012](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0012-a-baseline-is-a-ratchet.md).
875
1320
 
876
1321
  ### Fixed
877
1322
 
@@ -886,10 +1331,10 @@ requirements table headed `Status` was enough to trigger it.
886
1331
  - **A specification is a region of a file, not a file.** A register kept as
887
1332
  headings or as a table yields one specification per decision, each with its
888
1333
  own lifecycle, obligations and relations. See
889
- [ADR-0009](docs/adr/0009-a-specification-is-a-region.md).
1334
+ [ADR-0009](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0009-a-specification-is-a-region.md).
890
1335
  - **Repository configuration.** `.spec-graph.json`, `spec-graph.config.json`, or
891
1336
  a `"spec-graph"` key in `package.json`, with family allow and deny rules. See
892
- [ADR-0010](docs/adr/0010-configuration-belongs-to-the-repository.md).
1337
+ [ADR-0010](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0010-configuration-belongs-to-the-repository.md).
893
1338
 
894
1339
  ## 0.1.2
895
1340
 
@@ -897,7 +1342,7 @@ requirements table headed `Status` was enough to trigger it.
897
1342
 
898
1343
  - `--ignore-ref`, for repositories where `[[...]]` tags a concept rather than
899
1344
  naming a file. See
900
- [ADR-0008](docs/adr/0008-wiki-links-carry-no-path.md).
1345
+ [ADR-0008](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0008-wiki-links-carry-no-path.md).
901
1346
 
902
1347
  ## 0.1.1
903
1348