@descent-vtt/spec-graph 0.8.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.
- package/CHANGELOG.md +410 -0
- package/README.md +102 -17
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +38 -19
- package/dist/cli.js.map +1 -1
- package/dist/directives.d.ts +9 -2
- package/dist/directives.js +15 -4
- package/dist/directives.js.map +1 -1
- package/dist/extract.d.ts +1 -1
- package/dist/extract.js +50 -16
- package/dist/extract.js.map +1 -1
- package/dist/glob.d.ts +44 -44
- package/dist/glob.js +152 -116
- package/dist/glob.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.js +10 -2
- package/dist/lifecycle.js.map +1 -1
- package/dist/markdown.d.ts +53 -161
- package/dist/markdown.js +42 -864
- package/dist/markdown.js.map +1 -1
- package/dist/report.d.ts +23 -0
- package/dist/report.js +80 -0
- package/dist/report.js.map +1 -1
- package/dist/rules.js +48 -14
- package/dist/rules.js.map +1 -1
- package/dist/sections.d.ts +1 -1
- package/dist/sections.js +7 -3
- package/dist/sections.js.map +1 -1
- package/dist/select.d.ts +2 -2
- package/dist/select.js +3 -3
- package/dist/select.js.map +1 -1
- package/dist/source.d.ts +6 -19
- package/dist/source.js +4 -70
- package/dist/source.js.map +1 -1
- package/dist/types.d.ts +23 -3
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vendor/spec-core/markdown/frontmatter.d.ts +103 -0
- package/dist/vendor/spec-core/markdown/frontmatter.js +459 -0
- package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -0
- package/dist/vendor/spec-core/markdown/headings.d.ts +45 -0
- package/dist/vendor/spec-core/markdown/headings.js +199 -0
- package/dist/vendor/spec-core/markdown/headings.js.map +1 -0
- package/dist/vendor/spec-core/markdown/index.d.ts +5 -0
- package/dist/vendor/spec-core/markdown/index.js +4 -0
- package/dist/vendor/spec-core/markdown/index.js.map +1 -0
- package/dist/vendor/spec-core/markdown/layout.d.ts +39 -0
- package/dist/vendor/spec-core/markdown/layout.js +28 -0
- package/dist/vendor/spec-core/markdown/layout.js.map +1 -0
- package/dist/vendor/spec-core/markdown/links.d.ts +22 -0
- package/dist/vendor/spec-core/markdown/links.js +364 -0
- package/dist/vendor/spec-core/markdown/links.js.map +1 -0
- package/dist/vendor/spec-core/markdown/lists.d.ts +22 -0
- package/dist/vendor/spec-core/markdown/lists.js +127 -0
- package/dist/vendor/spec-core/markdown/lists.js.map +1 -0
- package/dist/vendor/spec-core/markdown/scan.d.ts +36 -0
- package/dist/vendor/spec-core/markdown/scan.js +439 -0
- package/dist/vendor/spec-core/markdown/scan.js.map +1 -0
- package/dist/vendor/spec-core/markdown/syntax.d.ts +75 -0
- package/dist/vendor/spec-core/markdown/syntax.js +181 -0
- package/dist/vendor/spec-core/markdown/syntax.js.map +1 -0
- package/dist/vendor/spec-core/markdown/tables.d.ts +18 -0
- package/dist/vendor/spec-core/markdown/tables.js +86 -0
- package/dist/vendor/spec-core/markdown/tables.js.map +1 -0
- package/dist/vendor/spec-core/markdown/types.d.ts +287 -0
- package/dist/vendor/spec-core/markdown/types.js +9 -0
- package/dist/vendor/spec-core/markdown/types.js.map +1 -0
- package/dist/vendor/spec-core/path/index.d.ts +1 -0
- package/dist/vendor/spec-core/path/index.js +2 -0
- package/dist/vendor/spec-core/path/index.js.map +1 -0
- package/dist/vendor/spec-core/path/posix.d.ts +83 -0
- package/dist/vendor/spec-core/path/posix.js +150 -0
- package/dist/vendor/spec-core/path/posix.js.map +1 -0
- package/dist/vendor/spec-core/pattern/automaton.d.ts +129 -0
- package/dist/vendor/spec-core/pattern/automaton.js +340 -0
- package/dist/vendor/spec-core/pattern/automaton.js.map +1 -0
- package/dist/vendor/spec-core/pattern/charset.d.ts +58 -0
- package/dist/vendor/spec-core/pattern/charset.js +116 -0
- package/dist/vendor/spec-core/pattern/charset.js.map +1 -0
- package/dist/vendor/spec-core/pattern/glob.d.ts +121 -0
- package/dist/vendor/spec-core/pattern/glob.js +481 -0
- package/dist/vendor/spec-core/pattern/glob.js.map +1 -0
- package/dist/vendor/spec-core/pattern/index.d.ts +6 -0
- package/dist/vendor/spec-core/pattern/index.js +4 -0
- package/dist/vendor/spec-core/pattern/index.js.map +1 -0
- package/dist/{regex.d.ts → vendor/spec-core/pattern/regex.d.ts} +20 -29
- package/dist/{regex.js → vendor/spec-core/pattern/regex.js} +40 -49
- package/dist/vendor/spec-core/pattern/regex.js.map +1 -0
- package/dist/vendor/spec-core/text/index.d.ts +2 -0
- package/dist/vendor/spec-core/text/index.js +2 -0
- package/dist/vendor/spec-core/text/index.js.map +1 -0
- package/dist/vendor/spec-core/text/lines.d.ts +72 -0
- package/dist/vendor/spec-core/text/lines.js +156 -0
- package/dist/vendor/spec-core/text/lines.js.map +1 -0
- package/dist/yaml.d.ts +44 -12
- package/dist/yaml.js +115 -206
- package/dist/yaml.js.map +1 -1
- package/package.json +3 -2
- package/src/vendor/spec-core/LICENSE +21 -0
- package/src/vendor/spec-core/README.md +8 -0
- package/dist/regex.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,416 @@
|
|
|
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
|
+
|
|
6
416
|
## 0.8.0
|
|
7
417
|
|
|
8
418
|
A register row took its identifier from the first thing its *title* said, not
|
package/README.md
CHANGED
|
@@ -123,12 +123,28 @@ onto four phases, chosen so one question can be answered from the phase alone:
|
|
|
123
123
|
| `draft` | `proposed`, `provisional`, `wip`, `in-review`, `experimental`, … | yes |
|
|
124
124
|
| `active` | `accepted`, `implementable`, `implemented`, `merged`, `adopted`, … | yes |
|
|
125
125
|
| `frozen` | `final`, `ratified`, `locked`, `published`, … | **no** |
|
|
126
|
-
| `retired` | `superseded`, `deprecated`, `rejected`, `withdrawn`,
|
|
126
|
+
| `retired` | `superseded`, `deprecated`, `rejected`, `withdrawn`, … | **no** |
|
|
127
127
|
|
|
128
128
|
The status is found wherever your team writes it — front matter (`status:`,
|
|
129
129
|
`state:`, `stage:`), a `## Status` section, `**Accepted** (2026-03-01) ✅`, or a
|
|
130
130
|
`docs/adr/archive/` directory. `Accepted, later superseded by ADR-0009` is
|
|
131
|
-
retired: retirement is terminal, so a retirement word anywhere wins.
|
|
131
|
+
retired: retirement is terminal, so a retirement word anywhere wins. Front
|
|
132
|
+
matter is read as YAML reads it, and a value YAML would not read -
|
|
133
|
+
`status: Superseded by ADR-9: see notes`, where a plain value cannot hold `: ` -
|
|
134
|
+
is left out rather than guessed at. Every run says how many statuses, ids and
|
|
135
|
+
relations it left out that way, and `--verbose` says where and why: quote the
|
|
136
|
+
value and it is read.
|
|
137
|
+
|
|
138
|
+
`archived` is none of the four. It is what spec-brief writes on a round of work
|
|
139
|
+
it has closed, and every spec-* tool reads it as a **record**: depending on it
|
|
140
|
+
is normal, its unticked boxes are not anyone's work, its links are still
|
|
141
|
+
checked, and new work handed to it is still a ghost handover. See
|
|
142
|
+
[Journals, changelogs and minutes](#journals-changelogs-and-minutes). Unlike a
|
|
143
|
+
journal, it still answers for what it claims: a supersession it declares, a
|
|
144
|
+
cycle it closes and a link to itself are reported as they would be anywhere.
|
|
145
|
+
To retire a decision, say `superseded` or `deprecated` -
|
|
146
|
+
`archived, superseded by ADR-0009` is retired - and a document under an
|
|
147
|
+
`archive/` directory that says nothing about itself is still retired.
|
|
132
148
|
|
|
133
149
|
An unrecognised status becomes `unknown`, and rules treat it permissively. It
|
|
134
150
|
never guesses.
|
|
@@ -399,10 +415,11 @@ against a fifty-four-character title takes `RegExp` 103 seconds and this 13
|
|
|
399
415
|
microseconds. It reads the usual syntax minus backreferences and lookaround,
|
|
400
416
|
which are not regular; both are refused when the selector is read, with the
|
|
401
417
|
character pointed at. Globs - the patterns to check, `--ignore`, `--ignore-ref`,
|
|
402
|
-
`--history` - are matched by
|
|
403
|
-
in it is
|
|
404
|
-
|
|
405
|
-
|
|
418
|
+
`--history` - are matched by an automaton too, because a glob with three stars
|
|
419
|
+
in it is enough to keep `RegExp` busy for two minutes over a long reference
|
|
420
|
+
target. See [ADR-0017](docs/adr/0017-a-predicate-must-finish.md). Both automata
|
|
421
|
+
are spec-core's, the library the spec-* tools share, copied into this package
|
|
422
|
+
rather than installed.
|
|
406
423
|
|
|
407
424
|
On a register, a region answers its file's front matter for every descriptive
|
|
408
425
|
key — `fm.owner` on a decision inside a register is the register's owner — while
|
|
@@ -508,7 +525,10 @@ had:
|
|
|
508
525
|
{ "historyPatterns": ["**/JOURNAL_*.md", "archive/**"] }
|
|
509
526
|
```
|
|
510
527
|
|
|
511
|
-
or, for one file, `<!-- @spec-history -->` at the top of it.
|
|
528
|
+
or, for one file, `<!-- @spec-history -->` at the top of it. A brief whose
|
|
529
|
+
status is `archived` is a record already, with nothing declared, though not a
|
|
530
|
+
log: the supersessions, cycles and self-references it declares are still
|
|
531
|
+
checked.
|
|
512
532
|
|
|
513
533
|
| | |
|
|
514
534
|
| --- | --- |
|
|
@@ -599,9 +619,34 @@ without drifting, because the `partialFingerprints` spec-graph writes are the
|
|
|
599
619
|
same identity a baseline is keyed on — the rule, the specification, and the
|
|
600
620
|
citation, with no line number in it (ADR-0012).
|
|
601
621
|
|
|
602
|
-
`--format
|
|
603
|
-
|
|
604
|
-
|
|
622
|
+
On GitLab, `--format gitlab` writes the Code Quality report a merge request
|
|
623
|
+
reads, and the merge request shows which findings it introduced and which it
|
|
624
|
+
resolved:
|
|
625
|
+
|
|
626
|
+
```yaml
|
|
627
|
+
spec-graph:
|
|
628
|
+
script:
|
|
629
|
+
- npx spec-graph --format gitlab > gl-code-quality-report.json
|
|
630
|
+
artifacts:
|
|
631
|
+
when: always
|
|
632
|
+
reports:
|
|
633
|
+
codequality: gl-code-quality-report.json
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Each finding is an issue with its rule as `check_name`, its message and hint as
|
|
637
|
+
`description`, and its file and line as `location`. Severities map as `error`
|
|
638
|
+
to `critical` - or `major` when only `--strict` made it an error - `warn` to
|
|
639
|
+
`minor` and `info` to `info`. The `fingerprint` is a SHA-256 of the rule, the
|
|
640
|
+
specification and what within it - a citation's target, or the document at the
|
|
641
|
+
other end - which is what a baseline counts by. There is no line in it, so a
|
|
642
|
+
finding a paragraph moved is not reported as new, and no message, so ticking
|
|
643
|
+
one of three open boxes does not make an `orphaned-obligation` new either; the
|
|
644
|
+
second of two findings identical in all three is told apart by its order. `when: always` keeps the report when the job fails, which
|
|
645
|
+
it does, with exit `1`, whenever there is an error to report.
|
|
646
|
+
|
|
647
|
+
`--format json` is the machine-readable output to parse if you are building
|
|
648
|
+
something of your own: it carries the baseline note, the summary and the
|
|
649
|
+
per-file detail that SARIF and Code Quality have nowhere to put.
|
|
605
650
|
|
|
606
651
|
**There is no `--watch`.** A full run on this repository takes 60 ms, so the
|
|
607
652
|
loop that would justify a resident process is one line of shell, and it belongs
|
|
@@ -661,6 +706,39 @@ all — a silently ignored `ignoreReference` is a configuration that looks appli
|
|
|
661
706
|
and is not. `--no-config` checks on defaults instead, and the exit code then
|
|
662
707
|
says which run it was.
|
|
663
708
|
|
|
709
|
+
### Patterns
|
|
710
|
+
|
|
711
|
+
The patterns to check, `--ignore`, `--history` and `historyPatterns` are the
|
|
712
|
+
glob dialect every spec-* tool reads, so a scope means the same to spec-graph
|
|
713
|
+
as to the tool that wrote it. They are matched against the whole
|
|
714
|
+
repository-relative path, **case-sensitively on every host** — a baseline
|
|
715
|
+
recorded on a Windows checkout holds in Linux CI:
|
|
716
|
+
|
|
717
|
+
| pattern | matches |
|
|
718
|
+
| --- | --- |
|
|
719
|
+
| `docs/**/*.md` | Markdown anywhere under `docs`; `**` is whole directories, none or more |
|
|
720
|
+
| `docs/**.md` | refused: `**` is whole directories or nothing, so write `docs/**/*.md` or `docs/*.md` |
|
|
721
|
+
| `docs` | a file called `docs`, or everything under the directory |
|
|
722
|
+
| `docs/` | everything under `docs`, and not `docs` itself |
|
|
723
|
+
| `{docs,specs}` | two literals, so both directories and what they hold |
|
|
724
|
+
| `adr/[0-9]*.md`, `adr/[!0-9]*.md` | a class, and a negated one; `[^0-9]` negates too |
|
|
725
|
+
| `!docs/drafts/**` | takes back what an earlier pattern matched; the last to match wins |
|
|
726
|
+
| `docs\adr\*.md` | the same as `docs/adr/*.md`: a `\` is a separator |
|
|
727
|
+
|
|
728
|
+
A class never matches a `/`, and a pattern that cannot mean a path under the
|
|
729
|
+
root — an unclosed `[` or `{`, `..`, a lone `.`, an empty one — stops the run
|
|
730
|
+
with exit `2` and names the pattern, wherever it was written. So does an
|
|
731
|
+
extended glob, `+(a|b)`: write `{a,b}`. A parenthesis with no `|` in its group
|
|
732
|
+
is itself, so `C++(notes).md` names that file. A `..` typed below the root,
|
|
733
|
+
`spec-graph "../*.md"` from `docs/deep`, is resolved against where you typed
|
|
734
|
+
it. A bare `--ignore` name, `--ignore drafts`, still prunes that directory at
|
|
735
|
+
any depth.
|
|
736
|
+
|
|
737
|
+
`--ignore-ref` is matched against reference targets rather than paths: a bare
|
|
738
|
+
pattern names one target exactly, case is ignored on every host, a `\` escapes
|
|
739
|
+
the next character, and `.` and `..` are the text of the link. See
|
|
740
|
+
[ADR-0022](docs/adr/0022-globs-are-the-family-path-dialect.md).
|
|
741
|
+
|
|
664
742
|
### Family rules
|
|
665
743
|
|
|
666
744
|
Every specification cites RFC 2119. In a repository that also keeps its own
|
|
@@ -720,7 +798,8 @@ spec-graph diff <before> <after> Compare two JSON graph exports.
|
|
|
720
798
|
--record-baseline <f> Write today's findings as accepted debt, exit 0
|
|
721
799
|
--ratchet Also fail when a baseline entry no longer occurs
|
|
722
800
|
--no-config Ignore .spec-graph.json and the package.json key
|
|
723
|
-
--format <fmt> human | json | sarif (check) |
|
|
801
|
+
--format <fmt> human | json | sarif (check) | gitlab (check)
|
|
802
|
+
| markdown (check, diff)
|
|
724
803
|
--graph-format <fmt> dot | mermaid | json
|
|
725
804
|
--documents-only Hide items; their relations lift onto their documents
|
|
726
805
|
--rule <id>=<severity> error | warn | info | off (repeatable)
|
|
@@ -838,7 +917,7 @@ exported. Reporters, editor extensions and custom rules are all first-class.
|
|
|
838
917
|
|
|
839
918
|
## Design
|
|
840
919
|
|
|
841
|
-
Twenty ADRs, which `spec-graph` validates on every CI run:
|
|
920
|
+
Twenty-three ADRs, which `spec-graph` validates on every CI run:
|
|
842
921
|
|
|
843
922
|
- [ADR-0001 — A hand-written Markdown scanner](docs/adr/0001-hand-written-markdown-scanner.md)
|
|
844
923
|
- [ADR-0002 — A four-phase lifecycle lattice](docs/adr/0002-lifecycle-lattice.md)
|
|
@@ -860,17 +939,23 @@ Twenty ADRs, which `spec-graph` validates on every CI run:
|
|
|
860
939
|
- [ADR-0018 — The configuration file is the root](docs/adr/0018-the-configuration-file-is-the-root.md)
|
|
861
940
|
- [ADR-0019 — The sweep runs in shards](docs/adr/0019-the-sweep-runs-in-shards.md)
|
|
862
941
|
- [ADR-0020 — A diff names only what it can tell apart](docs/adr/0020-a-diff-names-what-it-can-tell-apart.md)
|
|
942
|
+
- [ADR-0021 — Releases are published by CI, with provenance](docs/adr/0021-releases-are-published-by-ci.md)
|
|
943
|
+
- [ADR-0022 — Globs are the family's path dialect](docs/adr/0022-globs-are-the-family-path-dialect.md)
|
|
944
|
+
- [ADR-0023 — The scanner is the family's](docs/adr/0023-the-scanner-is-the-familys.md)
|
|
863
945
|
|
|
864
946
|
**Zero runtime dependencies.** Node 22+, native ESM, TypeScript strict with
|
|
865
947
|
`noUncheckedIndexedAccess` and `exactOptionalPropertyTypes`. The Markdown
|
|
866
948
|
scanner, the front-matter reader, the glob matcher, the query parser and the
|
|
867
|
-
regular-expression matcher behind `~=` are all written here
|
|
868
|
-
|
|
869
|
-
|
|
949
|
+
regular-expression matcher behind `~=` are all written here or in
|
|
950
|
+
[spec-core](https://github.com/DescentVTT/spec-core), the library the spec-*
|
|
951
|
+
tools share, whose code is copied into `src/vendor/` and checked by hash rather
|
|
952
|
+
than installed. So the whole package is auditable in an afternoon — and nothing
|
|
953
|
+
in the pipeline can take longer than its input.
|
|
870
954
|
|
|
871
|
-
**Verified, not just covered.**
|
|
955
|
+
**Verified, not just covered.** 1,053 tests; 96.8% statement and 98.2% line
|
|
872
956
|
coverage. Coverage says a line ran, so the suite is also held to a mutation
|
|
873
|
-
score -
|
|
957
|
+
score - 76.73% over 8,400 mutants at 0.9.0, measured incrementally in CI -
|
|
958
|
+
because a vocabulary entry or a boundary
|
|
874
959
|
condition can be weakened by an ordinary-looking refactor without a single test
|
|
875
960
|
going red. [ADR-0007](docs/adr/0007-mutation-testing.md) is straight about what
|
|
876
961
|
that number is and is not.
|