@descent-vtt/spec-graph 0.7.0 → 0.9.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.
Files changed (106) hide show
  1. package/CHANGELOG.md +477 -0
  2. package/README.md +109 -17
  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/identity.d.ts +12 -4
  16. package/dist/identity.js +32 -8
  17. package/dist/identity.js.map +1 -1
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.js +4 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/lifecycle.js +10 -2
  22. package/dist/lifecycle.js.map +1 -1
  23. package/dist/markdown.d.ts +53 -161
  24. package/dist/markdown.js +42 -864
  25. package/dist/markdown.js.map +1 -1
  26. package/dist/report.d.ts +23 -0
  27. package/dist/report.js +80 -0
  28. package/dist/report.js.map +1 -1
  29. package/dist/rules.js +48 -14
  30. package/dist/rules.js.map +1 -1
  31. package/dist/sections.d.ts +1 -1
  32. package/dist/sections.js +18 -4
  33. package/dist/sections.js.map +1 -1
  34. package/dist/select.d.ts +2 -2
  35. package/dist/select.js +3 -3
  36. package/dist/select.js.map +1 -1
  37. package/dist/source.d.ts +6 -19
  38. package/dist/source.js +4 -70
  39. package/dist/source.js.map +1 -1
  40. package/dist/types.d.ts +23 -3
  41. package/dist/types.js +1 -1
  42. package/dist/types.js.map +1 -1
  43. package/dist/vendor/spec-core/markdown/frontmatter.d.ts +103 -0
  44. package/dist/vendor/spec-core/markdown/frontmatter.js +459 -0
  45. package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -0
  46. package/dist/vendor/spec-core/markdown/headings.d.ts +45 -0
  47. package/dist/vendor/spec-core/markdown/headings.js +199 -0
  48. package/dist/vendor/spec-core/markdown/headings.js.map +1 -0
  49. package/dist/vendor/spec-core/markdown/index.d.ts +5 -0
  50. package/dist/vendor/spec-core/markdown/index.js +4 -0
  51. package/dist/vendor/spec-core/markdown/index.js.map +1 -0
  52. package/dist/vendor/spec-core/markdown/layout.d.ts +39 -0
  53. package/dist/vendor/spec-core/markdown/layout.js +28 -0
  54. package/dist/vendor/spec-core/markdown/layout.js.map +1 -0
  55. package/dist/vendor/spec-core/markdown/links.d.ts +22 -0
  56. package/dist/vendor/spec-core/markdown/links.js +364 -0
  57. package/dist/vendor/spec-core/markdown/links.js.map +1 -0
  58. package/dist/vendor/spec-core/markdown/lists.d.ts +22 -0
  59. package/dist/vendor/spec-core/markdown/lists.js +127 -0
  60. package/dist/vendor/spec-core/markdown/lists.js.map +1 -0
  61. package/dist/vendor/spec-core/markdown/scan.d.ts +36 -0
  62. package/dist/vendor/spec-core/markdown/scan.js +439 -0
  63. package/dist/vendor/spec-core/markdown/scan.js.map +1 -0
  64. package/dist/vendor/spec-core/markdown/syntax.d.ts +75 -0
  65. package/dist/vendor/spec-core/markdown/syntax.js +181 -0
  66. package/dist/vendor/spec-core/markdown/syntax.js.map +1 -0
  67. package/dist/vendor/spec-core/markdown/tables.d.ts +18 -0
  68. package/dist/vendor/spec-core/markdown/tables.js +86 -0
  69. package/dist/vendor/spec-core/markdown/tables.js.map +1 -0
  70. package/dist/vendor/spec-core/markdown/types.d.ts +287 -0
  71. package/dist/vendor/spec-core/markdown/types.js +9 -0
  72. package/dist/vendor/spec-core/markdown/types.js.map +1 -0
  73. package/dist/vendor/spec-core/path/index.d.ts +1 -0
  74. package/dist/vendor/spec-core/path/index.js +2 -0
  75. package/dist/vendor/spec-core/path/index.js.map +1 -0
  76. package/dist/vendor/spec-core/path/posix.d.ts +83 -0
  77. package/dist/vendor/spec-core/path/posix.js +150 -0
  78. package/dist/vendor/spec-core/path/posix.js.map +1 -0
  79. package/dist/vendor/spec-core/pattern/automaton.d.ts +129 -0
  80. package/dist/vendor/spec-core/pattern/automaton.js +340 -0
  81. package/dist/vendor/spec-core/pattern/automaton.js.map +1 -0
  82. package/dist/vendor/spec-core/pattern/charset.d.ts +58 -0
  83. package/dist/vendor/spec-core/pattern/charset.js +116 -0
  84. package/dist/vendor/spec-core/pattern/charset.js.map +1 -0
  85. package/dist/vendor/spec-core/pattern/glob.d.ts +121 -0
  86. package/dist/vendor/spec-core/pattern/glob.js +481 -0
  87. package/dist/vendor/spec-core/pattern/glob.js.map +1 -0
  88. package/dist/vendor/spec-core/pattern/index.d.ts +6 -0
  89. package/dist/vendor/spec-core/pattern/index.js +4 -0
  90. package/dist/vendor/spec-core/pattern/index.js.map +1 -0
  91. package/dist/{regex.d.ts → vendor/spec-core/pattern/regex.d.ts} +20 -29
  92. package/dist/{regex.js → vendor/spec-core/pattern/regex.js} +40 -49
  93. package/dist/vendor/spec-core/pattern/regex.js.map +1 -0
  94. package/dist/vendor/spec-core/text/index.d.ts +2 -0
  95. package/dist/vendor/spec-core/text/index.js +2 -0
  96. package/dist/vendor/spec-core/text/index.js.map +1 -0
  97. package/dist/vendor/spec-core/text/lines.d.ts +72 -0
  98. package/dist/vendor/spec-core/text/lines.js +156 -0
  99. package/dist/vendor/spec-core/text/lines.js.map +1 -0
  100. package/dist/yaml.d.ts +44 -12
  101. package/dist/yaml.js +115 -206
  102. package/dist/yaml.js.map +1 -1
  103. package/package.json +3 -2
  104. package/src/vendor/spec-core/LICENSE +21 -0
  105. package/src/vendor/spec-core/README.md +8 -0
  106. package/dist/regex.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,483 @@
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.0
7
+
8
+ Versions are staged by CI from a `v*` tag, through npm's trusted
9
+ publishing, and released by a maintainer with a second factor, no longer
10
+ published from a workstation
11
+ ([ADR-0021](docs/adr/0021-releases-are-published-by-ci.md)). There is no
12
+ publish token anywhere, and every version from here on carries a provenance
13
+ attestation naming the repository, the commit and the run that built the
14
+ tarball. `npm audit signatures` checks it without taking anyone's word for
15
+ anything. 0.2.0 to 0.8.0 have none and never will.
16
+
17
+ **What to expect on upgrading.** More findings rather than fewer, and nearly
18
+ all of them from one change: a code span ends with its paragraph. Over a large
19
+ repository of hand-written decisions and logs that 0.8.0 checked, almost every
20
+ difference was a `broken-reference`. Bare identifiers that a lone backtick
21
+ earlier in a paragraph or a table had hidden are read now, and many name
22
+ nothing; a few that 0.8.0 read out of real code spans, its backticks out of
23
+ step, are code again and go. Text read for the first time can hold a relation,
24
+ and bring a `stale-premise` or a `ghost-handover` with it, and a relation read
25
+ out of code can take an `unreciprocated-supersession` away with it. A
26
+ paragraph indented four columns past its list item's text after a blank line
27
+ is code now, as CommonMark reads it, and as GitHub renders it: eleven of this
28
+ repository's ADRs had answers written that way, and they are re-indented as
29
+ paragraphs of their items. The glob, `archived` and front-matter
30
+ changes are narrower, and each is described below; a pattern the dialect
31
+ refuses stops the run with exit `2` and says how to write it. Under
32
+ `--ratchet`, expect to record the baseline once; "What a baseline sees", under
33
+ Changed, lists the entries that go stale.
34
+
35
+ ### Added
36
+
37
+ **`--format gitlab`**, the Code Quality report a GitLab merge request reads,
38
+ so the family is not GitHub's alone. `check` only, like SARIF. One issue per
39
+ finding: the rule as `check_name`, message and hint as `description`, the file
40
+ and line as `location`, and a `fingerprint` that is a SHA-256 of the rule,
41
+ the specification and what within it - the identity a baseline counts by. No
42
+ line, so moving a paragraph does not make a finding new, and no message, whose
43
+ counts move when one of three boxes is ticked; a second finding identical in
44
+ all three is told apart by its order.
45
+ `error` is `critical`, or `major` when only `--strict` made it an error;
46
+ `warn` is `minor` and `info` is `info`. The README has the job to add. See
47
+ [ADR-0015](docs/adr/0015-feedback-goes-where-the-tools-already-look.md).
48
+
49
+ ### Breaking (library API)
50
+
51
+ The command line changes only as the sections below describe. A program that
52
+ imports spec-graph sees these as well:
53
+
54
+ - **`ScannedDocument.masked` is gone.** `ScannedDocument` is spec-core's
55
+ `MarkdownScan`, and the masked copy is `masks.structure`, beside
56
+ `masks.prose` and `masks.directives`.
57
+ - **`ScannedDocument.lines` holds the front matter's lines**, flagged
58
+ `frontMatter`, where it began at the body. Code that numbers or reads body
59
+ lines from it skips those.
60
+ - **`ScannedDocument.links` holds images**, flagged `Link.image`. Code that
61
+ reads it for citations filters them out, as the analysis does but for a wiki
62
+ embed.
63
+ - **Required fields are new**, so an object built by hand needs them:
64
+ `ScannedLine` has `frontMatter`, `html` and `comment`; `FrontMatter` has
65
+ `kind` and `closeLine`; `Link` has `image` and `targetEnd`; `Heading` has
66
+ `form`, `anchor`, `textStart`, `textEnd` and `endLine`; `ListItem` has
67
+ `endLine` and `quoteDepth`; `HtmlComment` has `closed`; `DocumentNode` has
68
+ `history`.
69
+ - **An error from `compilePattern` is named `RegexError`.** `PatternError` is
70
+ the same class, so `instanceof` holds; a check of `error.name` against
71
+ `'PatternError'` does not.
72
+ - **`createReferenceFilter` throws on a blank pattern**, which it used to drop,
73
+ and `compileGlob`, `createGlobMatcher` and `walkFiles` throw on every
74
+ pattern the dialect refuses.
75
+
76
+ `ParseProblem` has `unread`, and it is optional.
77
+
78
+ ### Changed
79
+
80
+ **The matcher behind `~=` is spec-core's.** It moved, unchanged, to spec-core -
81
+ the library the spec-* tools share - and is copied back into
82
+ `src/vendor/spec-core/`, byte for byte and checked against the SHA-256 it was
83
+ copied with, so spec-graph still installs nothing. The package carries the
84
+ copies compiled, and spec-core's MIT notice with them, as
85
+ `src/vendor/spec-core/LICENSE`. A selector reads exactly as
86
+ before. `compilePattern` and `PatternError` are still exported and are the same
87
+ function and class; a caught error's `name` is now `RegexError`. The tests that
88
+ hold the matcher to `RegExp` moved with it, and the copy is left out of this
89
+ repository's mutation sweep and coverage, which spec-core's measure instead.
90
+ See [ADR-0017](docs/adr/0017-a-predicate-must-finish.md).
91
+
92
+ **Globs are the dialect every spec-* tool reads.** The patterns to check,
93
+ `--ignore`, `--history` and `historyPatterns` are compiled by spec-core's glob
94
+ engine, in the `path` dialect spec-brief and spec-guard read too, so a scope
95
+ means the same thing to each of them. Where that reading differs from
96
+ spec-graph's old one, a repository will see it:
97
+
98
+ - **Case is respected on every host.** `Docs/**` matched `docs/a.md` on Windows
99
+ and nowhere else, so one repository gave one answer on a Windows checkout and
100
+ another in CI. And a pattern with no glob syntax was compared lower-cased on
101
+ every host, Linux included: `README.md` also took `readme.md`. Neither folds
102
+ now, and `compileGlob`'s `ignoreCase` is off unless asked for on any host.
103
+ - **`**` inside a name is refused.** `docs/**.md`, `**.ts` and `a**b` crossed
104
+ directories wherever the `**` was written. `**` means any number of
105
+ directories only as a whole segment now, and the error says so: write
106
+ `docs/**/*.md` for any depth, as it always read, or `*.md` for one level.
107
+ - **An extended glob is refused.** `+(a|b)` was the literal text `+(a|b)`, a
108
+ scope that matched nothing; the error says to write `{a,b}`, and `[(]` for a
109
+ parenthesis. A group is an extended glob only when it holds a `|`, so
110
+ `C++(notes).md` and `books/*(2017).md` are names with parentheses in them,
111
+ as they were.
112
+ - **An unclosed `[` is an error**, like an unclosed `{` always was. It was a
113
+ literal `[`, which made the pattern a scope that matched nothing. The run
114
+ stops with exit `2` and names the pattern, from the command line or from the
115
+ configuration file.
116
+ - **A trailing `/` means the directory's contents.** It was normalised away, so
117
+ `drafts*/` named a file called `drafts`, and `docs/` took `docs` itself.
118
+ - **Braces expand to literals.** `{docs,specs}` was a glob matching two names
119
+ exactly; it is two literals now, each a directory and what it holds.
120
+ - **A class never matches `/`**, and **`[^a]` negates**, as `[!a]` does. `a[!b]c`
121
+ took `a/c`, and `[^a]` was the class of `^` and `a`.
122
+ - **A pattern must name a path under the root.** `.` and `./` are refused
123
+ rather than read as nothing, and so is a `..` anywhere in a pattern:
124
+ `docs/../specs` was resolved to `specs`, and `../other/**` walked outside
125
+ the root. A `.` segment inside a pattern is dropped, as it was. A `..` typed
126
+ below the root is resolved against where it was typed, so
127
+ `spec-graph "../*.md"` in `docs/deep` still means `docs/*.md`.
128
+ - **An empty pattern is refused**, in every list: the patterns to check,
129
+ `--ignore`, `--history`, `--ignore-ref` and their keys in the configuration
130
+ file. Each was read as nothing, or dropped - an unset variable in
131
+ `--history "$LOGS"` checked the logs as specifications, and nothing said
132
+ why. A pattern of spaces is empty to `--ignore-ref`, which trims what it
133
+ reads, and a name to the others.
134
+ - **The walk finds a directory only as it is spelled on disk.** It starts at a
135
+ pattern's literal prefix, and on a filesystem that ignores case `Docs/` found
136
+ `docs/` and reported its files as `Docs/...`. A pattern rooted at `/` is not
137
+ walked under the root either, where it reported paths like `/docs/a.md`.
138
+
139
+ `--ignore-ref` runs on the same engine and keeps its own reading, since a
140
+ reference target is not a path: a bare pattern matches one target exactly, case
141
+ is ignored on every host, `.` and `..` are the text of the link, and a `\`
142
+ escapes the next character rather than being matched as itself. The glob
143
+ changes above apply to it otherwise, and three more come with the syntax: a `\`
144
+ before a letter or digit, or at the end, is refused, as is a leading `!` - both
145
+ were literals, and `--ignore-ref` has no list to take a pattern back out of -
146
+ and `a//b` reads as `a/b`.
147
+
148
+ `globToRegExp` is deprecated, and stays as the old reading that the test of
149
+ every difference above runs against; it no longer folds case on Windows. See
150
+ [ADR-0022](docs/adr/0022-globs-are-the-family-path-dialect.md).
151
+
152
+ **Documents are read by spec-core's scanner.** It moved to spec-core, built on
153
+ this repository's scanner as its model, and is copied back into
154
+ `src/vendor/spec-core/` and checked by hash, so spec-graph still installs
155
+ nothing. It follows CommonMark wherever CommonMark decides what is code or a
156
+ comment, and where that reads a document differently from before, spec-graph
157
+ follows it. Over this repository, its test corpora and spec-core's documents,
158
+ the graph and the findings are what they were. A repository sees a difference
159
+ where it wrote one of these:
160
+
161
+ - **A code span ends with its paragraph.** A lone backtick hid everything up to
162
+ the next run of its length, however many paragraphs on, citations included.
163
+ - **A heading reads as a renderer shows it.** A comment on its line is not part
164
+ of its text, and `# C#` is about `C#`: a closing run of `#` needs a space
165
+ before it. That text is a document's title, a section's name and the slug in
166
+ the id of every item under it, so an item under `## Notes <!-- x -->` moves
167
+ from `ADR-0001#notes----x---.1` to `ADR-0001#notes.1`, and
168
+ `## Open Questions <!-- short -->` holds obligations at last. A baseline does
169
+ not notice the move: an entry names the specification and what within it,
170
+ never an item.
171
+ - **A link to GitHub's anchor for a repeated heading resolves.** GitHub renders
172
+ the second `## Notes` as `#notes-1`, and a link to it was a
173
+ `broken-reference`. The slug still names the first heading, and `#notes-2`
174
+ beside two headings is still broken.
175
+ - **Nothing inside `<pre>`, `<script>`, `<style>` or `<textarea>` is
176
+ structure.** Their links were masked already; now no heading, list item, table
177
+ or `Status:` line is read from one either, so `- [ ] example` in a `<pre>`
178
+ block is not an open obligation, and a status section that opens with one
179
+ declares no status. A `@spec-item` above one does not reach an item below it,
180
+ as it never reached across a fence.
181
+ - **Nothing inside a comment opens a block.** A fence inside a comment opened
182
+ one, and with no closer everything after it was code; a list marker inside one
183
+ decided whether an indented block after it was code. Neither does now. A
184
+ comment that opens a line and never closes runs to the end of the document, as
185
+ it renders, and a parse problem says where it opened; no directive is read from
186
+ it, so a stray `<!-- @spec-ignore` does not drop the file.
187
+ - **Headings, rules and list items as CommonMark reads them.** A setext underline
188
+ is not used twice - `Title`, `===`, `---` is one heading and a rule, not a
189
+ second heading called `===` - and does not underline a line in a block quote.
190
+ `* * *` is a rule, not a list item. A list item ends at a fence or a block
191
+ quote no deeper than its marker, so a relation written after an unindented
192
+ fence is the document's and not the item's. And a heading closes every list,
193
+ so an item indented under a list a heading closed is at depth 0, and an
194
+ obligation when its section holds them.
195
+ - **Links as CommonMark reads them.** `[a](b c)` is text, not a link to `b`.
196
+ `[foo](not a link)` is the shortcut `[foo]` when `foo` is defined, not a link
197
+ to `not`. A footnote `[^1]: text` and a line `[Note]: prose` are not
198
+ definitions, so `[^1]` and `[Note]` are no longer links to the first word
199
+ after them, which were broken references. A link inside brackets that are not
200
+ one, `[see [ADR-7](0007.md) here]`, is read. Labels match with case and
201
+ whitespace folded, so `[Two Words][]` finds `[TWO WORDS]:`. A finding on
202
+ `[a](<b c.md>)` points inside the angle brackets, a column later than it did.
203
+ - **A table needs a delimiter row with as many cells as its header.** One
204
+ without was read as a table, and as a register when its columns said so.
205
+ - **A fence ends with its block quote.** One opened in a quote and never closed
206
+ made the rest of the document code.
207
+ - **Indented code is four columns past its container**: the margin, or the text
208
+ of the list item the line is in. Inside a list four columns were never code,
209
+ so a link or a checkbox in an example indented under an item was read as one.
210
+ Under `- item`, six columns after a blank line are code now, and four are
211
+ still the item's text. A fence line that deep opens no fence - after a blank
212
+ line it is indented code, and under a paragraph it is text - where it used to
213
+ open one at any depth and, unclosed, run to the end of the document.
214
+ - **Front matter opens on exactly `---` or `+++`, and closes on exactly
215
+ `---`, `...` or `+++`.** `----` is a rule. And `----` or `....` under an
216
+ opening `---` closed the block, and now leaves it open: a block that never
217
+ closes is read as a rule and Markdown, none of its keys are read, and a parse
218
+ problem says where it opened.
219
+
220
+ **What a baseline sees.** A finding the scan no longer produces leaves its
221
+ entry no longer occurring, which fails a run under `--ratchet`. These go: the
222
+ first word of a footnote `[^1]: See ...` or a line `[Note]: prose`, which was
223
+ a broken reference; the `b` of `[a](b c)` and the `not` of
224
+ `[foo](not a link)`; a link to GitHub's `#notes-1`, which was an unknown
225
+ anchor; an `orphaned-obligation` made of boxes held in `<pre>` or in a
226
+ comment; a link inside a code span that a lone backtick earlier in the document
227
+ had knocked out of step, so that it read as text; and a link in an example
228
+ indented under a list item. One `--record-baseline` settles them. What the scan
229
+ sees now and did not - a citation a lone backtick hid, an obligation under a
230
+ heading with a comment on its line, what followed a fence indented under a
231
+ paragraph, the shortcut `[foo]` - is a new finding, as any other is.
232
+
233
+ For the library API, the scanner's types are spec-core's, exported under the
234
+ names they had, and what that breaks is listed under
235
+ [Breaking (library API)](#breaking-library-api). Beside it, `blocks`,
236
+ `codeSpans` and `bom` are new on `ScannedDocument`, and
237
+ `isMasked(offset, mask?)` asks any of its three masks. The analysis leaves
238
+ images out but for wiki embeds. `parseDirectives` skips a comment whose
239
+ `closed` is `false`. `createLineIndex` is spec-core's, the same table. See
240
+ [ADR-0023](docs/adr/0023-the-scanner-is-the-familys.md).
241
+
242
+ **Front matter is read by spec-core's reader, and what it does not read says
243
+ why.** Keys are compared in lower case and one level of nesting is read as
244
+ `parent.child`, as before. Where the reader and YAML part from the old one:
245
+
246
+ - **A value YAML does not say is not read.** A plain value holding `: ` -
247
+ `title: ADR-7: Sharding`, which YAML reads as an error - a value continued onto
248
+ the next line, which was read as its first line, a block scalar, an anchor and
249
+ a tag are left out rather than guessed at. Quote the value and it is read. An
250
+ inline list on the line under its key, `translators:` over an indented `[]`,
251
+ is left out too, with the advice to write it after the colon, since quoting it
252
+ would make it a string. Each is a parse problem at the value, under
253
+ `--verbose` and in `--format json`, naming the reason; so are a line that is
254
+ not `key: value`, a key written twice, of which the last still wins, and TOML
255
+ front matter, which was read as nothing in silence. A status left out this way
256
+ is looked for in a `## Status` section, as a missing one always was.
257
+ - **What the graph lost is counted without `--verbose`.** A status, an id, an
258
+ alias or a relation left out - or a block never closed, or a line that belongs
259
+ to no key - can turn a failing check into a passing one:
260
+ `status: Superseded by ADR-3: see notes` reads as unknown, and the
261
+ supersession finding 0.8.0 reported is gone. So a run without `--verbose` prints one
262
+ line above its verdict, `! 2 front-matter values not read - see --verbose`, in
263
+ the terminal and in `--format markdown`. It is not a finding and does not
264
+ change the exit code. A title or a description left out is not counted.
265
+ `ParseProblem` says which problems count, as `unread`.
266
+ - **A key indented under nothing is not read.** `status: accepted` indented on
267
+ the first line of front matter is valid YAML, and 0.8.0 read it. It is a line
268
+ that belongs to no key now, so the document reads as `unknown`, and the count
269
+ of values not read includes it. Write the key at the margin.
270
+ - **A list written at its key's own indentation is a list.** `deps:` over
271
+ `- ADR-1` was an empty value.
272
+ - **Quoted values are YAML's.** `'it''s'` is `it's`, and `"a\tb"` holds a tab.
273
+
274
+ `parseFrontMatter` keeps its signature and reads with spec-core's reader, so it
275
+ leaves out what the reader does not read. See
276
+ [ADR-0023](docs/adr/0023-the-scanner-is-the-familys.md).
277
+
278
+ **`archived` is a record, not a retired decision.** spec-brief closes a round
279
+ of work by writing `status: archived` and moving the brief to an archive
280
+ directory, and every spec-* tool reads that word as a finished round which it
281
+ is normal to depend on. spec-graph read it as retired: a live brief that
282
+ depended on an archived one was reported as a `stale-premise`, and an archived
283
+ brief with a task left unticked as an `orphaned-obligation`. It is the `record`
284
+ phase now, as a file declared by `historyPatterns` is - its links are still
285
+ checked, its unticked boxes are nobody's work and leave the open count, and
286
+ live work handed to it is still a `ghost-handover`, which says it is archived.
287
+ So is the status word `archive`.
288
+
289
+ It is a closed round and not a log, so that is all that changes. The claims an
290
+ archived document makes are still checked as they were when it read as
291
+ retired: a supersession it declares (`live-supersession` on what it
292
+ supersedes), a cycle it closes (`circular-delegation`) and a link to itself
293
+ (`self-reference`). What it hands on and what it rests on are still not held
294
+ against it. An archived document that something supersedes and that never
295
+ says so is an `unreciprocated-supersession`, whose hint is now to make its
296
+ status `archived, superseded by X` - which retires it, so what depends on it
297
+ is a `stale-premise` again. A document declared by `historyPatterns` or
298
+ `<!-- @spec-history -->` is exempt from all of it, as before; `DocumentNode`
299
+ says which kind of record a document is, as `history`.
300
+
301
+ A document still retires by saying `superseded`, `deprecated` or `retired`,
302
+ and `archived, superseded by B-7` is retired, since a retirement word wins
303
+ wherever it is written. A document under an `archive/` directory that declares
304
+ no status is retired as before. A repository that wrote `archived` for a
305
+ retired decision loses the `stale-premise` findings on documents that depend
306
+ on it, and the `orphaned-obligation` findings on its open items, and a baseline
307
+ that accepted one reports that entry as no longer occurring, which fails a run
308
+ under `--ratchet`. See
309
+ [ADR-0011](docs/adr/0011-a-record-is-not-a-specification.md).
310
+
311
+ ### Fixed
312
+
313
+ **A code span that mentions a comment is code.** ``Use `<!--` to open one``
314
+ opened an HTML comment at the `<!--`, and it ran to the next `-->` anywhere in
315
+ the document: every heading, citation and link in between went into it, and a
316
+ link to a file that does not exist went unreported. Comments were found first
317
+ and code spans after, so the span never had a say. Both are now found in one
318
+ pass, left to right, and whichever opens first wins, which is how CommonMark
319
+ reads them: a `<!--` inside a span is text, and so is a backtick inside a
320
+ comment. A span still closes only on a run of its own length, and an escaped
321
+ backtick still opens none.
322
+
323
+ The same defect read a directive quoted in inline code as a directive. This
324
+ repository's README and ADR-0011 both write `` `<!-- @spec-history -->` `` in a
325
+ sentence, and both were being checked as historical records. They are a
326
+ document and an accepted decision again, and `selfcheck` counts one more open
327
+ obligation than it did, because ADR-0011's open question is no longer exempt. A document quoting `<!-- @spec-ignore -->` that way was skipped whole,
328
+ and is checked now.
329
+
330
+ **Unclosed backtick runs cost the document's length once, not once each.** Each
331
+ searched the rest of the document for its closer, so a line of runs of different
332
+ lengths made the scan grow as the length to the power 1.5: 2 MB took fifteen
333
+ seconds. The first search that fails now keeps what it saw, and the same 2 MB
334
+ takes about sixty milliseconds.
335
+
336
+ **What a comment holds is not structure.** Headings, list items and tables are
337
+ read from lines, and none of them asked whether the line was inside a comment.
338
+ `## Hidden` in `<!-- -->` was a heading and started a section, a checklist
339
+ commented out was a list of open obligations nobody could see, and
340
+ `<!-- note -->` over `---` was a heading called "<!-- note -->". A line whose
341
+ first character is inside a comment is now none of these, and cannot end a list
342
+ item either; `ScannedLine` says so as `comment`. Directives are read out of
343
+ comments exactly as before.
344
+
345
+ Nor is a status read out of one. Under `## Status`, a template's
346
+ `<!-- proposed | accepted -->` was taken as the status - which no vocabulary
347
+ knows, so the phase was `unknown` with `Accepted` on the next line - and a
348
+ commented-out `Status: proposed` gave a register's section the second signal it
349
+ needs to become a specification.
350
+
351
+ An obligation that was only ever inside a comment is gone on upgrade, with any
352
+ finding about it, so a baseline that accepted one reports that entry as no
353
+ longer occurring under `--ratchet`. An item under a heading that was only inside
354
+ a comment is numbered under the heading above it instead.
355
+
356
+ ### Verified
357
+
358
+ `npm run lint`, 987 tests, `npm run build` and `npm run selfcheck`. Fifteen of
359
+ the 25 new tests fail against the unfixed source; the other ten hold the one pass
360
+ to what the two it replaced already got right - fences, escapes, runs that never
361
+ close, a `<` that is not `<!--`. Measured on their own with
362
+ `--coverageAnalysis all`, before and after: `markdown.ts` went from 69.30% to
363
+ 72.97%, the status readers in `sections.ts` from 52.63% to 59.49%, and
364
+ `statusSectionBody` in `extract.ts` from 60.00% to 73.08%. The mutants left alive
365
+ on the new lines change how long an answer takes, or compare offsets that cannot
366
+ be equal. 600,000 generated documents read the same through the one pass as
367
+ through a naive left-to-right reading of the same rule, and this repository's
368
+ documents scan no slower than before.
369
+
370
+ Then spec-core, the record phase and GitLab: `npm run lint`, 1,010 tests,
371
+ `npm run build` and `npm run selfcheck`. The 30 tests of `tests/regex.test.ts`
372
+ went to spec-core with the matcher, where they run with only its names
373
+ changed. Every glob change listed above has a test that fails against the
374
+ 0.8.0 matcher - the host-dependent ones on Windows, where the defect was - and
375
+ the differential in `tests/glob.test.ts` requires each of them to occur and
376
+ nothing else to; five of the eight tests for `archived` fail without its table
377
+ entry, and the other three hold what must not move. Measured on their own with
378
+ `--coverageAnalysis all`, before and after: `glob.ts` from 73.74% to 77.22%,
379
+ `cli.ts` from 81.11% to 83.45%, `report.ts` from 69.03% to 70.17% and
380
+ `lifecycle.ts` from 53.10% to 53.26%. `select.ts`, whose only change is an
381
+ import, read 74.01% and then 71.08%: sixteen selector-parsing mutants that no
382
+ assertion kills in either run timed out in the first and survived in the
383
+ second. Of the mutants left alive on new lines, seven are equivalent and say
384
+ so where they are; the rest are on the ignore filter, whose reading did not
385
+ change, and on the symlink branch of the walk, which no test reaches, as
386
+ before.
387
+
388
+ Then spec-core's scanner and front-matter reader: `npm run lint`, 1,041 tests,
389
+ `npm run build` and `npm run selfcheck`. Of the 27 new tests, 23 fail against
390
+ the source before the move; the other four hold what must not move - an embed's
391
+ edge, the last of a key written twice, no directive from a comment that never
392
+ closes, and no suffix for a heading that is not repeated. The binaries before
393
+ and after read this repository, its seven fixture corpora and spec-core's
394
+ documents into identical graph exports and identical findings. Over a corpus
395
+ written to hit each difference above, every difference in their output was one
396
+ of them. The scanner's own mutants, 72% to 77% here, are spec-core's sweep's
397
+ now, at 98% there; what the move left here is measured by CI's sweep.
398
+
399
+ Then the review of those rounds: `npm run lint`, 1,053 tests, `npm run build`
400
+ and `npm run selfcheck`, with spec-core copied again at cbe2223. Every new test
401
+ fails against the source the review read but one, which holds what an archived
402
+ document is still exempt from. They cover spec-core's licence in the package,
403
+ `**` inside a name and an extended glob, code past a list item's text, an empty
404
+ `--ignore` and `--ignore-ref`, what an archived document still answers for, the
405
+ count of front-matter values not read, front matter that never closes, and a
406
+ GitLab fingerprint across a ticked box. A baseline recorded by 0.8.0 over a
407
+ corpus holding each scanner difference, run under `--ratchet`, accepted the
408
+ finding on a moved item and went stale only on findings the scan no longer
409
+ produces. Against 0.8.0 over the review's corpora for `archived`, the difference
410
+ is the stale premise and the orphaned obligation the change is for. Over the
411
+ large repository above, this branch and the one reviewed report identical
412
+ findings. spec-core's documents and the seven fixture corpora read into the same
413
+ graph export as before the copy; this repository's lost the seven relations
414
+ above.
415
+
416
+ ## 0.8.0
417
+
418
+ A register row took its identifier from the first thing its *title* said, not
419
+ from its ID column. Found on the same 885-document repository as 0.7.0: 27 of
420
+ 232 rows of one issues register were filed under the wrong entity.
421
+
422
+ A minor rather than a patch, and the baseline is the reason. A baseline is
423
+ fingerprinted on document ids, and the whole of this fix is that those ids move.
424
+ Measured on an unchanged corpus against a baseline recorded by 0.7.0: the
425
+ accepted finding came back as new, its entry was reported as no longer
426
+ occurring, and `check --baseline` went from exit `0` to exit `1`. One
427
+ `--record-baseline` settles it, and the entries it writes are the ones that were
428
+ meant all along. The rule at the top of this file says a patch asks nothing of a
429
+ repository that upgrades, and this asks for that run.
430
+
431
+ ### Fixed
432
+
433
+ **A title names nothing.** `| OI-V-05 | ADR-040's enforcement point has no
434
+ browser test |` was read as `ADR-040`. Eighteen rows collided with the ADR they
435
+ cite that way, and the collision does not merely mislabel a row: run against a
436
+ corpus holding both, the ADR keeps the id and the issue has no node in the graph
437
+ at all. Nine more opened with a noun phrase, inventing a `STAGE-1`, a
438
+ `GUARDRAIL-5` and a `CLOSED-2026` that nobody had written, each of them open -
439
+ which is what drew the false `ghost-handover` findings the register was reported
440
+ for; two rows collapsed onto one `STAGE-1`.
441
+
442
+ The identifier a title opens with almost always belongs to the document the
443
+ title is *about*, so a title is read as a name only where nothing else has given
444
+ one - and where it is not the name it registers no alias either, because an
445
+ alias sends every citation of the real document to whatever quoted it.
446
+ `# ADR-0040 considered harmful` at the top of `0007-sharding.md` was registering
447
+ `adr0040` against `ADR-0007`.
448
+
449
+ Two things count as having given one: a number, from a declaration or a file
450
+ name, and for a register row its id column, whatever shape the id is in. A whole
451
+ file keeps the looser reading and its H1 can still name it, because there
452
+ nothing separates a title about another document from a title about this one:
453
+ `slug:` holds a URL segment rather than an id, so `slug: sharding` in
454
+ `sharding.md` under `# ADR-0007: Sharding` has to stay `ADR-0007`. A declaration
455
+ and the file name are still weighed together rather than ranked, so
456
+ `slug: sharding-the-write-path` in `0007-sharding.md` is `ADR-0007` as before.
457
+
458
+ **A region no longer reads a number out of the file it sits in.** Every row of
459
+ a register kept in `0042-open-issues.md` answered to `ADR-0042`, and so did the
460
+ file. ADR-0009 already said a region does not claim the file's *path*, for the
461
+ reason that two nodes answering to one name make every link to it ambiguous; the
462
+ file's name is that same claim spelled differently, and the rule was written
463
+ down as two.
464
+
465
+ **A file's own title heading is matched by its number, not its spelling.**
466
+ `# ADR-040` at the top of `0040-enforce.md` was read as a region *inside* the
467
+ file it names: one decision, two nodes, a split lifecycle, and a citation
468
+ arriving at whichever padding it happened to use. Padding is not part of an
469
+ identity anywhere else - `ADR-40`, `ADR-040` and `ADR-0040` all resolve to one
470
+ document - and now it is not part of this comparison either.
471
+
472
+ ### Verified
473
+
474
+ `npm run lint`, 950 tests and `npm run selfcheck`. Thirteen of the fifteen new
475
+ tests fail against the unfixed source; the other two are the guards on the
476
+ layouts that must not move, which have to pass both ways. `identity.ts` and
477
+ `sections.ts` were measured on their own before and after: 72.77% and 66.89%
478
+ became 75.18% and 71.93%, and no mutant on the new lines survived, so nothing
479
+ was masked. ADR-0004 carries the rule as a
480
+ fifth asymmetry, with the hyphenated-prefix question declined under it, and
481
+ ADR-0009 is amended in place.
482
+
6
483
  ## 0.7.0
7
484
 
8
485
  Three ways a run handed back something other than what it said it was: a