@descent-vtt/spec-guard 0.5.1 → 0.7.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 +444 -283
- package/README.md +1094 -868
- package/dist/cli.d.ts +18 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +136 -7
- package/dist/cli.js.map +1 -1
- package/dist/glob.d.ts +17 -2
- package/dist/glob.d.ts.map +1 -1
- package/dist/glob.js +25 -9
- package/dist/glob.js.map +1 -1
- package/dist/graph.d.ts +124 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +241 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +19 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/layers.d.ts +72 -0
- package/dist/layers.d.ts.map +1 -0
- package/dist/layers.js +102 -0
- package/dist/layers.js.map +1 -0
- package/dist/mcp.d.ts +117 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +544 -0
- package/dist/mcp.js.map +1 -0
- package/dist/parser.d.ts +50 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +232 -16
- package/dist/parser.js.map +1 -1
- package/dist/query.d.ts +108 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +206 -0
- package/dist/query.js.map +1 -0
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +62 -5
- package/dist/reporter.js.map +1 -1
- package/dist/rules.d.ts +108 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +153 -0
- package/dist/rules.js.map +1 -0
- package/dist/runner.d.ts +24 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +335 -117
- package/dist/runner.js.map +1 -1
- package/dist/specs.d.ts +44 -0
- package/dist/specs.d.ts.map +1 -0
- package/dist/specs.js +62 -0
- package/dist/specs.js.map +1 -0
- package/dist/types.d.ts +60 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +73 -73
package/CHANGELOG.md
CHANGED
|
@@ -1,283 +1,444 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project are documented here. Versions follow
|
|
4
|
-
[semantic versioning](https://semver.org), with the 0.x caveat that behaviour
|
|
5
|
-
may change in a minor release — each such change is listed under **Changed**
|
|
6
|
-
with the flag that restores the previous behaviour.
|
|
7
|
-
|
|
8
|
-
## 0.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
the
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
to
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
- **
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. Versions follow
|
|
4
|
+
[semantic versioning](https://semver.org), with the 0.x caveat that behaviour
|
|
5
|
+
may change in a minor release — each such change is listed under **Changed**
|
|
6
|
+
with the flag that restores the previous behaviour.
|
|
7
|
+
|
|
8
|
+
## 0.7.0
|
|
9
|
+
|
|
10
|
+
Two things an architecture document could not do before. It can state the shape
|
|
11
|
+
of a codebase: which layers depend on which, and that nothing imports itself
|
|
12
|
+
round a loop. And it can be asked, before any code is written, which of its
|
|
13
|
+
rules govern the file about to change - from the command line, or by an AI agent
|
|
14
|
+
over the Model Context Protocol. Building the second found a false green older
|
|
15
|
+
than every release, and it is fixed below.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`@assert-layers`** - one directive for a layered architecture.
|
|
20
|
+
`order="src/domain, src/application, src/infrastructure"` runs from the layer
|
|
21
|
+
everything may depend on to the layer that may depend on everything; a file
|
|
22
|
+
importing from a layer listed after its own is a violation, reported with both
|
|
23
|
+
layers and the import. Layers are patterns in the `module=` language, so a bare
|
|
24
|
+
`domain` holds Python, Go, Rust and C# code to the same order as TypeScript.
|
|
25
|
+
A layer that matches no file, a file two layers both claim, and a missing
|
|
26
|
+
target all fail rather than pass; files no layer claims are counted in a
|
|
27
|
+
warning. Takes `max`, `types`, `exclude`, `allow-empty` and a debt `baseline`.
|
|
28
|
+
- **`@assert-import-cycle`** - import cycles, counted as strongly connected
|
|
29
|
+
components rather than simple cycles, so a second route around an existing
|
|
30
|
+
knot does not move the count. Each is shown as its shortest loop with the line
|
|
31
|
+
of every import on it. Found with an iterative Tarjan's algorithm: the
|
|
32
|
+
recursive form overflows Node's default stack at 10,000 files.
|
|
33
|
+
`types="ignore"` asks the runtime question. JavaScript and TypeScript only,
|
|
34
|
+
because a cycle needs to know which file an import *is*; imports that should
|
|
35
|
+
have become an edge and did not are reported, and fail under `--strict`.
|
|
36
|
+
See [ADR-0011](docs/adr/0011-layers-and-cycles.md) for the resolution table,
|
|
37
|
+
and for why the other four languages are not in the graph.
|
|
38
|
+
- `buildGraph`, `resolveReference`, `stronglyConnected`, `cyclicComponents`,
|
|
39
|
+
`witness`, `checkLayers` and `referenceForms` are exported, with their types.
|
|
40
|
+
- **`spec-guard query <paths...>`** - the rules in force for a file or directory,
|
|
41
|
+
grouped by the document that states them, with each rule's reason, a path's
|
|
42
|
+
position in every layer order (`may import`, `must not import`), and any
|
|
43
|
+
baseline entry that exempts it. Answered from the specs alone, without reading
|
|
44
|
+
the codebase, so it works for a file that does not exist yet; `--json` for
|
|
45
|
+
scripts. Rules in documents that are not in force are counted and named, and
|
|
46
|
+
listed with `--ignore-status`. The arithmetic behind it is tested against the
|
|
47
|
+
files a real run searches, under both engines and on random trees; see
|
|
48
|
+
[ADR-0012](docs/adr/0012-query-and-mcp.md) for the three things it cannot see.
|
|
49
|
+
- **`spec-guard mcp`** - a Model Context Protocol server on stdio, with no
|
|
50
|
+
dependency on the MCP SDK. Tools `get_architectural_rules` (the query) and
|
|
51
|
+
`check_architecture` (a run, narrowed to the rules governing given paths, each
|
|
52
|
+
judged over its whole scope, with violations marked as in those paths or not);
|
|
53
|
+
resources `spec://rules` and `spec://doc/{+path}`. Serves clients that open
|
|
54
|
+
with `initialize` (2024-10-07 to 2025-11-25) and clients on 2026-07-28 that
|
|
55
|
+
version every request and probe with `server/discover`, classifying each
|
|
56
|
+
request the way the TypeScript SDK's own server does. Run end to end against
|
|
57
|
+
the official clients: `@modelcontextprotocol/sdk` 1.30.0, and
|
|
58
|
+
`@modelcontextprotocol/client` 2.0.0 pinned to 2026-07-28, probing, and on
|
|
59
|
+
the legacy handshake.
|
|
60
|
+
- **`--spec <pattern>`**, repeatable: where `query` and `mcp` take their specs,
|
|
61
|
+
and an alternative to positional patterns for a run.
|
|
62
|
+
- `select` on `runSpecGuard`, to execute only some of the resolved assertions.
|
|
63
|
+
`readSpecs`, `parseDocument`, `parseTitle`, `walkPaths`, `governs`,
|
|
64
|
+
`queryRules`, `createMcpHandler` and `serveStdio` are exported, with their types.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- **A rule whose every `target` was missing searched the whole repository**
|
|
69
|
+
under `--allow-missing-targets`, because an empty target list means the root
|
|
70
|
+
to the engine. So `@assert-count target="src/auth" symbol="verifyToken"
|
|
71
|
+
min="1"` kept passing after `src/auth` was deleted, as long as a test still
|
|
72
|
+
named `verifyToken`; import, layer and cycle rules did the same. This has been
|
|
73
|
+
true since 0.1.0 - the 0.3.0 entry below that describes the old behaviour as
|
|
74
|
+
"search nothing, find nothing" was wrong about what the code did. Such a rule
|
|
75
|
+
now has an empty scope: it fails as one, and with `--allow-empty-scope` or
|
|
76
|
+
`allow-empty="true"` it finds nothing. **If you rely on
|
|
77
|
+
`--allow-missing-targets` for a rule whose only target is gone, add
|
|
78
|
+
`--allow-empty-scope`.**
|
|
79
|
+
- An import rule whose only target is missing, without `--allow-missing-targets`,
|
|
80
|
+
now says the target does not exist instead of that its scope is empty.
|
|
81
|
+
|
|
82
|
+
### Changed
|
|
83
|
+
|
|
84
|
+
- **`query` and `mcp` are commands when they are the first argument.** A spec
|
|
85
|
+
file literally named `query` or `mcp`, passed as the first pattern, now starts
|
|
86
|
+
a command instead; write `./query`, or put it after `--`.
|
|
87
|
+
- **A rule whose every `target` is missing has an empty scope under
|
|
88
|
+
`--allow-missing-targets`**, where it used to search the whole repository -
|
|
89
|
+
see Fixed. Add `--allow-empty-scope`, or `allow-empty="true"` on the rule, for
|
|
90
|
+
it to pass. Searching the root in its place is not restorable: it was the bug.
|
|
91
|
+
- **Mutation score 98.47%** over 6,263 mutants, up from 0.6.0's 97.77%, with 93
|
|
92
|
+
survivors - four fewer than before the 1,185 mutants this release adds. The
|
|
93
|
+
layering and cycle modules are at 99.51% (`graph.ts`) and 96.43%
|
|
94
|
+
(`layers.ts`); their three survivors were shown equivalent by running the
|
|
95
|
+
mutated build against the real one on thousands of random inputs. The query
|
|
96
|
+
and the server (`query.ts`, `rules.ts`, `mcp.ts`, `specs.ts`) are all at 100%;
|
|
97
|
+
most of the survivors found on the way were code nothing could observe, and it
|
|
98
|
+
was deleted. See [ADR-0003](docs/adr/0003-mutation-testing.md) for both
|
|
99
|
+
accounts, and for why one sweep's count is good to about three.
|
|
100
|
+
- `version(manifest?)` takes the manifest to read, so its fallback for a broken
|
|
101
|
+
install - `0.0.0` rather than a crash - is tested.
|
|
102
|
+
- **Reading specs is several times faster**, which a query's budget made worth
|
|
103
|
+
measuring. `maskCode` builds ranges instead of blanking a character array
|
|
104
|
+
(7.8 ms to 1.1 ms over this repository's specs, identical output on 72 real
|
|
105
|
+
documents and 100,000 random inputs); spec globs no longer stat every file
|
|
106
|
+
they list (102 ms to 18 ms over 1,200 specs on Windows); and specs are read
|
|
107
|
+
16 at a time. A query over this repository went from 24.6 ms to 5.1 ms. A run
|
|
108
|
+
spends most of its time searching code, so this repository's selfcheck gained
|
|
109
|
+
about 8%.
|
|
110
|
+
- The SARIF fingerprint of an assertion that names neither a symbol nor a file
|
|
111
|
+
now includes its description. Only the two new kinds are affected; every
|
|
112
|
+
existing alert keeps its identity. Without it, two cycle rules on one target
|
|
113
|
+
would have been merged into one alert.
|
|
114
|
+
|
|
115
|
+
## 0.6.0
|
|
116
|
+
|
|
117
|
+
ADRs have a life, and spec-guard now reads it. A proposed ADR can carry live
|
|
118
|
+
assertions without breaking the build, and a superseded one can stay on disk
|
|
119
|
+
without enforcing the rule it records being replaced. Most of the work is in
|
|
120
|
+
making sure a rule that stops running can never look like a rule that passed.
|
|
121
|
+
|
|
122
|
+
### Added
|
|
123
|
+
|
|
124
|
+
- **Document lifecycle status.** A Markdown document whose status is `draft`,
|
|
125
|
+
`proposed`, `rejected`, `deprecated` or `superseded` is parsed, validated,
|
|
126
|
+
reported by name - and not executed. A proposed ADR can now be written with
|
|
127
|
+
its assertions live and the build green, and a superseded ADR can stay on
|
|
128
|
+
disk, intact, enforcing nothing. Three spellings are read, because three are
|
|
129
|
+
in use: MADR front-matter (quoted, as MADR's template writes it, or not), a
|
|
130
|
+
Nygard `## Status` section, and a bold `**Status:**` label in the preamble.
|
|
131
|
+
CRLF documents read the same as LF ones. Anything else - an unrecognised
|
|
132
|
+
word, a misspelling, no status at all - keeps enforcing, which is the
|
|
133
|
+
direction that cannot turn a typo into a silently disabled rule.
|
|
134
|
+
|
|
135
|
+
Withholding is never quiet. Every withheld document is named in the human
|
|
136
|
+
report (`○ docs/adr/0011.md is Proposed. - 2 assertions not executed`), in
|
|
137
|
+
`--format json` as `inactiveSpecs`, and in `--format sarif` as a note-level
|
|
138
|
+
execution notification - because a rule that has gone quiet is otherwise
|
|
139
|
+
indistinguishable from a rule that passed. Directives in a withheld document
|
|
140
|
+
are still checked for typos, bad attributes and unresolvable values, so a
|
|
141
|
+
draft's mistake is found on the day it is written rather than on the day
|
|
142
|
+
everyone agrees the rule is right and stops looking at it.
|
|
143
|
+
See [ADR-0010](docs/adr/0010-spec-status.md), which also records why there is
|
|
144
|
+
no per-directive `if-status` attribute.
|
|
145
|
+
- `--ignore-status` executes every directive whatever its document declares -
|
|
146
|
+
how to ask whether a draft would pass if you accepted it today.
|
|
147
|
+
- `parseStatus`, `INACTIVE_STATUSES`, and the `SpecStatus` / `InactiveSpec`
|
|
148
|
+
types are exported from the programmatic API.
|
|
149
|
+
|
|
150
|
+
### Changed
|
|
151
|
+
|
|
152
|
+
- **A run that executed no assertions no longer reports that every assertion
|
|
153
|
+
holds.** It says `no assertion was executed, so nothing was verified`. True
|
|
154
|
+
and useless was the old sentence's problem: it is the exact line someone
|
|
155
|
+
reads as proof their specification is being enforced. Reachable before this
|
|
156
|
+
release through a spec file with no directives in it; withholding made it
|
|
157
|
+
easy to reach.
|
|
158
|
+
- `RunSummary` gains `inactive` and `RunReport` gains `inactiveSpecs`. Both are
|
|
159
|
+
always present; consumers deep-comparing a summary object will see the new
|
|
160
|
+
field.
|
|
161
|
+
- **Mutation score 97.77%** over 4,482 mutants, against the gate of 97 - up
|
|
162
|
+
from the 97.65% 0.5.1 shipped with, and no survivor in the new code. Getting there turned up a way a local sweep overstates a score
|
|
163
|
+
that the timeout warning could not see: a regex constant at module scope runs
|
|
164
|
+
the whole suite per mutant, so on a loaded machine an unrelated test timing
|
|
165
|
+
out is scored as a kill. Those patterns are now verified by applying every
|
|
166
|
+
mutant by hand, `scripts/mutation-regex.mjs`, and the account is in
|
|
167
|
+
[ADR-0003](docs/adr/0003-mutation-testing.md).
|
|
168
|
+
|
|
169
|
+
## 0.5.1
|
|
170
|
+
|
|
171
|
+
Writing down what the code already claimed. Every module was driven to its
|
|
172
|
+
honest mutation-testing ceiling, and the exercise turned up six wrong answers
|
|
173
|
+
rather than six missing tests — which is the argument for doing it at all. No
|
|
174
|
+
new features, and nothing a user has to change.
|
|
175
|
+
|
|
176
|
+
### Fixed
|
|
177
|
+
|
|
178
|
+
- **Two assertions on the same symbol answered each other's comment handling.**
|
|
179
|
+
The result cache keyed on the symbol, the targets and the search flags, but
|
|
180
|
+
not on `comments`. A spec with `<!-- @assert-count symbol="X" ... -->` above
|
|
181
|
+
`<!-- @assert-count symbol="X" ... comments="include" -->` returned whichever
|
|
182
|
+
count ran first for both. The grouping test had the same omission, so the two
|
|
183
|
+
were also merged into a single pass and the second was scanned with the
|
|
184
|
+
first's mask. There is now one definition of "the same question", in three
|
|
185
|
+
nested scopes, and the scope policy is part of it too.
|
|
186
|
+
- **An unreadable directory went unreported on a small repository.** The
|
|
187
|
+
adaptive engine had the walk's skip ledger in hand and returned without it on
|
|
188
|
+
any tree small enough to scan in process, so the same directory was a reported
|
|
189
|
+
gap on a large repository and silence on a small one. Same tree, two answers,
|
|
190
|
+
decided by its size.
|
|
191
|
+
- **Snippets from CRLF files carried a carriage return into the report**, which
|
|
192
|
+
returns the terminal cursor to column 0 and overwrites the line just printed.
|
|
193
|
+
The trim looked for `\r?\n` at the end of a line the caller had already cut at
|
|
194
|
+
the newline, so it could never match.
|
|
195
|
+
- **A bad `regex="true"` pattern was reported as `Invalid regular expression:
|
|
196
|
+
Invalid regular expression: /(/: ...`** — V8's message already says it once.
|
|
197
|
+
|
|
198
|
+
### Changed
|
|
199
|
+
|
|
200
|
+
- **Two requests differing only in `scope` are no longer merged into one
|
|
201
|
+
search pass.** Reachable today only through the programmatic API, where a
|
|
202
|
+
caller may build more than one `ScopePolicy` per run.
|
|
203
|
+
- `enumerateCandidates` takes a `WalkRequest` — a `SearchRequest` without the
|
|
204
|
+
`symbol`, because a walk does not depend on one. Existing callers are
|
|
205
|
+
unaffected; the symbol is now optional rather than required.
|
|
206
|
+
- `CachedEngine` declares `searchBatch` as present rather than optional, which
|
|
207
|
+
`createCachedEngine` has always guaranteed.
|
|
208
|
+
- `KINDS` and `ALLOWED_ATTRIBUTES` are exported from the parser, so the
|
|
209
|
+
directive grammar can be asserted rather than restated.
|
|
210
|
+
- **The mutation gate moves from 85 to 97**, against a CI measurement of 97.65%
|
|
211
|
+
over 4,299 mutants. Every module is above 95% and three are at 100%, where
|
|
212
|
+
0.5.0 ran from 73% to 96%. The 101 mutants still alive have each been applied
|
|
213
|
+
individually and produce byte-identical output, so that figure is the ceiling
|
|
214
|
+
rather than a way-point — and 29 that were assumed equivalent turned out not
|
|
215
|
+
to be, and are now tested. See
|
|
216
|
+
[ADR-0003](docs/adr/0003-mutation-testing.md), which also records a Stryker
|
|
217
|
+
limitation found on the way: a mutant that stops a test file *loading* is
|
|
218
|
+
reported as survived even though the suite is killing it.
|
|
219
|
+
- **`src/imports.ts` went from 73.74% to 95.89%.** The tokenizer was tested as a
|
|
220
|
+
step towards a list of module references rather than as a thing with an
|
|
221
|
+
output, so a template resumption that lost its place, a regex escape that
|
|
222
|
+
skipped a character, and three lookup tables were all unpinned. One
|
|
223
|
+
behaviour-preserving change came with it: the word branch starts its scan one
|
|
224
|
+
character in, so progress is unconditional by construction rather than a
|
|
225
|
+
consequence of a relation nothing enforced. Verified identical on 53.6 MB of
|
|
226
|
+
real JavaScript.
|
|
227
|
+
|
|
228
|
+
### Internal
|
|
229
|
+
|
|
230
|
+
Not user-visible, but this is where the release's weight is: nine new test
|
|
231
|
+
files, 1,770 tests against 1,206 at 0.5.0, 100% line coverage, and the analyser
|
|
232
|
+
verified byte-identical to the published 0.5.0 across 6,976 files of real code.
|
|
233
|
+
Thirty-five negative controls confirm the suite goes red for each defect above,
|
|
234
|
+
and `scripts/mutation-equivalence.mjs` is in the repository so the equivalence
|
|
235
|
+
measurement stays reproducible.
|
|
236
|
+
|
|
237
|
+
## 0.5.0
|
|
238
|
+
|
|
239
|
+
Import assertions covered one language, so a dependency rule pointed at a
|
|
240
|
+
directory of Go files did not fail - it passed, having analysed nothing. That is
|
|
241
|
+
the same defect 0.4.0 was written to remove, and this release closes the two
|
|
242
|
+
remaining shapes of it: a rule that cannot read the language, and a rule whose
|
|
243
|
+
scope holds no files at all.
|
|
244
|
+
|
|
245
|
+
### Added
|
|
246
|
+
|
|
247
|
+
- **Import assertions read Python, Go, Rust and C#**, alongside JavaScript and
|
|
248
|
+
TypeScript. `import a.b` / `from .rel import x`, `import ( ... )` groups,
|
|
249
|
+
`use a::{b, c}` with nested expansion, `using static` and `global using`, and
|
|
250
|
+
the dynamic forms (`importlib.import_module`) that can only be reported.
|
|
251
|
+
Not four new tokenizers: the comment and string lexer from ADR-0006 masks the
|
|
252
|
+
source and the readers work on what is left. Tree-sitter measured 94 MB
|
|
253
|
+
unpacked against this package's 0.33 MB. See
|
|
254
|
+
[ADR-0008](docs/adr/0008-polyglot-imports.md).
|
|
255
|
+
- **`baseline` and `ratchet`** on the absence assertions, for adopting a strict
|
|
256
|
+
rule on a codebase that already breaks it. `baseline="src/legacy/a.ts:2"`
|
|
257
|
+
names the debt by file and count. The ratchet is two-sided: new violations
|
|
258
|
+
fail, and so does an entry the code no longer supports, because a baseline
|
|
259
|
+
that only grows is an `exclude` with extra steps. `ratchet="one-way"` relaxes
|
|
260
|
+
the second half. See [ADR-0009](docs/adr/0009-debt-baselines.md).
|
|
261
|
+
- **`--print-baseline`** prints the attribute that would exempt today's
|
|
262
|
+
violations, for a human to paste. It prints; it does not edit. That is the
|
|
263
|
+
whole answer to `--fix`, and ADR-0009 argues it.
|
|
264
|
+
- **`--format sarif`** writes SARIF 2.1.0, which GitHub turns into an annotation
|
|
265
|
+
on the offending line. One alert per broken rule, anchored on the code, with
|
|
266
|
+
the directive as a related location and a fingerprint that survives the code
|
|
267
|
+
moving. `--json` is unchanged and is now also `--format json`.
|
|
268
|
+
- **`allow-empty` and `--allow-empty-scope`**, for the rules where covering
|
|
269
|
+
nothing is the honest state of the world.
|
|
270
|
+
- **`baselinedMatches`, `staleBaseline` and `fileMatches`** on each result in
|
|
271
|
+
`--json`.
|
|
272
|
+
|
|
273
|
+
### Changed
|
|
274
|
+
|
|
275
|
+
- **An assertion that inspected no files now fails.** A rule whose scope holds
|
|
276
|
+
nothing passes every time and reads in the report exactly like a rule that
|
|
277
|
+
inspected a thousand files and found nothing. The usual causes are a `glob`
|
|
278
|
+
matching no extension in the tree, an `exclude` that swallowed the target, or
|
|
279
|
+
an emptied directory. **If a run starts failing this way, the rule was
|
|
280
|
+
covering nothing before it started failing;** `allow-empty="true"` on the
|
|
281
|
+
directive, or `--allow-empty-scope` for the run, restores the old behaviour.
|
|
282
|
+
- **An import assertion fails when nothing in scope is in a language it can
|
|
283
|
+
read**, rather than reporting "analysed 0 of 12 files" in a warning and
|
|
284
|
+
passing.
|
|
285
|
+
- **`.py`, `.pyi`, `.go`, `.rs`, `.cs` and `.csx` files are now analysed** by import
|
|
286
|
+
assertions rather than counted as skipped, so a rule over a polyglot tree
|
|
287
|
+
starts finding dependencies it previously reported as unanalysable.
|
|
288
|
+
- **Mutation testing in CI runs in two tiers**: incremental on branches,
|
|
289
|
+
a full authoritative sweep on `main` and nightly. The gate stays at 85 in
|
|
290
|
+
both. See [ADR-0003](docs/adr/0003-mutation-testing.md).
|
|
291
|
+
|
|
292
|
+
### Fixed
|
|
293
|
+
|
|
294
|
+
- `excludeFiles` was applied after enumeration rather than during it, so an
|
|
295
|
+
excluded spec file counted towards a budgeted walk. Only reachable through the
|
|
296
|
+
adaptive engine's probe, where it could make a tree look larger than the file
|
|
297
|
+
set actually being searched.
|
|
298
|
+
- The README described the safe-batching apparatus - containment and
|
|
299
|
+
dovetailing checks before merging literals into a ripgrep alternation - as
|
|
300
|
+
current behaviour. It was deleted in 0.4.0 when ripgrep became a pre-filter.
|
|
301
|
+
|
|
302
|
+
## 0.4.0
|
|
303
|
+
|
|
304
|
+
An audit found spec-guard reporting a clean pass on a repository that contained
|
|
305
|
+
the forbidden symbol. On a tree with eight copies of one token, the scanner
|
|
306
|
+
found two and ripgrep found four, and neither said anything about the rest.
|
|
307
|
+
|
|
308
|
+
### Changed
|
|
309
|
+
|
|
310
|
+
- **Hidden directories are searched.** `.github`, `.husky`, `.claude-rules` and
|
|
311
|
+
`.agents` hold CI, hooks and agent rules, and both engines skipped them
|
|
312
|
+
entirely - so an absence assertion passed while the forbidden thing sat in a
|
|
313
|
+
workflow file.
|
|
314
|
+
- **The skip list is now four names**: `.git`, `.hg`, `.svn`, `node_modules`.
|
|
315
|
+
`dist`, `build`, `out`, `coverage`, `.next` and eight others are searched,
|
|
316
|
+
because spec-guard cannot tell build output from a directory of build scripts,
|
|
317
|
+
and guessing wrong means a rule silently covers nothing. **If a run starts
|
|
318
|
+
failing on your build output, that is this change, and the fix is to say so:**
|
|
319
|
+
|
|
320
|
+
```md
|
|
321
|
+
<!-- @assert-absence target="." symbol="TODO" exclude="dist coverage" -->
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
- **`.gitignore` is no longer consulted.** It describes what git should carry,
|
|
325
|
+
not what a rule covers - and ripgrep applies it only inside a git repository,
|
|
326
|
+
so the same tree gave different answers depending on whether a `.git`
|
|
327
|
+
directory existed above it.
|
|
328
|
+
- **Binary files are searched, and a match in one is reported** rather than
|
|
329
|
+
dropped. It is not counted as a violation, but it is no longer invisible.
|
|
330
|
+
"Binary" now means "contains a NUL byte" for both engines; the scanner used to
|
|
331
|
+
look only at the first 8KB, which disagreed with ripgrep on files whose first
|
|
332
|
+
NUL came later.
|
|
333
|
+
- **`--strict` fails when a file could not be inspected**, which now has a
|
|
334
|
+
precise meaning: unreadable, or binary and containing the symbol.
|
|
335
|
+
- **The engines are one implementation.** ripgrep now answers only *which files
|
|
336
|
+
contain this text*; the scanner does all counting, comment classification,
|
|
337
|
+
binary handling and reporting for both. `--engine` changes how long a run
|
|
338
|
+
takes, not what it concludes. See [ADR-0007](docs/adr/0007-search-scope.md).
|
|
339
|
+
|
|
340
|
+
### Added
|
|
341
|
+
|
|
342
|
+
- **`--no-default-skips`** to search even those four directories.
|
|
343
|
+
- **`skipped`** on each result in `--json`: what was not inspected, and why.
|
|
344
|
+
|
|
345
|
+
### Removed
|
|
346
|
+
|
|
347
|
+
- `DEFAULT_IGNORED_DIRECTORIES`, `canBatchLiterals`, `shouldBatchPatterns`,
|
|
348
|
+
`createRipgrepSink` and `byteColumnToCharacter` from the public API. All of
|
|
349
|
+
them existed to make ripgrep's own counting trustworthy; as a pre-filter it
|
|
350
|
+
does not count, so pattern batching no longer has to be proved safe and
|
|
351
|
+
ripgrep's byte columns no longer need converting.
|
|
352
|
+
|
|
353
|
+
### Fixed
|
|
354
|
+
|
|
355
|
+
- ripgrep's per-file errors are no longer discarded. `--no-messages` meant a
|
|
356
|
+
file that could not be opened produced no match, no error, and no way to tell
|
|
357
|
+
it apart from one that was read and found clean.
|
|
358
|
+
- `comments="include"` no longer changes whether binary files are searched. An
|
|
359
|
+
attribute about comments decided that, because it selected a different code
|
|
360
|
+
path through the engine.
|
|
361
|
+
|
|
362
|
+
## 0.3.0
|
|
363
|
+
|
|
364
|
+
### Added
|
|
365
|
+
|
|
366
|
+
- **`exclude` attribute.** Most real rules are "nowhere except one place", not
|
|
367
|
+
"not here": `exclude="src/config/**"` expresses that in one assertion instead
|
|
368
|
+
of a hand-maintained list of every directory that is not `src/config`. Follows
|
|
369
|
+
gitignore semantics — deliberately different from `glob`'s basename matching —
|
|
370
|
+
and a parity matrix asserts both engines agree.
|
|
371
|
+
- **Import assertions:** `@assert-import-absence` and `@assert-import-count`.
|
|
372
|
+
"The UI layer must not depend on the database layer" is a dependency claim,
|
|
373
|
+
not a text claim, and approximating it with a string search fails in both
|
|
374
|
+
directions. A zero-dependency tokenizer resolves `import`, `export … from`,
|
|
375
|
+
`import type`, dynamic `import()` with a literal, and `require()`. Module
|
|
376
|
+
references it cannot resolve statically are reported, never counted as clean.
|
|
377
|
+
See [ADR-0005](docs/adr/0005-import-assertions.md).
|
|
378
|
+
- **`comments` attribute.** See Changed, below.
|
|
379
|
+
- **`--allow-missing-targets`** to restore the previous handling of a `target`
|
|
380
|
+
path that does not exist.
|
|
381
|
+
- **`commentMatches` and `unclassifiedFiles`** on every result in `--json`.
|
|
382
|
+
|
|
383
|
+
### Changed
|
|
384
|
+
|
|
385
|
+
- **Matches inside comments no longer count**, so the note recording that a
|
|
386
|
+
symbol was deleted is no longer read as an occurrence of that symbol. This is
|
|
387
|
+
the defect where documenting a decision defeated the assertion enforcing it.
|
|
388
|
+
Comment syntax is known for 59 extensions across 9 families; strings are
|
|
389
|
+
tracked too, since `//` inside a URL is not a comment. Where classification is
|
|
390
|
+
uncertain the text counts as code, and every run reports how many matches it
|
|
391
|
+
excluded — a pass caused by comment exclusion is never silent.
|
|
392
|
+
`comments="include"` restores counting per assertion.
|
|
393
|
+
See [ADR-0006](docs/adr/0006-comment-classification.md).
|
|
394
|
+
- **A `target` path that does not exist now fails the run** instead of warning
|
|
395
|
+
and searching what remained. An assertion pointed at a renamed directory used
|
|
396
|
+
to search nothing, find nothing, and report success. `--allow-missing-targets`
|
|
397
|
+
restores the old behaviour.
|
|
398
|
+
- **`--strict`** now means "treat analysis that could not be completed as a
|
|
399
|
+
failure" — it no longer carries the missing-target meaning, which is now the
|
|
400
|
+
default.
|
|
401
|
+
|
|
402
|
+
### Fixed
|
|
403
|
+
|
|
404
|
+
- `search()` on the ripgrep engine was not comment-aware while `searchBatch()`
|
|
405
|
+
was, so a spec containing exactly one assertion kept its comment matches.
|
|
406
|
+
- The two engines reported different columns for the same match on a line
|
|
407
|
+
containing non-ASCII text: ripgrep counts bytes, the scanner counts
|
|
408
|
+
characters, and editors count characters. ripgrep's column is now converted,
|
|
409
|
+
so a reported location points at the match in both engines.
|
|
410
|
+
- Leftover `*.test.ts` files in scratch fixture directories were collected by
|
|
411
|
+
Vitest, breaking a run with "no test suite found" because of debris from a
|
|
412
|
+
previous one.
|
|
413
|
+
|
|
414
|
+
### Notes
|
|
415
|
+
|
|
416
|
+
The version in `package.json` was bumped to 0.3.0 during development but never
|
|
417
|
+
published; npm went 0.1.0 -> 0.2.0. Everything above ships together as the
|
|
418
|
+
first published 0.3.0.
|
|
419
|
+
|
|
420
|
+
## 0.2.0
|
|
421
|
+
|
|
422
|
+
### Added
|
|
423
|
+
|
|
424
|
+
- **Adaptive engine selection.** `--engine auto` now measures rather than
|
|
425
|
+
guesses: a budgeted, stat-only enumeration decides whether a tree is small
|
|
426
|
+
enough for the JavaScript scanner to beat a ripgrep process spawn. The probe
|
|
427
|
+
is the work — the enumeration it performs is reused by the scanner, so
|
|
428
|
+
choosing costs nothing when the scanner wins.
|
|
429
|
+
See [ADR-0004](docs/adr/0004-adaptive-engine.md).
|
|
430
|
+
- **Mutation testing** with Stryker, wired into CI on every push, plus the tests
|
|
431
|
+
that closed the gaps it exposed. See [ADR-0003](docs/adr/0003-mutation-testing.md).
|
|
432
|
+
|
|
433
|
+
### Changed
|
|
434
|
+
|
|
435
|
+
- Published as `@descent-vtt/spec-guard`. The binary is still plain
|
|
436
|
+
`spec-guard`.
|
|
437
|
+
|
|
438
|
+
## 0.1.0
|
|
439
|
+
|
|
440
|
+
Initial release: `@assert-absence`, `@assert-count` and `@assert-present`,
|
|
441
|
+
written as HTML comments that are invisible in every Markdown renderer. ripgrep
|
|
442
|
+
primary with a pure-JavaScript fallback of identical semantics, safe assertion
|
|
443
|
+
batching, `--json`, and exit code 2 for "spec-guard could not run" as distinct
|
|
444
|
+
from "your specs failed".
|