@descent-vtt/spec-guard 0.2.0 → 0.3.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 +90 -0
- package/README.md +173 -12
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +21 -3
- package/dist/cli.js.map +1 -1
- package/dist/comments.d.ts +78 -0
- package/dist/comments.d.ts.map +1 -0
- package/dist/comments.js +241 -0
- package/dist/comments.js.map +1 -0
- package/dist/engine.d.ts +29 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +155 -13
- package/dist/engine.js.map +1 -1
- package/dist/glob.d.ts +15 -0
- package/dist/glob.d.ts.map +1 -1
- package/dist/glob.js +36 -0
- package/dist/glob.js.map +1 -1
- package/dist/imports.d.ts +94 -0
- package/dist/imports.d.ts.map +1 -0
- package/dist/imports.js +492 -0
- package/dist/imports.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +24 -2
- package/dist/parser.js.map +1 -1
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +37 -2
- package/dist/reporter.js.map +1 -1
- package/dist/runner.d.ts +12 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +168 -12
- package/dist/runner.js.map +1 -1
- package/dist/types.d.ts +34 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
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.3.0
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`exclude` attribute.** Most real rules are "nowhere except one place", not
|
|
13
|
+
"not here": `exclude="src/config/**"` expresses that in one assertion instead
|
|
14
|
+
of a hand-maintained list of every directory that is not `src/config`. Follows
|
|
15
|
+
gitignore semantics — deliberately different from `glob`'s basename matching —
|
|
16
|
+
and a parity matrix asserts both engines agree.
|
|
17
|
+
- **Import assertions:** `@assert-import-absence` and `@assert-import-count`.
|
|
18
|
+
"The UI layer must not depend on the database layer" is a dependency claim,
|
|
19
|
+
not a text claim, and approximating it with a string search fails in both
|
|
20
|
+
directions. A zero-dependency tokenizer resolves `import`, `export … from`,
|
|
21
|
+
`import type`, dynamic `import()` with a literal, and `require()`. Module
|
|
22
|
+
references it cannot resolve statically are reported, never counted as clean.
|
|
23
|
+
See [ADR-0005](docs/adr/0005-import-assertions.md).
|
|
24
|
+
- **`comments` attribute.** See Changed, below.
|
|
25
|
+
- **`--allow-missing-targets`** to restore the previous handling of a `target`
|
|
26
|
+
path that does not exist.
|
|
27
|
+
- **`commentMatches` and `unclassifiedFiles`** on every result in `--json`.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **Matches inside comments no longer count**, so the note recording that a
|
|
32
|
+
symbol was deleted is no longer read as an occurrence of that symbol. This is
|
|
33
|
+
the defect where documenting a decision defeated the assertion enforcing it.
|
|
34
|
+
Comment syntax is known for 59 extensions across 9 families; strings are
|
|
35
|
+
tracked too, since `//` inside a URL is not a comment. Where classification is
|
|
36
|
+
uncertain the text counts as code, and every run reports how many matches it
|
|
37
|
+
excluded — a pass caused by comment exclusion is never silent.
|
|
38
|
+
`comments="include"` restores counting per assertion.
|
|
39
|
+
See [ADR-0006](docs/adr/0006-comment-classification.md).
|
|
40
|
+
- **A `target` path that does not exist now fails the run** instead of warning
|
|
41
|
+
and searching what remained. An assertion pointed at a renamed directory used
|
|
42
|
+
to search nothing, find nothing, and report success. `--allow-missing-targets`
|
|
43
|
+
restores the old behaviour.
|
|
44
|
+
- **`--strict`** now means "treat analysis that could not be completed as a
|
|
45
|
+
failure" — it no longer carries the missing-target meaning, which is now the
|
|
46
|
+
default.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- `search()` on the ripgrep engine was not comment-aware while `searchBatch()`
|
|
51
|
+
was, so a spec containing exactly one assertion kept its comment matches.
|
|
52
|
+
- The two engines reported different columns for the same match on a line
|
|
53
|
+
containing non-ASCII text: ripgrep counts bytes, the scanner counts
|
|
54
|
+
characters, and editors count characters. ripgrep's column is now converted,
|
|
55
|
+
so a reported location points at the match in both engines.
|
|
56
|
+
- Leftover `*.test.ts` files in scratch fixture directories were collected by
|
|
57
|
+
Vitest, breaking a run with "no test suite found" because of debris from a
|
|
58
|
+
previous one.
|
|
59
|
+
|
|
60
|
+
### Notes
|
|
61
|
+
|
|
62
|
+
The version in `package.json` was bumped to 0.3.0 during development but never
|
|
63
|
+
published; npm went 0.1.0 -> 0.2.0. Everything above ships together as the
|
|
64
|
+
first published 0.3.0.
|
|
65
|
+
|
|
66
|
+
## 0.2.0
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **Adaptive engine selection.** `--engine auto` now measures rather than
|
|
71
|
+
guesses: a budgeted, stat-only enumeration decides whether a tree is small
|
|
72
|
+
enough for the JavaScript scanner to beat a ripgrep process spawn. The probe
|
|
73
|
+
is the work — the enumeration it performs is reused by the scanner, so
|
|
74
|
+
choosing costs nothing when the scanner wins.
|
|
75
|
+
See [ADR-0004](docs/adr/0004-adaptive-engine.md).
|
|
76
|
+
- **Mutation testing** with Stryker, wired into CI on every push, plus the tests
|
|
77
|
+
that closed the gaps it exposed. See [ADR-0003](docs/adr/0003-mutation-testing.md).
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
|
|
81
|
+
- Published as `@descent-vtt/spec-guard`. The binary is still plain
|
|
82
|
+
`spec-guard`.
|
|
83
|
+
|
|
84
|
+
## 0.1.0
|
|
85
|
+
|
|
86
|
+
Initial release: `@assert-absence`, `@assert-count` and `@assert-present`,
|
|
87
|
+
written as HTML comments that are invisible in every Markdown renderer. ripgrep
|
|
88
|
+
primary with a pure-JavaScript fallback of identical semantics, safe assertion
|
|
89
|
+
batching, `--json`, and exit code 2 for "spec-guard could not run" as distinct
|
|
90
|
+
from "your specs failed".
|
package/README.md
CHANGED
|
@@ -120,6 +120,85 @@ may use double or single quotes, and a bare attribute means `="true"`.
|
|
|
120
120
|
Fails when the symbol occurs more than `expected` times (default `0`).
|
|
121
121
|
`max="..."` is accepted as a synonym for `expected`.
|
|
122
122
|
|
|
123
|
+
### `exclude` - everywhere except
|
|
124
|
+
|
|
125
|
+
Most real rules are "nowhere except one place", not "not here":
|
|
126
|
+
|
|
127
|
+
```md
|
|
128
|
+
<!-- @assert-absence target="src" symbol="process.env" exclude="src/config/**" -->
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
That is the rule this README opens with - *no secret is ever read from
|
|
132
|
+
`process.env` outside `src/config`* - written as one assertion instead of a
|
|
133
|
+
hand-maintained list of every directory that is not `src/config`.
|
|
134
|
+
|
|
135
|
+
`exclude` follows gitignore rules, which are **not** the same as `glob`'s:
|
|
136
|
+
|
|
137
|
+
| Pattern | Excludes |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `tests` | any directory or file named `tests`, at any depth, and everything under it |
|
|
140
|
+
| `src/config` | that directory and everything under it |
|
|
141
|
+
| `src/config/**` | the same, written explicitly |
|
|
142
|
+
| `*.test.ts` | any file with that name shape, at any depth |
|
|
143
|
+
|
|
144
|
+
The difference is deliberate. `glob="*.ts"` filters files, so basename matching
|
|
145
|
+
is what you want; `exclude="tests"` means the directory, because that is what
|
|
146
|
+
people mean when they write it - and it is what `rg -g '!tests'` does. Both
|
|
147
|
+
engines implement the same rule, and a parity matrix asserts they agree on it.
|
|
148
|
+
|
|
149
|
+
List attributes accept commas or whitespace, so both of these work:
|
|
150
|
+
|
|
151
|
+
```md
|
|
152
|
+
<!-- @assert-absence target="src" symbol="TODO" exclude="src/legacy/** tests" -->
|
|
153
|
+
<!-- @assert-absence target="src" symbol="TODO" exclude="src/legacy/**,tests" -->
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
(A path containing a space therefore cannot be written; there is no quoting
|
|
157
|
+
inside an attribute value.)
|
|
158
|
+
|
|
159
|
+
### `comments` - the note about a deletion is not the deletion
|
|
160
|
+
|
|
161
|
+
You delete a symbol and leave the explanation where the next person will look:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
// LegacyThing was removed in ADR-398; do not reintroduce it.
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
A plain text search reads that comment as an occurrence, so the assertion that
|
|
168
|
+
keeps the symbol deleted fails on the sentence proving it was deleted. Both
|
|
169
|
+
fixes are bad: delete the note and lose the reason, or delete the rule.
|
|
170
|
+
|
|
171
|
+
So matches inside comments do not count:
|
|
172
|
+
|
|
173
|
+
```md
|
|
174
|
+
<!-- comments are ignored by default -->
|
|
175
|
+
<!-- @assert-absence target="src" symbol="LegacyThing" -->
|
|
176
|
+
|
|
177
|
+
<!-- ...unless you ask for them -->
|
|
178
|
+
<!-- @assert-absence target="src" symbol="Copyright" comments="include" -->
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`comments="include"` is right for assertions that really are about text — a
|
|
182
|
+
licence header, or a name that must appear nowhere in the repository at all.
|
|
183
|
+
|
|
184
|
+
Because this is the one thing that can turn a failing run green without anyone
|
|
185
|
+
touching code, a run that passed this way says so:
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
⚠ 1 match inside comments was not counted; add comments="include" to count it
|
|
189
|
+
|
|
190
|
+
1 passed · 7ms
|
|
191
|
+
✔ every spec assertion holds
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Comment syntax is known for around 59 extensions across 9 families (JS/TS, C,
|
|
195
|
+
C#, Rust, Go, Python-style `#`, SQL-style `--`, markup, and formats with no
|
|
196
|
+
comments at all). Strings are tracked too, because `//` inside a URL is not a
|
|
197
|
+
comment and reading it as one would hide real code. Where spec-guard is unsure —
|
|
198
|
+
an unknown extension, an unterminated literal — the text counts as code, and the
|
|
199
|
+
report says which files it could not classify. A match wrongly kept is a visible
|
|
200
|
+
failure you can argue with; a match wrongly dropped is a lie.
|
|
201
|
+
|
|
123
202
|
### `@assert-count` - this symbol occurs exactly / at least / at most N times
|
|
124
203
|
|
|
125
204
|
```md
|
|
@@ -132,6 +211,67 @@ Fails when the symbol occurs more than `expected` times (default `0`).
|
|
|
132
211
|
Requires `expected`, or `min` and/or `max`. `expected` cannot be combined with
|
|
133
212
|
`min`/`max`.
|
|
134
213
|
|
|
214
|
+
An exact count is the most brittle assertion in the set: a test that merely
|
|
215
|
+
mentions the symbol will break it. spec-guard does not silently skip test files
|
|
216
|
+
for you - an assertion that quietly ignores part of the tree is the failure mode
|
|
217
|
+
this tool exists to prevent - so say what you mean:
|
|
218
|
+
|
|
219
|
+
```md
|
|
220
|
+
<!-- @assert-count target="src" symbol="UserSessionManager" expected="1" exclude="*.test.ts *.spec.ts" -->
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`min="1"` is often the better rule anyway: it says "this exists" without
|
|
224
|
+
breaking every time someone writes a second test.
|
|
225
|
+
|
|
226
|
+
### `@assert-import-absence` / `@assert-import-count` - dependencies, not text
|
|
227
|
+
|
|
228
|
+
The rules architecture documents actually contain are usually about
|
|
229
|
+
dependencies, and a text search answers those badly in both directions - it
|
|
230
|
+
matches a mention in a comment, and it misses `export * from '../db'`.
|
|
231
|
+
|
|
232
|
+
```md
|
|
233
|
+
<!-- @assert-import-absence target="src/ui" module="src/db" reason="the UI talks to services, not storage" -->
|
|
234
|
+
<!-- @assert-import-count target="src" module="axios" max="1" -->
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
These read the dependency rather than the text. A JavaScript/TypeScript
|
|
238
|
+
tokenizer - comments, strings, template substitutions and regular expressions,
|
|
239
|
+
no parser and no new dependency - extracts module references and counts the
|
|
240
|
+
**files** that depend on the module. It understands:
|
|
241
|
+
|
|
242
|
+
| Form | Counted |
|
|
243
|
+
| --- | --- |
|
|
244
|
+
| `import { A } from 'x'`, `import 'x'`, `import * as ns from 'x'` | yes |
|
|
245
|
+
| `import type { A } from 'x'` | yes, unless `types="ignore"` |
|
|
246
|
+
| `export * from 'x'`, `export { A } from 'x'` | yes - a re-export is a dependency, and a barrel file is how layering rules usually get broken |
|
|
247
|
+
| `require('x')` | yes, with a literal argument |
|
|
248
|
+
| `import('x')` | yes, with a literal argument |
|
|
249
|
+
| `import(name)`, `require(expr)` | **no - reported, see below** |
|
|
250
|
+
|
|
251
|
+
Relative specifiers are resolved by path arithmetic, so `module="src/db"`
|
|
252
|
+
matches `../db/client.js` seen from `src/ui/`. There is deliberately no
|
|
253
|
+
filesystem resolution: tsconfig `paths` aliases and package `exports` maps are
|
|
254
|
+
not followed, so an alias is written out as itself (`module="@app/db"`).
|
|
255
|
+
|
|
256
|
+
**It tells you what it could not see.** Roughly 1% of module references in real
|
|
257
|
+
code are dynamic, and about 0.09% of files defeat the tokenizer outright. Both
|
|
258
|
+
are reported rather than counted as clean:
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
✔ docs/adr.md:1 @assert-import-absence "src/db" (0 matches) in src/core
|
|
262
|
+
⚠ 1 module reference could not be resolved statically
|
|
263
|
+
⚠ src/core/plugin.ts:1 import(name)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The count is still true of everything that could be seen; the warning is what
|
|
267
|
+
stops it being mistaken for a complete answer. `--strict` turns those warnings
|
|
268
|
+
into failures. Files in scope that are not JavaScript or TypeScript are counted
|
|
269
|
+
and reported too, so a rule pointed at the wrong tree says "analysed 1 of 3
|
|
270
|
+
files" rather than quietly passing.
|
|
271
|
+
|
|
272
|
+
[ADR-0005](docs/adr/0005-import-assertions.md) has the measurements and the
|
|
273
|
+
reasoning behind each boundary.
|
|
274
|
+
|
|
135
275
|
### `@assert-present` - this file exists
|
|
136
276
|
|
|
137
277
|
```md
|
|
@@ -150,10 +290,14 @@ Passes when every listed path exists relative to `--root`. Directories count.
|
|
|
150
290
|
| `file` | present | Comma-separated paths that must exist |
|
|
151
291
|
| `expected` | absence, count | Upper bound for absence; exact count for count |
|
|
152
292
|
| `min` / `max` | count (`max` also on absence) | Inclusive bounds |
|
|
153
|
-
| `glob` | absence, count |
|
|
293
|
+
| `glob` | absence, count | Include-only file filters, e.g. `*.ts,*.tsx` (ripgrep `-g` semantics) |
|
|
294
|
+
| `exclude` | absence, count | Paths to leave out, gitignore-style: `src/config/**`, `tests`, `*.test.ts` |
|
|
154
295
|
| `regex` | absence, count | Treat `symbol` as a regular expression |
|
|
155
296
|
| `word` | absence, count | Require word boundaries, so `Primary` does not match `PrimaryButton` |
|
|
156
297
|
| `ignore-case` | absence, count | Case-insensitive matching |
|
|
298
|
+
| `comments` | absence, count | `ignore` (default) or `include` for matches inside comments |
|
|
299
|
+
| `module` | import assertions | Which dependency, matched like `exclude` |
|
|
300
|
+
| `types` | import assertions | `include` (default) or `ignore` for `import type` |
|
|
157
301
|
| `reason` | all | Human-readable justification, printed on failure |
|
|
158
302
|
|
|
159
303
|
Unknown attributes are an error, not a shrug: `expct="1"` fails the run instead
|
|
@@ -172,7 +316,8 @@ spec-guard [patterns...] [options]
|
|
|
172
316
|
| `--fail-fast` | Stop at the first failing assertion |
|
|
173
317
|
| `--json` | Machine-readable report on stdout |
|
|
174
318
|
| `--engine <auto\|rg\|js>` | Search engine (default `auto`: scanner for small trees, ripgrep for big ones) |
|
|
175
|
-
| `--strict` | Treat
|
|
319
|
+
| `--strict` | Treat analysis that could not be completed as a failure |
|
|
320
|
+
| `--allow-missing-targets` | Warn instead of failing when a `target` path does not exist |
|
|
176
321
|
| `--include-specs` | Also count matches inside the spec files themselves |
|
|
177
322
|
| `--max-snippets <n>` | Failure snippets per assertion (default 5) |
|
|
178
323
|
| `--concurrency <n>` | Search passes in flight at once (default 8) |
|
|
@@ -317,7 +462,10 @@ implementation departs from the obvious reading of the brief, here is why.
|
|
|
317
462
|
"`LegacyGateway` must not appear" contains the string `LegacyGateway`. Without
|
|
318
463
|
this rule, absence assertions would fail on the document asserting them - the
|
|
319
464
|
single most confusing possible first-run experience. `--include-specs` restores
|
|
320
|
-
the naive behaviour
|
|
465
|
+
the naive behaviour, and counts the prose of your specs; the directives
|
|
466
|
+
themselves stay uncounted, because a directive is an HTML comment and a rule
|
|
467
|
+
whose own text trips it can never be satisfied. `comments="include"` counts even
|
|
468
|
+
those.
|
|
321
469
|
|
|
322
470
|
**Fenced code and inline code are masked before parsing.** A README documenting
|
|
323
471
|
the syntax must not execute it. Masking preserves byte offsets, so reported line
|
|
@@ -352,6 +500,18 @@ count.
|
|
|
352
500
|
**Exit code 2 exists.** "Your specs failed" and "spec-guard could not run" are
|
|
353
501
|
different facts, and CI should be able to tell them apart.
|
|
354
502
|
|
|
503
|
+
**A missing `target` fails the run.** It used to warn and search what was left,
|
|
504
|
+
which meant an assertion pointed at a renamed directory searched nothing, found
|
|
505
|
+
nothing, and reported success — the exact shape of a green check that verified
|
|
506
|
+
nothing. `--allow-missing-targets` restores the old behaviour for repositories
|
|
507
|
+
where a path is legitimately optional.
|
|
508
|
+
|
|
509
|
+
**Comments are excluded by default, and the exclusion is reported.** Counting
|
|
510
|
+
the note that records a deletion as an occurrence of the thing deleted punishes
|
|
511
|
+
the documentation this tool exists to keep honest ([ADR-0006](docs/adr/0006-comment-classification.md)).
|
|
512
|
+
The reverse risk — a rule that quietly stops checking anything because every
|
|
513
|
+
match now sits in a comment — is why every run says how many matches it dropped.
|
|
514
|
+
|
|
355
515
|
## spec-guard checks itself
|
|
356
516
|
|
|
357
517
|
The invariants in [`docs/adr/0001-invariants.md`](docs/adr/0001-invariants.md)
|
|
@@ -363,7 +523,7 @@ build fails if they stop being true.
|
|
|
363
523
|
|
|
364
524
|
This README is executable too:
|
|
365
525
|
|
|
366
|
-
<!-- @assert-present file="src/parser.ts,src/engine.ts,src/reporter.ts,src/runner.ts,src/cli.ts" -->
|
|
526
|
+
<!-- @assert-present file="src/parser.ts,src/engine.ts,src/reporter.ts,src/runner.ts,src/cli.ts,src/comments.ts" -->
|
|
367
527
|
|
|
368
528
|
## Development
|
|
369
529
|
|
|
@@ -372,7 +532,7 @@ npm install
|
|
|
372
532
|
npm run build # tsc -> dist/
|
|
373
533
|
npm test # vitest
|
|
374
534
|
npm run test:coverage
|
|
375
|
-
npm run test:mutation # stryker (~
|
|
535
|
+
npm run test:mutation # stryker (~16 min in CI; much slower locally)
|
|
376
536
|
npm run lint # tsc --noEmit
|
|
377
537
|
npm run selfcheck # run spec-guard on its own docs
|
|
378
538
|
```
|
|
@@ -386,8 +546,8 @@ prebuilt binary and is never a runtime dependency.
|
|
|
386
546
|
### Mutation testing
|
|
387
547
|
|
|
388
548
|
Coverage says a line ran. It does not say an assertion would notice if the line
|
|
389
|
-
behaved differently. This repository measures the difference: **
|
|
390
|
-
2,
|
|
549
|
+
behaved differently. This repository measures the difference: **84.63%** of
|
|
550
|
+
2,949 mutants are killed in CI, against high line coverage.
|
|
391
551
|
|
|
392
552
|
That gap is the point. The first run scored 77.23%, and the weakest file was the
|
|
393
553
|
reporter at 65.48% - not because it lacked tests, but because its tests were
|
|
@@ -397,16 +557,17 @@ test still passed. Exact whole-output comparison took it to 87.30%. The engine
|
|
|
397
557
|
and the walker were then rewritten for testability rather than papered over with
|
|
398
558
|
more tests, which is what moved them from 75%/78% to 83%/93%.
|
|
399
559
|
|
|
400
|
-
`npm run test:mutation` runs it locally and takes about
|
|
401
|
-
|
|
560
|
+
`npm run test:mutation` runs it locally and takes about an hour; CI runs it on
|
|
561
|
+
**every push**, gated at 84%. The CI figure is the one
|
|
402
562
|
quoted above, and it is the lower of the two: the same commit scores 88.76% on
|
|
403
563
|
Windows, where three times as many mutants hang and Stryker counts a hang as a
|
|
404
564
|
kill.
|
|
405
565
|
|
|
406
|
-
|
|
566
|
+
Four cautionary tales are in [ADR-0003](docs/adr/0003-mutation-testing.md): a
|
|
407
567
|
run whose score was pure fiction because the mutants were never activated, a
|
|
408
|
-
tuning knob that lifted the score six points without adding a test,
|
|
409
|
-
|
|
568
|
+
tuning knob that lifted the score six points without adding a test, the platform
|
|
569
|
+
gap above, and the baseline being re-anchored when a tokenizer arrived - along
|
|
570
|
+
with the real bug that chasing the gate uncovered.
|
|
410
571
|
|
|
411
572
|
## Requirements
|
|
412
573
|
|
package/dist/cli.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export interface CliOptions {
|
|
|
24
24
|
failFast: boolean;
|
|
25
25
|
json: boolean;
|
|
26
26
|
engine: EnginePreference;
|
|
27
|
+
allowMissingTargets: boolean;
|
|
27
28
|
strictTargets: boolean;
|
|
28
29
|
includeSpecs: boolean;
|
|
29
30
|
allowEmpty: boolean;
|
|
@@ -35,7 +36,7 @@ export interface CliOptions {
|
|
|
35
36
|
}
|
|
36
37
|
export declare class UsageError extends Error {
|
|
37
38
|
}
|
|
38
|
-
export declare const HELP = "spec-guard - Executable architecture assertions for Markdown specs & ADRs\n\nUsage\n spec-guard [patterns...] [options]\n\nPatterns\n Globs or paths to the Markdown specs to execute. A directory expands to the\n Markdown files inside it. Defaults to \"docs/**/*.md\" when omitted.\n\nOptions\n -r, --root <path> Codebase root that assertions are resolved against (default: cwd)\n -v, --verbose Print passing assertions too\n --fail-fast Stop at the first failing assertion\n --json Emit a machine-readable JSON report\n --engine <name> auto | rg | js (default: auto - scanner for small trees, ripgrep for big ones)\n --strict Treat a target
|
|
39
|
+
export declare const HELP = "spec-guard - Executable architecture assertions for Markdown specs & ADRs\n\nUsage\n spec-guard [patterns...] [options]\n\nPatterns\n Globs or paths to the Markdown specs to execute. A directory expands to the\n Markdown files inside it. Defaults to \"docs/**/*.md\" when omitted.\n\nOptions\n -r, --root <path> Codebase root that assertions are resolved against (default: cwd)\n -v, --verbose Print passing assertions too\n --fail-fast Stop at the first failing assertion\n --json Emit a machine-readable JSON report\n --engine <name> auto | rg | js (default: auto - scanner for small trees, ripgrep for big ones)\n --strict Treat analysis that could not be completed as a failure\n --allow-missing-targets\n Tolerate target paths that do not exist (they fail by default)\n --include-specs Also count matches inside the spec files themselves\n --max-snippets <n> Failure snippets per assertion (default: 5)\n --concurrency <n> Assertions executed in parallel (default: 8)\n --allow-empty Exit 0 when no spec files matched\n --color/--no-color Force colour on or off (NO_COLOR is honoured)\n -h, --help Show this help\n --version Print the version\n\nDirectives\n <!-- @assert-absence target=\"src/\" symbol=\"LegacyGateway\" exclude=\"src/legacy/**\" -->\n <!-- @assert-count target=\"src/\" symbol=\"SessionManager\" expected=\"1\" -->\n <!-- @assert-present file=\"SECURITY.md\" -->\n\n Matches inside comments do not count; add comments=\"include\" to count them.\n\nExit codes\n 0 all assertions passed 1 an assertion failed 2 spec-guard could not run";
|
|
39
40
|
/** Minimal, dependency-free argv parser. Supports `--flag value` and `--flag=value`. */
|
|
40
41
|
export declare function parseArgs(argv: readonly string[], cwd: string): CliOptions;
|
|
41
42
|
/** Runs the CLI and resolves to the process exit code. */
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,eAAO,MAAM,OAAO,IAAI,CAAC;AACzB,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAaxC,eAAO,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,eAAO,MAAM,OAAO,IAAI,CAAC;AACzB,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC;IACzB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAaxC,eAAO,MAAM,IAAI,otDAkC8D,CAAC;AAgChF,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAuG1E;AAYD,0DAA0D;AAC1D,wBAAsB,IAAI,CAAC,IAAI,GAAE,SAAS,MAAM,EAA0B,EAAE,EAAE,GAAE,KAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgEpH"}
|
package/dist/cli.js
CHANGED
|
@@ -41,7 +41,9 @@ Options
|
|
|
41
41
|
--fail-fast Stop at the first failing assertion
|
|
42
42
|
--json Emit a machine-readable JSON report
|
|
43
43
|
--engine <name> auto | rg | js (default: auto - scanner for small trees, ripgrep for big ones)
|
|
44
|
-
--strict Treat
|
|
44
|
+
--strict Treat analysis that could not be completed as a failure
|
|
45
|
+
--allow-missing-targets
|
|
46
|
+
Tolerate target paths that do not exist (they fail by default)
|
|
45
47
|
--include-specs Also count matches inside the spec files themselves
|
|
46
48
|
--max-snippets <n> Failure snippets per assertion (default: ${DEFAULT_MAX_SNIPPETS})
|
|
47
49
|
--concurrency <n> Assertions executed in parallel (default: ${DEFAULT_CONCURRENCY})
|
|
@@ -51,10 +53,12 @@ Options
|
|
|
51
53
|
--version Print the version
|
|
52
54
|
|
|
53
55
|
Directives
|
|
54
|
-
<!-- @assert-absence target="src/" symbol="LegacyGateway" -->
|
|
56
|
+
<!-- @assert-absence target="src/" symbol="LegacyGateway" exclude="src/legacy/**" -->
|
|
55
57
|
<!-- @assert-count target="src/" symbol="SessionManager" expected="1" -->
|
|
56
58
|
<!-- @assert-present file="SECURITY.md" -->
|
|
57
59
|
|
|
60
|
+
Matches inside comments do not count; add comments="include" to count them.
|
|
61
|
+
|
|
58
62
|
Exit codes
|
|
59
63
|
0 all assertions passed 1 an assertion failed 2 spec-guard could not run`;
|
|
60
64
|
const ENGINE_ALIASES = {
|
|
@@ -65,6 +69,15 @@ const ENGINE_ALIASES = {
|
|
|
65
69
|
javascript: 'javascript',
|
|
66
70
|
node: 'javascript',
|
|
67
71
|
};
|
|
72
|
+
/**
|
|
73
|
+
* Takes the next argv element as a value.
|
|
74
|
+
*
|
|
75
|
+
* A leading dash means the value was omitted and the next option got swallowed
|
|
76
|
+
* ("--root --verbose"). That heuristic only applies here: in the `--opt=value`
|
|
77
|
+
* form there is nothing ambiguous about a leading dash, so "--max-snippets=-1"
|
|
78
|
+
* should be told it wants a non-negative integer rather than that it is missing
|
|
79
|
+
* a value.
|
|
80
|
+
*/
|
|
68
81
|
function requireValue(name, value) {
|
|
69
82
|
if (value === undefined || value.startsWith('-')) {
|
|
70
83
|
throw new UsageError(`Option ${name} requires a value.`);
|
|
@@ -85,6 +98,7 @@ export function parseArgs(argv, cwd) {
|
|
|
85
98
|
failFast: false,
|
|
86
99
|
json: false,
|
|
87
100
|
engine: 'auto',
|
|
101
|
+
allowMissingTargets: false,
|
|
88
102
|
strictTargets: false,
|
|
89
103
|
includeSpecs: false,
|
|
90
104
|
allowEmpty: false,
|
|
@@ -109,7 +123,7 @@ export function parseArgs(argv, cwd) {
|
|
|
109
123
|
const inlineValue = equals === -1 ? undefined : argument.slice(equals + 1);
|
|
110
124
|
const nextValue = () => {
|
|
111
125
|
if (inlineValue !== undefined)
|
|
112
|
-
return
|
|
126
|
+
return inlineValue;
|
|
113
127
|
index += 1;
|
|
114
128
|
return requireValue(name, argv[index]);
|
|
115
129
|
};
|
|
@@ -134,6 +148,9 @@ export function parseArgs(argv, cwd) {
|
|
|
134
148
|
case '--strict':
|
|
135
149
|
options.strictTargets = true;
|
|
136
150
|
break;
|
|
151
|
+
case '--allow-missing-targets':
|
|
152
|
+
options.allowMissingTargets = true;
|
|
153
|
+
break;
|
|
137
154
|
case '--include-specs':
|
|
138
155
|
options.includeSpecs = true;
|
|
139
156
|
break;
|
|
@@ -209,6 +226,7 @@ export async function main(argv = process.argv.slice(2), io = defaultIO()) {
|
|
|
209
226
|
root: options.root,
|
|
210
227
|
engine: options.engine,
|
|
211
228
|
failFast: options.failFast,
|
|
229
|
+
allowMissingTargets: options.allowMissingTargets,
|
|
212
230
|
strictTargets: options.strictTargets,
|
|
213
231
|
includeSpecs: options.includeSpecs,
|
|
214
232
|
concurrency: options.concurrency,
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGtF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC;AACzB,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comment classification.
|
|
3
|
+
*
|
|
4
|
+
* A documented codebase defeats a plain text search in the most ironic way
|
|
5
|
+
* available: the comment recording that a symbol was deleted is itself a match
|
|
6
|
+
* for that symbol. `@assert-absence symbol="LegacyThing"` then fails on
|
|
7
|
+
*
|
|
8
|
+
* // LegacyThing was removed in ADR-398
|
|
9
|
+
*
|
|
10
|
+
* which is the sentence proving the assertion true. So matches are classified
|
|
11
|
+
* and, by default, those inside comments do not count.
|
|
12
|
+
*
|
|
13
|
+
* The scanner tracks strings as well as comments, and it has to: a `//` inside
|
|
14
|
+
* a string literal is not a comment, and misreading one is the dangerous
|
|
15
|
+
* direction. Consider
|
|
16
|
+
*
|
|
17
|
+
* const url = "http://example.com"; const x = LegacyThing;
|
|
18
|
+
*
|
|
19
|
+
* Treating `//` in that URL as a comment start would hide a real use of
|
|
20
|
+
* LegacyThing and turn a violation into a silent pass. Every uncertainty here
|
|
21
|
+
* therefore resolves the same way: **when in doubt, it is code**. A match
|
|
22
|
+
* wrongly kept is a visible failure someone can argue with; a match wrongly
|
|
23
|
+
* dropped is a lie.
|
|
24
|
+
*
|
|
25
|
+
* Languages it does not recognise get no comment ranges at all, so their
|
|
26
|
+
* matches all count - and the result says so rather than implying the file was
|
|
27
|
+
* understood.
|
|
28
|
+
*/
|
|
29
|
+
interface StringRule {
|
|
30
|
+
open: string;
|
|
31
|
+
close: string;
|
|
32
|
+
/** Backslash escapes apply inside. */
|
|
33
|
+
escape: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface CommentSyntax {
|
|
36
|
+
name: string;
|
|
37
|
+
/** Prefixes that comment out the rest of the line. */
|
|
38
|
+
line: readonly string[];
|
|
39
|
+
/** Delimiter pairs for block comments. */
|
|
40
|
+
block: ReadonlyArray<readonly [string, string]>;
|
|
41
|
+
/** Block comments nest, as in Rust. */
|
|
42
|
+
nested: boolean;
|
|
43
|
+
/** Literal forms that may contain comment-looking text. */
|
|
44
|
+
strings: readonly StringRule[];
|
|
45
|
+
}
|
|
46
|
+
/** The comment syntax for a path, or null when the language is unknown. */
|
|
47
|
+
export declare function syntaxFor(filePath: string): CommentSyntax | null;
|
|
48
|
+
/** Half-open [start, end) offsets that are comment text. */
|
|
49
|
+
export type CommentRange = readonly [number, number];
|
|
50
|
+
/**
|
|
51
|
+
* Finds every comment in the source.
|
|
52
|
+
*
|
|
53
|
+
* One pass, tracking strings so that comment markers inside them are ignored.
|
|
54
|
+
* An unterminated comment or string runs to the end of the file, which matches
|
|
55
|
+
* how a compiler would read it.
|
|
56
|
+
*
|
|
57
|
+
* The scan is written so that it cannot stand still: whatever the branches
|
|
58
|
+
* below decide, the loop advances by at least one character. That guarantee is
|
|
59
|
+
* the difference between a wrong answer and a hang, and this loop appends as it
|
|
60
|
+
* goes - standing still here would not spin, it would eat memory until the
|
|
61
|
+
* process died, on somebody's file, in somebody's CI.
|
|
62
|
+
*/
|
|
63
|
+
export declare function commentRanges(source: string, syntax: CommentSyntax): CommentRange[];
|
|
64
|
+
/**
|
|
65
|
+
* Decides whether a byte offset falls inside a comment.
|
|
66
|
+
*
|
|
67
|
+
* `classified` is false when the language is unknown, in which case nothing is
|
|
68
|
+
* treated as a comment. Callers report that rather than implying the file was
|
|
69
|
+
* understood.
|
|
70
|
+
*/
|
|
71
|
+
export interface CommentMask {
|
|
72
|
+
readonly classified: boolean;
|
|
73
|
+
readonly syntax: string | null;
|
|
74
|
+
isComment(offset: number): boolean;
|
|
75
|
+
}
|
|
76
|
+
export declare function createCommentMask(source: string, filePath: string): CommentMask;
|
|
77
|
+
export {};
|
|
78
|
+
//# sourceMappingURL=comments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../src/comments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,0CAA0C;IAC1C,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,uCAAuC;IACvC,MAAM,EAAE,OAAO,CAAC;IAChB,2DAA2D;IAC3D,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;CAChC;AAuHD,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAEhE;AAED,4DAA4D;AAC5D,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAqCrD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,YAAY,EAAE,CAkCnF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;CACpC;AAQD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAqB/E"}
|