@descent-vtt/spec-graph 0.4.0 → 0.6.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 +355 -0
- package/README.md +89 -6
- package/dist/cli.d.ts +14 -5
- package/dist/cli.js +253 -63
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +33 -0
- package/dist/config.js +40 -1
- package/dist/config.js.map +1 -1
- package/dist/diff.d.ts +120 -0
- package/dist/diff.js +404 -0
- package/dist/diff.js.map +1 -0
- package/dist/directives.d.ts +18 -1
- package/dist/directives.js +79 -0
- package/dist/directives.js.map +1 -1
- package/dist/extract.d.ts +20 -0
- package/dist/extract.js +43 -8
- package/dist/extract.js.map +1 -1
- package/dist/glob.d.ts +34 -0
- package/dist/glob.js +51 -4
- package/dist/glob.js.map +1 -1
- package/dist/index.d.ts +11 -9
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/regex.d.ts +76 -0
- package/dist/regex.js +822 -0
- package/dist/regex.js.map +1 -0
- package/dist/report.d.ts +29 -0
- package/dist/report.js +111 -0
- package/dist/report.js.map +1 -1
- package/dist/resolve.js +11 -0
- package/dist/resolve.js.map +1 -1
- package/dist/rules.d.ts +13 -0
- package/dist/rules.js +26 -0
- package/dist/rules.js.map +1 -1
- package/dist/select.d.ts +8 -0
- package/dist/select.js +33 -14
- package/dist/select.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,361 @@
|
|
|
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.6.0
|
|
7
|
+
|
|
8
|
+
A diff of two graph exports that names only the changes it can tell apart,
|
|
9
|
+
every glob moved onto the automaton `~=` already ran on, and a directive that
|
|
10
|
+
annotates one item where it used to annotate every item in reach. The mutation
|
|
11
|
+
figure is lower than 0.5.0's, and it is the first a release has carried from
|
|
12
|
+
tests that no longer race each other on disk.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
**A glob can no longer keep a run busy for minutes.** 0.5.0 moved `~=` onto an
|
|
17
|
+
automaton that cannot backtrack and left globs on `RegExp`, on the argument
|
|
18
|
+
that a glob has no nested quantifiers. Nobody had timed it. Stars that are not
|
|
19
|
+
nested still divide a failing subject between them every possible way:
|
|
20
|
+
|
|
21
|
+
| glob | subject | `RegExp` | now |
|
|
22
|
+
| --- | --- | ---: | ---: |
|
|
23
|
+
| `**/*-*-*-*.md` | a 643-character hyphenated file name | 2.3s | 0.2ms |
|
|
24
|
+
| `*-*-*-x` | a 10,000-character reference target | 120s | 0.9ms |
|
|
25
|
+
|
|
26
|
+
The second is the one that matters: `--ignore-ref` and `ignoreReferences` are
|
|
27
|
+
matched against targets read out of documents, which nothing limits the length
|
|
28
|
+
of. Every glob - the patterns to check, `--ignore`, `--ignore-ref`, `--history` -
|
|
29
|
+
now runs on the same automaton as `~=`, verified against the `RegExp` it
|
|
30
|
+
replaces on every glob and path in a differential corpus. See
|
|
31
|
+
[ADR-0017](docs/adr/0017-a-predicate-must-finish.md).
|
|
32
|
+
|
|
33
|
+
- **An invalid glob names itself.** `docs/{a` used to report
|
|
34
|
+
`Invalid regular expression: /^docs\/(?:a$/i: Unterminated group`, about a
|
|
35
|
+
parenthesis nobody typed. It now reports `invalid glob "docs/{a": unclosed "{"`.
|
|
36
|
+
- **`--ignore-ref` reads case the same way on every platform.** It lower-cased
|
|
37
|
+
both sides, and on Windows alone also folded case the way the `i` flag does,
|
|
38
|
+
which made a handful of distinct characters - the micro sign and the Greek
|
|
39
|
+
mu - one target there and two everywhere else.
|
|
40
|
+
- **A `@spec-item` directive annotates one item.** Each item used to look for
|
|
41
|
+
its own, so a directive was found by every item within 200 characters below
|
|
42
|
+
it and by every item enclosing it. The question after a declared one took the
|
|
43
|
+
same id and vanished from the graph without a word, and a parent could take
|
|
44
|
+
its child's directive in place of its own. A directive now binds to the item
|
|
45
|
+
directly below it, with nothing but blank lines and comments between, or else
|
|
46
|
+
to the innermost item it is written on or indented under. An id two items
|
|
47
|
+
still claim, by declaring it twice or by declaring another item's number, is
|
|
48
|
+
a parse problem, and the first item keeps it. `bindItemDirectives` is
|
|
49
|
+
exported; `directiveFor` is unchanged and still exported.
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
**`spec-graph diff <before.json> <after.json>` says what a change did to the
|
|
54
|
+
decisions:** documents added, removed, moved or accepted, relations added or
|
|
55
|
+
removed, and obligations resolved or reopened. It names only what it can tell
|
|
56
|
+
apart. An obligation's id is its position in its section, so one inserted
|
|
57
|
+
question renumbers every question below it, and a diff keyed on those ids would
|
|
58
|
+
report a closed question as reopened when nobody touched it. An obligation is
|
|
59
|
+
paired only by a declared id, or by its document, section and title when nothing
|
|
60
|
+
else shares them; anything unpaired is reported as having appeared or
|
|
61
|
+
disappeared. Human, `--format json` or `--format markdown`, and exit 0 either
|
|
62
|
+
way. See [ADR-0020](docs/adr/0020-a-diff-names-what-it-can-tell-apart.md).
|
|
63
|
+
|
|
64
|
+
- The JSON graph export names its `generator`, so a diff can warn when two
|
|
65
|
+
exports came from different versions, and each item says whether its id was
|
|
66
|
+
`declared`. Both are additions; the export is still version 1.
|
|
67
|
+
- `compileGlob(pattern, { ignoreCase })`, the matcher the walk uses, and an
|
|
68
|
+
`ignoreCase` option on `compilePattern` for a pattern that must respect case.
|
|
69
|
+
`globToRegExp` is unchanged and still exported.
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
One behaviour moves, and it can turn a run that passed into one that fails.
|
|
74
|
+
|
|
75
|
+
- **A `@spec-item` directive with prose, a heading or code between it and the
|
|
76
|
+
item below binds to nothing.** It used to reach any item starting within 200
|
|
77
|
+
characters. Its id, state and title no longer apply: the item takes its
|
|
78
|
+
numbered id, a `state="moot"` stops closing it, and a link to the declared id
|
|
79
|
+
is a `broken-reference`. Move the directive down to the line above the item.
|
|
80
|
+
|
|
81
|
+
### Verified
|
|
82
|
+
|
|
83
|
+
| | |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `npm run lint` | pass |
|
|
86
|
+
| `npm test` | 921 passing, 23 files |
|
|
87
|
+
| `npm run selfcheck` | pass, over 23 documents and 184 relations |
|
|
88
|
+
| hosted full sweep, which governs | **75.25%** over 10,519 mutants, four shards of 22 to 33 minutes |
|
|
89
|
+
| `break` | unchanged at 70 |
|
|
90
|
+
|
|
91
|
+
0.5.0's 76.83% is not the figure to compare with. About three points of it were
|
|
92
|
+
kills no assertion made: tests writing to fixed paths deleted each other's files
|
|
93
|
+
under Stryker's parallel workers. Race-free, the sweep read 73.42% before
|
|
94
|
+
`spec-graph diff` and 75.25% with this release: `diff.ts` at 98.35%, and
|
|
95
|
+
`directives.ts` from 66.88% to 78.01%. Lose every timeout and it reads 71.89%.
|
|
96
|
+
See [ADR-0007](docs/adr/0007-mutation-testing.md) and
|
|
97
|
+
[ADR-0019](docs/adr/0019-the-sweep-runs-in-shards.md).
|
|
98
|
+
|
|
99
|
+
The sweep after the directive fix moved `extract.ts` down 0.18 points, among the
|
|
100
|
+
usual movement in files nobody touched, and this one was real. Four mutants
|
|
101
|
+
that hand an item to every specification in a register had gone from killed to
|
|
102
|
+
surviving: resolution now drops the duplicates they create, and the test that
|
|
103
|
+
caught them only counted items. It now also asserts that no problem is
|
|
104
|
+
reported.
|
|
105
|
+
|
|
106
|
+
## 0.5.0
|
|
107
|
+
|
|
108
|
+
One shipped feature turned out to have shipped a false positive, and the
|
|
109
|
+
predicate that was documented as dangerous is now an automaton that cannot
|
|
110
|
+
backtrack. Four of the ADR suite's open questions answered by building the
|
|
111
|
+
thing, seven directions declined with a reason, one deferred with its design
|
|
112
|
+
settled so it is not re-litigated.
|
|
113
|
+
|
|
114
|
+
### Fixed
|
|
115
|
+
|
|
116
|
+
**A register's decisions answer for the front matter above them.** This was a
|
|
117
|
+
false positive in the feature 0.4.0 led with, and the worst kind: confident and
|
|
118
|
+
unanswerable. A region answered *nothing* for `fm.owner`, `!=` against an absent
|
|
119
|
+
value is a mismatch, so a repository writing
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
"rules": { "owned": { "query": "document[fm.owner!=platform]" } }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
was told twice that a register saying `owner: platform` is not owned by
|
|
126
|
+
platform — with the message rendering `{0.fm.owner}` as the literal placeholder,
|
|
127
|
+
because there was nothing to put there.
|
|
128
|
+
|
|
129
|
+
A region now carries its file's front matter minus two kinds of key. A
|
|
130
|
+
**relation** key stays behind: `supersedes:` at the top of a register supersedes
|
|
131
|
+
on behalf of the register, not of each decision in it. So does a key the region
|
|
132
|
+
**answers for itself** — its identifier, status, title and aliases, which are
|
|
133
|
+
precisely the things a register exists to vary row by row.
|
|
134
|
+
[ADR-0009](docs/adr/0009-a-specification-is-a-region.md) carried this as a
|
|
135
|
+
narrowed open question and called it a gap. It was a defect, and it was found by
|
|
136
|
+
pointing the binary at a register rather than by reading the code.
|
|
137
|
+
|
|
138
|
+
Read from the other side, this can *add* findings: a rule phrased positively,
|
|
139
|
+
`document[fm.deprecated]`, now matches a register's decisions as well as the
|
|
140
|
+
register. That is the same correction - the decisions really are described by
|
|
141
|
+
the front matter above them - and worth knowing before upgrading a repository
|
|
142
|
+
that keeps registers and writes rules about front matter.
|
|
143
|
+
|
|
144
|
+
### Added
|
|
145
|
+
|
|
146
|
+
**`~=` is matched by an automaton that cannot backtrack.**
|
|
147
|
+
[ADR-0016](docs/adr/0016-a-query-needs-a-sentence.md) documented this hazard
|
|
148
|
+
with `(a+)+$`, which nobody writes, and priced the fix at "worth it only if a
|
|
149
|
+
real repository hangs". Both were wrong. `^([A-Za-z0-9_]+[ ]?)+$` is what
|
|
150
|
+
somebody writes to check that a title is words separated by single spaces:
|
|
151
|
+
|
|
152
|
+
| subject | `RegExp` | now |
|
|
153
|
+
| --- | ---: | ---: |
|
|
154
|
+
| `the quick brown fox jumps over the lazy dog!` | 0.9s | 11us |
|
|
155
|
+
| `... over the lazy dog and!` | 5.4s | 12us |
|
|
156
|
+
| `... over the lazy dog and keeps!` | 103s | 13us |
|
|
157
|
+
|
|
158
|
+
A word of k letters can be cut into pieces 2^(k-1) ways and a backtracking
|
|
159
|
+
engine tries the product, so a sixty-character title does not finish this year —
|
|
160
|
+
unattended, on every build, against a corpus whose titles the author of the rule
|
|
161
|
+
has not read. The work is now exactly O(pattern x subject), and **no stage of
|
|
162
|
+
this pipeline can take longer than its input.**
|
|
163
|
+
|
|
164
|
+
It costs a dialect. Backreferences and lookaround are not regular, so they are
|
|
165
|
+
refused while the selector is read, with the character pointed at — as are three
|
|
166
|
+
things `RegExp` accepts and should not: an unknown letter escape, which `RegExp`
|
|
167
|
+
reads as the letter, so a pattern meant as an anchor silently matches a capital
|
|
168
|
+
A; a Unicode property escape, which needs a table this package will not carry;
|
|
169
|
+
and octal escapes. Everything else works, and a pattern that does not parse is
|
|
170
|
+
now a usage error rather than a pattern that silently matches nothing.
|
|
171
|
+
|
|
172
|
+
Verified against the engine it replaces: **1.33 million pattern-subject pairs,
|
|
173
|
+
zero disagreements**, 19,000 of them committed as a gate. It earned that twice
|
|
174
|
+
over, on one clause of the language specification read backwards in two
|
|
175
|
+
different places - the word characters `\b` recognises, and whether a character
|
|
176
|
+
whose upper case is ASCII may match `[A-Z]`. The second was found by the
|
|
177
|
+
mutation score pointing at a corpus with a blind spot in it rather than at a
|
|
178
|
+
test. See [ADR-0017](docs/adr/0017-a-predicate-must-finish.md) and
|
|
179
|
+
[ADR-0007](docs/adr/0007-mutation-testing.md).
|
|
180
|
+
|
|
181
|
+
**Configuration is discovered upward, and the file holding it is the root.**
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
cd packages/auth && spec-graph check # the repository's rules, the
|
|
185
|
+
# repository's patterns, the
|
|
186
|
+
# repository's paths
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
[ADR-0010](docs/adr/0010-configuration-belongs-to-the-repository.md) worried
|
|
190
|
+
that discovery would make a run depend on where it started. Reading the working
|
|
191
|
+
directory already did, and silently: a check from a package directory found no
|
|
192
|
+
configuration, ran no project rules, used the default include patterns, and
|
|
193
|
+
printed a verdict in the same shape as the real one. Because the directory
|
|
194
|
+
holding the file becomes the root, and every path here is relative to the root,
|
|
195
|
+
a run from anywhere inside the repository now produces **byte-identical** output
|
|
196
|
+
to a run from the top. The walk stops at the repository — a directory holding
|
|
197
|
+
`.git` — so a stray file in a home directory cannot reach it. `--root` names the
|
|
198
|
+
root yourself and turns discovery off, and a path typed on the command line
|
|
199
|
+
stays relative to where you typed it. See
|
|
200
|
+
[ADR-0018](docs/adr/0018-the-configuration-file-is-the-root.md).
|
|
201
|
+
|
|
202
|
+
**`spec-graph query project:<rule>` runs a registered rule by name.** The rule
|
|
203
|
+
is already compiled by the time the command runs, and the alternative was
|
|
204
|
+
copying its selector back out of the configuration file by hand. Several
|
|
205
|
+
selectors are deduped the way the check dedupes them, so this prints the set the
|
|
206
|
+
check reports on; `--verbose` names them, which is the question a team
|
|
207
|
+
calibrating a convention is actually asking. Built-in rules are deliberately not
|
|
208
|
+
addressable this way: a project rule *is* its selector, while a built-in is a
|
|
209
|
+
selector plus judgement, and printing a set that differs from the findings would
|
|
210
|
+
invite the wrong conclusion.
|
|
211
|
+
|
|
212
|
+
**`spec-graph rules <rule-id> --explain` names the ADR that decided it.** A rule
|
|
213
|
+
that fires is a claim about somebody's repository, and the reasoning behind the
|
|
214
|
+
claim was in a document nobody could find from the message. The table holds a
|
|
215
|
+
pointer rather than a paraphrase, because a second copy of the reasoning would
|
|
216
|
+
drift from the first — and a test holds every pointer to a file this corpus
|
|
217
|
+
checks, so a renamed ADR breaks the link in the same run that breaks the
|
|
218
|
+
reference.
|
|
219
|
+
|
|
220
|
+
**`--format markdown`** writes the same facts as a GitHub-flavoured table, for
|
|
221
|
+
`$GITHUB_STEP_SUMMARY` or a pull-request comment:
|
|
222
|
+
|
|
223
|
+
```yaml
|
|
224
|
+
- run: npx spec-graph --format markdown >> "$GITHUB_STEP_SUMMARY"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
SARIF puts a finding on the line that caused it, which is where somebody fixing
|
|
228
|
+
one wants it, and says nothing at all to the person deciding whether to merge.
|
|
229
|
+
This leads with the verdict, counts the corpus, gives every finding its hint,
|
|
230
|
+
and *names* the stale baseline entries rather than counting them — on a pull
|
|
231
|
+
request a count is the one thing a reader cannot act on. No environment
|
|
232
|
+
detection: a format that changed because a variable was set would be a format
|
|
233
|
+
nobody controls.
|
|
234
|
+
|
|
235
|
+
### Changed
|
|
236
|
+
|
|
237
|
+
Four behaviours move, none of them a rule. Three can turn a run that passed
|
|
238
|
+
into a usage error, which is the point in each case; the fourth changes what a
|
|
239
|
+
run from a subdirectory reports.
|
|
240
|
+
|
|
241
|
+
- **A `~=` pattern that does not parse is a usage error**, with the character
|
|
242
|
+
pointed at, rather than a pattern that matches nothing. The old silence was
|
|
243
|
+
indistinguishable from a rule that ran and found none.
|
|
244
|
+
- **A `~=` pattern using a backreference, lookaround, `\p{...}`, an octal
|
|
245
|
+
escape or an unknown letter escape is refused**, for the reasons above. If
|
|
246
|
+
you have one, the message says which construct and why; `^=`, `$=` and `*=`
|
|
247
|
+
cover the fixed-prefix, suffix and substring cases without a pattern at all.
|
|
248
|
+
- **A run from a subdirectory now finds the repository's configuration**, and
|
|
249
|
+
reports repository-relative paths because the configuration's directory is
|
|
250
|
+
the root. A script that read paths out of a nested run will see them change.
|
|
251
|
+
`--root` keeps the old behaviour exactly.
|
|
252
|
+
- **`spec-graph rules <word>` reads the word as a rule id** and reports one
|
|
253
|
+
that names nothing. It used to be collected as an include pattern, which the
|
|
254
|
+
command has no use for.
|
|
255
|
+
|
|
256
|
+
### Declined
|
|
257
|
+
|
|
258
|
+
Each with its reasoning in the ADR that owns the question, rather than in a
|
|
259
|
+
roadmap nobody reads:
|
|
260
|
+
|
|
261
|
+
- **An MCP server.** The capability is already here — `analyseSources()` takes
|
|
262
|
+
text, `--format json` is the answer — so what is left is a protocol
|
|
263
|
+
implementation, hand-written to keep the dependency count at zero, tracking a
|
|
264
|
+
specification that is still moving, to expose three tools that are three shell
|
|
265
|
+
commands an agent can already run.
|
|
266
|
+
- **A language server.** The right shape for editor feedback and a second
|
|
267
|
+
package's worth of work, and SARIF already reaches the problems pane. Two of
|
|
268
|
+
the three hard parts are done — `analyseSources()` needs no file on disk, and
|
|
269
|
+
every finding carries a span — so it should start when somebody wants hover
|
|
270
|
+
and go-to-definition, not as a way of delivering diagnostics that already
|
|
271
|
+
arrive.
|
|
272
|
+
- **A `--watch` daemon.** Declined again, with a better reason than last time:
|
|
273
|
+
the incremental half of it cannot work. Whether a bare `ADR-0099` in prose is
|
|
274
|
+
a citation depends on which families exist elsewhere, and a near-miss
|
|
275
|
+
suggestion is computed against every sibling — touch one file and the honest
|
|
276
|
+
set to recompute is all of them. Which is fine, because the pipeline is a pure
|
|
277
|
+
function and 60 ms long. And then the resident index buys nothing, and the
|
|
278
|
+
feature is a loop.
|
|
279
|
+
- **Code frames in the terminal.** The report already prints
|
|
280
|
+
`file:line:column`, which every terminal in use turns into a click, and
|
|
281
|
+
`renderMatch` already prints the shape of a multi-hop path.
|
|
282
|
+
- **Provenance and attestation metadata.** A timestamp in the output means no
|
|
283
|
+
two reports of one corpus are ever byte-identical, which is the property every
|
|
284
|
+
format here is built to have. The commit is something CI already knows.
|
|
285
|
+
- **An interactive SVG.** A layout engine or a vendored library is a runtime
|
|
286
|
+
dependency wherever it sits in the tarball, and its bytes depend on a layout
|
|
287
|
+
pass. `dot` and `mermaid` hand layout to tools built for it, and both are
|
|
288
|
+
text. The other half of that ask - phase styling and typed edges in the
|
|
289
|
+
Mermaid export - has been there since the first release. What is left is a
|
|
290
|
+
`subgraph` per directory, which changes the shape of an export somebody's
|
|
291
|
+
documentation build is parsing, to group a corpus that is one directory in
|
|
292
|
+
most repositories.
|
|
293
|
+
- **Definition lists as a register form.** Two unrelated syntaxes behind one
|
|
294
|
+
name: raw `<dl>`, which the scanner leaves as prose deliberately because
|
|
295
|
+
ADR-0001's masking guarantee is what keeps a code fence quiet, and the
|
|
296
|
+
PHP-Markdown-Extra form, which is not CommonMark and which no corpus here
|
|
297
|
+
contains. Either would need a new parser for a convention nobody has written
|
|
298
|
+
down.
|
|
299
|
+
|
|
300
|
+
One is deferred rather than declined, with half its design recorded so it is not
|
|
301
|
+
re-litigated: a **relational diff** between two states of the graph, taken
|
|
302
|
+
between two `--graph-format json` exports rather than between two git revisions,
|
|
303
|
+
because revisions mean spawning a binary underneath a pipeline that is a pure
|
|
304
|
+
function of text. See
|
|
305
|
+
[ADR-0015](docs/adr/0015-feedback-goes-where-the-tools-already-look.md).
|
|
306
|
+
|
|
307
|
+
### Verified
|
|
308
|
+
|
|
309
|
+
| | |
|
|
310
|
+
|---|---|
|
|
311
|
+
| `npm run lint` | pass |
|
|
312
|
+
| `npm test` | 833 passing, 21 files |
|
|
313
|
+
| `npm run selfcheck` | pass, over 21 documents and 159 relations |
|
|
314
|
+
| `npm run test:mutation` | 80.54% over 9,697 mutants, 74m27s |
|
|
315
|
+
| hosted full run, which governs | **76.83%**, 168m06s |
|
|
316
|
+
| hosted incremental run, same commit | 76.43%, 64m27s |
|
|
317
|
+
| `break` | unchanged at 70 |
|
|
318
|
+
|
|
319
|
+
79.41% over 8,585 mutants at 0.4.0 against 80.54% over 9,697 here, on the same
|
|
320
|
+
machine: 1,112 more mutants and a point higher. The new code carries itself -
|
|
321
|
+
`regex.ts` at **90.61%**, `config.ts` at 92.37%, `project-rules.ts` still at
|
|
322
|
+
99.50% - and lose all 343 timeouts and the figure reads 77.00%.
|
|
323
|
+
|
|
324
|
+
The first full run of this release read 80.35%, and its survivor list is where
|
|
325
|
+
the last defect came from. A mutant on the line stepping past `^` in a negated
|
|
326
|
+
class survived 1.33 million differential comparisons, because the corpus only
|
|
327
|
+
ever tested classes against two-character subjects; one-character subjects
|
|
328
|
+
killed it and then caught `[A-Z]` matching U+017F. `report.ts` went from 66.30%
|
|
329
|
+
to 70.84% on the same pass, once the Markdown tests asserted the table's
|
|
330
|
+
structure - rectangular rows, a blank line before every block, every summary
|
|
331
|
+
number present - rather than fragments of its text. What remains there is
|
|
332
|
+
mostly the older formatters.
|
|
333
|
+
|
|
334
|
+
Between those two runs `rules.ts` moved from 79.42% to 76.02% and
|
|
335
|
+
`directives.ts` from 77.92% to 74.03%, with neither file touched: the tests added
|
|
336
|
+
in between changed which tests `perTest` believes cover which mutants.
|
|
337
|
+
[ADR-0007](docs/adr/0007-mutation-testing.md) has recorded that shape for two
|
|
338
|
+
releases, and it is why a per-file drop is a question and not an answer.
|
|
339
|
+
|
|
340
|
+
The hosted number is the one that governs, because it is where the build fails:
|
|
341
|
+
**76.83%**, up from 75.40% at 0.4.0. Lose every timeout and it reads 73.31%, the
|
|
342
|
+
first time the pessimistic figure has cleared the guard by more than three
|
|
343
|
+
points.
|
|
344
|
+
|
|
345
|
+
The incremental run on the same commit read 0.40 points low - the third
|
|
346
|
+
observation after 3.18 high at 0.3.0 and 1.29 low at 0.4.0, and the reason
|
|
347
|
+
[ADR-0007](docs/adr/0007-mutation-testing.md) calls it a signal rather than a
|
|
348
|
+
figure. The tag's run also logged a cache save, which this entry first read as
|
|
349
|
+
the cache-key fix from 0.4.0 doing its job. *Corrected 2026-09-13:* it saved a
|
|
350
|
+
copy of the stale report it had restored, because the full step never wrote one,
|
|
351
|
+
and an entry saved under a tag cannot be restored on `main` in any case. See
|
|
352
|
+
[ADR-0007](docs/adr/0007-mutation-testing.md).
|
|
353
|
+
|
|
354
|
+
**The rebuild took 168 of its 180 minutes.** 13% more mutants than 0.4.0, and 63%
|
|
355
|
+
longer on the hosted runner against 15% longer locally. At 0.4.0's old cap of
|
|
356
|
+
120 it would have been cancelled with nothing reported. The cause is not yet
|
|
357
|
+
attributed - runner variance and the new differential test are both candidates -
|
|
358
|
+
and the choice between raising the cap again and cutting per-mutant cost is left
|
|
359
|
+
open in ADR-0007 until it is.
|
|
360
|
+
|
|
6
361
|
## 0.4.0
|
|
7
362
|
|
|
8
363
|
Ten open questions across the ADR suite, answered. Five shipped, three declined
|
package/README.md
CHANGED
|
@@ -386,6 +386,23 @@ Items inherit their document's lifecycle, so `item[phase=retired]` means what yo
|
|
|
386
386
|
expect. A query evaluates to **paths**, not endpoints, which is why a finding can
|
|
387
387
|
name both ends and the line that connects them.
|
|
388
388
|
|
|
389
|
+
`~=` is a regular expression matched by an automaton that cannot backtrack, so a
|
|
390
|
+
predicate is linear in the subject whatever the pattern — `^([A-Za-z0-9_]+[ ]?)+$`
|
|
391
|
+
against a fifty-four-character title takes `RegExp` 103 seconds and this 13
|
|
392
|
+
microseconds. It reads the usual syntax minus backreferences and lookaround,
|
|
393
|
+
which are not regular; both are refused when the selector is read, with the
|
|
394
|
+
character pointed at. Globs - the patterns to check, `--ignore`, `--ignore-ref`,
|
|
395
|
+
`--history` - are matched by the same automaton, because a glob with three stars
|
|
396
|
+
in it is
|
|
397
|
+
enough to keep `RegExp` busy for two minutes over a long reference target. See
|
|
398
|
+
[ADR-0017](docs/adr/0017-a-predicate-must-finish.md).
|
|
399
|
+
|
|
400
|
+
On a register, a region answers its file's front matter for every descriptive
|
|
401
|
+
key — `fm.owner` on a decision inside a register is the register's owner — while
|
|
402
|
+
the keys a region owns itself, its identifier and status and title, and any key
|
|
403
|
+
that declares a relation, stay on the file. See
|
|
404
|
+
[ADR-0009](docs/adr/0009-a-specification-is-a-region.md).
|
|
405
|
+
|
|
389
406
|
## Your own rules
|
|
390
407
|
|
|
391
408
|
A convention spec-graph never anticipated is a selector plus a sentence. Write
|
|
@@ -432,6 +449,12 @@ escalated by `--strict`, listed by `spec-graph rules`, exempted on journals like
|
|
|
432
449
|
every other rule about obligations, and emitted as a first-class `ruleId` in
|
|
433
450
|
SARIF, where a project rule describes itself by the selector it is.
|
|
434
451
|
|
|
452
|
+
To see what a rule matches without copying its selector back out of the file:
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
spec-graph query project:no-draft-dependency --verbose
|
|
456
|
+
```
|
|
457
|
+
|
|
435
458
|
Mistakes are reported when the file is read, not discovered as a rule that
|
|
436
459
|
quietly finds nothing:
|
|
437
460
|
|
|
@@ -460,6 +483,11 @@ Directives are ordinary HTML comments — invisible in every Markdown renderer:
|
|
|
460
483
|
A directive always wins, and the report says the state came from a directive, so
|
|
461
484
|
an override is visible rather than mysterious.
|
|
462
485
|
|
|
486
|
+
A `@spec-item` annotates one item: the one directly below it, with nothing but
|
|
487
|
+
blank lines or other comments between, or else the one it is written on or
|
|
488
|
+
indented under. An id that two items end up with is a parse problem, listed by
|
|
489
|
+
`--verbose`, and the first item keeps it.
|
|
490
|
+
|
|
463
491
|
## Journals, changelogs and minutes
|
|
464
492
|
|
|
465
493
|
A 2024 journal noting *"decision deferred to ADR-002"* is not delegating
|
|
@@ -594,6 +622,14 @@ command. spec-graph reads the first of `.spec-graph.json`,
|
|
|
594
622
|
}
|
|
595
623
|
```
|
|
596
624
|
|
|
625
|
+
The file is looked for in the working directory and then upward, as far as the
|
|
626
|
+
repository — a directory holding `.git` — and **the directory holding it becomes
|
|
627
|
+
the root**. So a run from `packages/auth` reports what a run from the top
|
|
628
|
+
reports, byte for byte, because every path here is relative to the root. The
|
|
629
|
+
nearest file wins, `--root` names the root yourself and turns discovery off, and
|
|
630
|
+
a path typed on the command line stays relative to where you typed it. See
|
|
631
|
+
[ADR-0018](docs/adr/0018-the-configuration-file-is-the-root.md).
|
|
632
|
+
|
|
597
633
|
A flag always wins over the file, and list flags **add** to it rather than
|
|
598
634
|
replacing it — a `--ignore-ref` on the command line is one more exclusion, not a
|
|
599
635
|
decision to discard what the repository already declared. `--verbose` prints
|
|
@@ -644,10 +680,15 @@ already exists in the corpus.
|
|
|
644
680
|
```text
|
|
645
681
|
spec-graph [check] [patterns...] Validate the graph. The default.
|
|
646
682
|
spec-graph query <selector> Run a selector, print matching paths.
|
|
683
|
+
spec-graph query project:<rule> Run a registered rule by its id.
|
|
647
684
|
spec-graph graph [patterns...] Export for Graphviz, Mermaid or JSON.
|
|
648
|
-
spec-graph rules [
|
|
685
|
+
spec-graph rules [rule-id] List the diagnostics; --explain adds
|
|
686
|
+
each selector and the ADR behind it.
|
|
687
|
+
spec-graph diff <before> <after> Compare two JSON graph exports.
|
|
649
688
|
|
|
650
|
-
--root <dir> Directory patterns resolve against
|
|
689
|
+
--root <dir> Directory patterns resolve against, and the root of the
|
|
690
|
+
run. Without it the config file is discovered upward and
|
|
691
|
+
its directory is the root
|
|
651
692
|
--ignore <glob> Skip paths (repeatable)
|
|
652
693
|
--ignore-ref <glob> Do not report these reference targets (repeatable)
|
|
653
694
|
--family <name> Families a bare identifier may name (repeatable)
|
|
@@ -657,7 +698,7 @@ spec-graph rules [--explain] List the diagnostics.
|
|
|
657
698
|
--record-baseline <f> Write today's findings as accepted debt, exit 0
|
|
658
699
|
--ratchet Also fail when a baseline entry no longer occurs
|
|
659
700
|
--no-config Ignore .spec-graph.json and the package.json key
|
|
660
|
-
--format <fmt> human | json | sarif
|
|
701
|
+
--format <fmt> human | json | sarif (check) | markdown (check, diff)
|
|
661
702
|
--graph-format <fmt> dot | mermaid | json
|
|
662
703
|
--documents-only Hide items; their relations lift onto their documents
|
|
663
704
|
--rule <id>=<severity> error | warn | info | off (repeatable)
|
|
@@ -680,6 +721,19 @@ rather than a silent success.
|
|
|
680
721
|
run: npx spec-graph "docs/**/*.md" --format json > spec-graph.json
|
|
681
722
|
```
|
|
682
723
|
|
|
724
|
+
`--format markdown` writes the same facts as a table, for the place reviewers
|
|
725
|
+
actually look:
|
|
726
|
+
|
|
727
|
+
```yaml
|
|
728
|
+
- name: Summarise the specification graph
|
|
729
|
+
if: always()
|
|
730
|
+
run: npx spec-graph --format markdown >> "$GITHUB_STEP_SUMMARY"
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
It leads with the verdict, counts the corpus, gives every finding its hint, and
|
|
734
|
+
*names* the baseline entries that no longer occur rather than counting them —
|
|
735
|
+
on a pull request a count is the one thing a reader cannot act on.
|
|
736
|
+
|
|
683
737
|
The JSON report is versioned, flat, and carries start and end positions for
|
|
684
738
|
every finding, so a bot can annotate a diff:
|
|
685
739
|
|
|
@@ -704,6 +758,28 @@ every finding, so a bot can annotate a diff:
|
|
|
704
758
|
}
|
|
705
759
|
```
|
|
706
760
|
|
|
761
|
+
A check says what is wrong. `spec-graph diff` says what a pull request changed:
|
|
762
|
+
documents added, removed, moved or accepted, relations added or removed, and
|
|
763
|
+
obligations resolved or reopened. It compares two exports made by the same
|
|
764
|
+
binary, so CI makes one from the base branch and one from the pull request:
|
|
765
|
+
|
|
766
|
+
```yaml
|
|
767
|
+
- run: git fetch --depth 1 origin "$GITHUB_BASE_REF" && git worktree add ../base FETCH_HEAD
|
|
768
|
+
- run: npx spec-graph graph --root ../base --graph-format json > base.json
|
|
769
|
+
- run: npx spec-graph graph --graph-format json > head.json
|
|
770
|
+
- run: npx spec-graph diff base.json head.json --format markdown >> "$GITHUB_STEP_SUMMARY"
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
It names only what it can tell apart. A document keeps its id through a rename,
|
|
774
|
+
so a moved document reads as moved. An obligation's id is its position in its
|
|
775
|
+
section, so inserting a question renumbers every question below it; an
|
|
776
|
+
obligation is therefore paired only by an id its author declared, or by its
|
|
777
|
+
document, section and title when nothing else shares them. Anything unpaired
|
|
778
|
+
is reported as having appeared or disappeared, never as resolved or reopened.
|
|
779
|
+
Findings are left to `--baseline`, which already reports what is new. The exit
|
|
780
|
+
code is `0` whether anything changed or not: a diff describes, and `check`
|
|
781
|
+
gates. See [ADR-0020](docs/adr/0020-a-diff-names-what-it-can-tell-apart.md).
|
|
782
|
+
|
|
707
783
|
## Visualising
|
|
708
784
|
|
|
709
785
|
```bash
|
|
@@ -739,7 +815,7 @@ exported. Reporters, editor extensions and custom rules are all first-class.
|
|
|
739
815
|
|
|
740
816
|
## Design
|
|
741
817
|
|
|
742
|
-
|
|
818
|
+
Twenty ADRs, which `spec-graph` validates on every CI run:
|
|
743
819
|
|
|
744
820
|
- [ADR-0001 — A hand-written Markdown scanner](docs/adr/0001-hand-written-markdown-scanner.md)
|
|
745
821
|
- [ADR-0002 — A four-phase lifecycle lattice](docs/adr/0002-lifecycle-lattice.md)
|
|
@@ -756,11 +832,18 @@ Twelve ADRs, which `spec-graph` validates on every CI run:
|
|
|
756
832
|
- [ADR-0013 — When the scanner is unsure, it hands back prose](docs/adr/0013-the-scanner-hands-back-prose.md)
|
|
757
833
|
- [ADR-0014 — A relation is spelled in both directions](docs/adr/0014-a-relation-is-spelled-both-ways.md)
|
|
758
834
|
- [ADR-0015 — Feedback goes where the tools already look](docs/adr/0015-feedback-goes-where-the-tools-already-look.md)
|
|
835
|
+
- [ADR-0016 — A query needs a sentence before it is a rule](docs/adr/0016-a-query-needs-a-sentence.md)
|
|
836
|
+
- [ADR-0017 — A predicate must finish](docs/adr/0017-a-predicate-must-finish.md)
|
|
837
|
+
- [ADR-0018 — The configuration file is the root](docs/adr/0018-the-configuration-file-is-the-root.md)
|
|
838
|
+
- [ADR-0019 — The sweep runs in shards](docs/adr/0019-the-sweep-runs-in-shards.md)
|
|
839
|
+
- [ADR-0020 — A diff names only what it can tell apart](docs/adr/0020-a-diff-names-what-it-can-tell-apart.md)
|
|
759
840
|
|
|
760
841
|
**Zero runtime dependencies.** Node 22+, native ESM, TypeScript strict with
|
|
761
842
|
`noUncheckedIndexedAccess` and `exactOptionalPropertyTypes`. The Markdown
|
|
762
|
-
scanner, the front-matter reader, the glob matcher
|
|
763
|
-
written here, so the whole
|
|
843
|
+
scanner, the front-matter reader, the glob matcher, the query parser and the
|
|
844
|
+
regular-expression matcher behind `~=` are all written here, so the whole
|
|
845
|
+
package is auditable in an afternoon — and nothing in the pipeline can take
|
|
846
|
+
longer than its input.
|
|
764
847
|
|
|
765
848
|
**Verified, not just covered.** 411 tests; 94.2% statement and 96.9% line
|
|
766
849
|
coverage. Coverage says a line ran, so the suite is also held to a mutation
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The command line.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Five verbs, because there are five questions worth asking of a specification
|
|
5
5
|
* graph: is it consistent (`check`), what does it contain (`query`), what does
|
|
6
|
-
* it look like (`graph`),
|
|
6
|
+
* it look like (`graph`), what will you check for me (`rules`), and what did a
|
|
7
|
+
* change do to it (`diff`).
|
|
7
8
|
*
|
|
8
9
|
* Exit codes are the contract with CI: `0` clean, `1` findings, `2` the tool
|
|
9
10
|
* itself could not run. A usage mistake never masquerades as a passing build.
|
|
@@ -16,11 +17,19 @@ export declare const EXIT_ERROR = 2;
|
|
|
16
17
|
export declare class UsageError extends Error {
|
|
17
18
|
constructor(message: string);
|
|
18
19
|
}
|
|
19
|
-
export type Command = 'check' | 'query' | 'graph' | 'rules';
|
|
20
|
+
export type Command = 'check' | 'query' | 'graph' | 'rules' | 'diff';
|
|
20
21
|
export interface CliOptions {
|
|
21
22
|
readonly command: Command;
|
|
22
23
|
readonly patterns: readonly string[];
|
|
23
24
|
readonly root: string;
|
|
25
|
+
/**
|
|
26
|
+
* Whether `--root` was given.
|
|
27
|
+
*
|
|
28
|
+
* When it was, the caller has named the root and configuration is read from
|
|
29
|
+
* exactly there. When it was not, the root is discovered - which is a
|
|
30
|
+
* different question from what the root currently is. See ADR-0018.
|
|
31
|
+
*/
|
|
32
|
+
readonly rootExplicit: boolean;
|
|
24
33
|
readonly ignore: readonly string[];
|
|
25
34
|
/** Reference targets to leave unreported when they do not resolve. */
|
|
26
35
|
readonly ignoreReferences: readonly string[];
|
|
@@ -45,7 +54,7 @@ export interface CliOptions {
|
|
|
45
54
|
readonly ratchet: boolean;
|
|
46
55
|
/** Skip the repository configuration file entirely. */
|
|
47
56
|
readonly noConfig: boolean;
|
|
48
|
-
readonly format: 'human' | 'json' | 'sarif';
|
|
57
|
+
readonly format: 'human' | 'json' | 'sarif' | 'markdown';
|
|
49
58
|
readonly graphFormat: GraphFormat;
|
|
50
59
|
readonly severities: Partial<Record<AnyRuleId, Severity>>;
|
|
51
60
|
readonly color: boolean | null;
|
|
@@ -68,7 +77,7 @@ export interface CliIO {
|
|
|
68
77
|
readonly env?: Readonly<Record<string, string | undefined>> | undefined;
|
|
69
78
|
readonly isTTY?: boolean | undefined;
|
|
70
79
|
}
|
|
71
|
-
export declare const HELP = "spec-graph - turn Markdown specifications into a verifiable graph\n\nUSAGE\n spec-graph [check] [patterns...] [options]\n spec-graph query <selector> [patterns...] [options]\n spec-graph graph [patterns...] [--graph-format dot|mermaid|json]\n spec-graph rules [--explain]\n\nCOMMANDS\n check Validate the specification graph. The default.\n query Run a selector and
|
|
80
|
+
export declare const HELP = "spec-graph - turn Markdown specifications into a verifiable graph\n\nUSAGE\n spec-graph [check] [patterns...] [options]\n spec-graph query <selector|project:rule> [patterns...] [options]\n spec-graph graph [patterns...] [--graph-format dot|mermaid|json]\n spec-graph rules [rule-id] [--explain]\n spec-graph diff <before.json> <after.json> [--format human|json|markdown]\n\nCOMMANDS\n check Validate the specification graph. The default.\n query Run a selector - or a registered project rule, by its id - and\n print the matching paths.\n graph Export the graph for Graphviz, Mermaid, or another tool.\n rules List the diagnostics that will run, built in and project. Name one\n to see only that one; --explain adds its selector and the ADR that\n decided it.\n diff Compare two graph exports: documents added, removed, moved or\n accepted; relations added or removed; and obligations resolved or\n reopened where they can be told apart. Make both exports with the\n same spec-graph. Exits 0 whether anything changed or not.\n\nOPTIONS\n --root <dir> Directory the patterns resolve against. Without it,\n .spec-graph.json is looked for in the working\n directory and then upward as far as the repository,\n and the directory holding it becomes the root - so a\n run from a subdirectory reports what a run from the\n top reports. Paths typed on the command line stay\n relative to where they were typed.\n --ignore <glob> Skip paths. Repeatable.\n --ignore-ref <glob> Do not report these reference targets when they fail\n to resolve, for repositories where [[...]] tags a\n concept rather than naming a file. Repeatable.\n Suppresses findings only, never edges.\n --family <name> Families a bare identifier in prose may name. When\n given, everything else stays prose. Repeatable.\n --ignore-family <name> Families that are never citations - RFC when the repo\n cites RFC 2119 and keeps its own RFCs. Repeatable.\n --history <glob> Files that log what was decided rather than deciding\n it - journals, changelogs, minutes. Their links are\n still checked; their obligations are not. Repeatable.\n --baseline <file> Accept the findings recorded in this file and report\n only what is new since. Missing file = accept nothing.\n --record-baseline <f> Write today's findings to this file as accepted debt,\n and exit 0 without judging them.\n --ratchet Also fail when a baseline entry no longer occurs, so\n a paid-off exemption cannot outlive the defect.\n --no-config Ignore .spec-graph.json and the package.json key.\n --format <fmt> human, json, sarif or markdown. sarif is the\n interchange format GitHub code scanning and editors\n already read, for check; markdown is a table for a\n pull-request comment or $GITHUB_STEP_SUMMARY, for check\n or diff (default: human)\n --graph-format <fmt> dot, mermaid or json (default: dot)\n --documents-only Leave items out of the exported graph\n --rule <id>=<severity> Override one rule: error, warn, info or off. A project\n rule is named in full: project:no-draft-dependency.\n Repeatable.\n --max <n> Show at most n findings (0 = no limit)\n --max-warnings <n> Fail when warnings exceed n (default: no limit)\n --strict Raise every warning to an error. An explicit --rule\n still wins, so --strict --rule x=warn exempts x.\n --color / --no-color Force colour on or off\n --ascii Use ASCII glyphs only\n --verbose Include parse problems, per-file detail, and every\n reference --ignore-ref or --ignore-family silenced\n -h, --help Show this help\n -v, --version Show the version\n\nSELECTORS\n A selector is a path through the graph.\n\n item[openness=open] -delegates-to-> document[phase=retired]\n document[phase=active] -assumes-> document[phase=retired]\n document =supersedes=> document\n *[path^=docs/adr] -contains-> item[state=accepted-debt]\n\n Node types: document, item, * (any)\n Attributes: id, kind, title, path, file, line, phase, status, receptivity,\n alias, document, state (or disposition), openness, section,\n text, body, evidence, conflicted, fm.<front-matter-key>\n Operators: = != ^= $= *= ~= and [attr] for \"is present\"\n ~= is a regular expression, matched by an automaton that cannot\n backtrack: linear in the subject, whatever the pattern. It reads\n the usual syntax minus backreferences and lookaround, which are\n not regular - both are refused when the selector is read, with\n the character pointed at\n Relations: -kind-> <-kind- =kind=> <=kind= (= forms are transitive)\n\nEXIT CODES\n 0 clean\n 1 findings at error severity (or over --max-warnings)\n 2 the tool could not run\n\nCONFIGURATION\n Anything repeated on every run belongs in the repository rather than in the\n command. spec-graph reads the first of these that exists:\n\n .spec-graph.json, spec-graph.config.json, or a \"spec-graph\" key in\n package.json\n\n { \"patterns\": [\"docs/**/*.md\"],\n \"ignoreReferences\": [\"trap *\"],\n \"ignoreFamilies\": [\"RFC\"],\n \"historyPatterns\": [\"**/JOURNAL_*.md\", \"archive/**\"],\n \"baseline\": \".spec-graph-baseline.json\",\n \"severities\": { \"self-reference\": \"off\" },\n \"strict\": true }\n\n A flag always wins over the file, and list flags add to it rather than\n replacing it.\n\nPROJECT RULES\n A convention spec-graph never anticipated is a selector plus a sentence, and\n belongs in the same file. Every rule declared here runs beside the built-ins\n and is reported, baselined, escalated by --strict and silenced by --rule in\n exactly the same way.\n\n { \"rules\": {\n \"no-draft-dependency\": {\n \"query\": \"document[phase=active] -depends-on-> document[phase=draft]\",\n \"message\": \"{0} depends on {1}, which is still a draft\",\n \"hint\": \"wait for {1} to be accepted, or drop it from {0.path}\",\n \"severity\": \"error\" } } }\n\n query One selector, or a list of them read as a union.\n message The headline. {0} is the first node on the path, {1} the next;\n {1.phase} and {0.fm.owner} read any selector attribute.\n hint The next action. Optional, and templated the same way.\n severity error, warn, info or off. Defaults to warn - a rule a team has\n just written has not yet earned the right to fail their build.\n\n The id is the name with project: in front, which is why it can never collide\n with a built-in. A selector that does not parse, or a {2} the query can never\n reach, is reported when the file is read rather than found missing later.\n\n To see what one matches without copying its selector out of the file:\n\n spec-graph query project:no-draft-dependency --verbose\n\nADOPTING THIS ON AN OLD REPOSITORY\n Record what is already wrong, then report only what happens next:\n\n spec-graph check --record-baseline .spec-graph-baseline.json\n git add .spec-graph-baseline.json\n\n The file is keyed by specification and citation, not by line number, so it\n survives edits, moves and renames. Findings that stop occurring are reported\n so the file can be tightened; nothing new gets in.\n\nEXAMPLES\n spec-graph \"docs/**/*.md\"\n spec-graph check --rule self-reference=off --format json\n spec-graph query 'item[openness=open] -delegates-to-> document[phase=retired]'\n spec-graph check --ignore-ref \"trap *\" # [[trap 55]] tags a concept, not a file\n spec-graph graph --documents-only --graph-format mermaid > graph.mmd\n spec-graph check --history \"**/JOURNAL_*.md\" # a log is not a specification\n spec-graph check --baseline .spec-graph-baseline.json\n spec-graph rules --explain # including this repository's own\n spec-graph rules ghost-handover --explain # and why it exists\n spec-graph check --rule project:no-draft-dependency=off\n spec-graph query project:no-draft-dependency # what does that rule match?\n spec-graph check --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n spec-graph diff base.json head.json --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n";
|
|
72
81
|
/** Parses argv into options. Throws {@link UsageError} on anything malformed. */
|
|
73
82
|
export declare function parseArgs(argv: readonly string[], cwd: string): CliOptions;
|
|
74
83
|
/** Runs the CLI and returns the process exit code. Never throws. */
|