@descent-vtt/spec-guard 0.2.0 → 0.4.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 +150 -0
- package/README.md +262 -36
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +27 -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 +62 -40
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +236 -186
- package/dist/engine.js.map +1 -1
- package/dist/glob.d.ts +34 -6
- package/dist/glob.d.ts.map +1 -1
- package/dist/glob.js +50 -28
- 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 +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -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 +67 -2
- package/dist/reporter.js.map +1 -1
- package/dist/runner.d.ts +23 -3
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +274 -73
- package/dist/runner.js.map +1 -1
- package/dist/scope.d.ts +108 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +115 -0
- package/dist/scope.js.map +1 -0
- package/dist/types.d.ts +47 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -6
- package/dist/types.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
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.4.0
|
|
9
|
+
|
|
10
|
+
An audit found spec-guard reporting a clean pass on a repository that contained
|
|
11
|
+
the forbidden symbol. On a tree with eight copies of one token, the scanner
|
|
12
|
+
found two and ripgrep found four, and neither said anything about the rest.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **Hidden directories are searched.** `.github`, `.husky`, `.claude-rules` and
|
|
17
|
+
`.agents` hold CI, hooks and agent rules, and both engines skipped them
|
|
18
|
+
entirely - so an absence assertion passed while the forbidden thing sat in a
|
|
19
|
+
workflow file.
|
|
20
|
+
- **The skip list is now four names**: `.git`, `.hg`, `.svn`, `node_modules`.
|
|
21
|
+
`dist`, `build`, `out`, `coverage`, `.next` and eight others are searched,
|
|
22
|
+
because spec-guard cannot tell build output from a directory of build scripts,
|
|
23
|
+
and guessing wrong means a rule silently covers nothing. **If a run starts
|
|
24
|
+
failing on your build output, that is this change, and the fix is to say so:**
|
|
25
|
+
|
|
26
|
+
```md
|
|
27
|
+
<!-- @assert-absence target="." symbol="TODO" exclude="dist coverage" -->
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- **`.gitignore` is no longer consulted.** It describes what git should carry,
|
|
31
|
+
not what a rule covers - and ripgrep applies it only inside a git repository,
|
|
32
|
+
so the same tree gave different answers depending on whether a `.git`
|
|
33
|
+
directory existed above it.
|
|
34
|
+
- **Binary files are searched, and a match in one is reported** rather than
|
|
35
|
+
dropped. It is not counted as a violation, but it is no longer invisible.
|
|
36
|
+
"Binary" now means "contains a NUL byte" for both engines; the scanner used to
|
|
37
|
+
look only at the first 8KB, which disagreed with ripgrep on files whose first
|
|
38
|
+
NUL came later.
|
|
39
|
+
- **`--strict` fails when a file could not be inspected**, which now has a
|
|
40
|
+
precise meaning: unreadable, or binary and containing the symbol.
|
|
41
|
+
- **The engines are one implementation.** ripgrep now answers only *which files
|
|
42
|
+
contain this text*; the scanner does all counting, comment classification,
|
|
43
|
+
binary handling and reporting for both. `--engine` changes how long a run
|
|
44
|
+
takes, not what it concludes. See [ADR-0007](docs/adr/0007-search-scope.md).
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- **`--no-default-skips`** to search even those four directories.
|
|
49
|
+
- **`skipped`** on each result in `--json`: what was not inspected, and why.
|
|
50
|
+
|
|
51
|
+
### Removed
|
|
52
|
+
|
|
53
|
+
- `DEFAULT_IGNORED_DIRECTORIES`, `canBatchLiterals`, `shouldBatchPatterns`,
|
|
54
|
+
`createRipgrepSink` and `byteColumnToCharacter` from the public API. All of
|
|
55
|
+
them existed to make ripgrep's own counting trustworthy; as a pre-filter it
|
|
56
|
+
does not count, so pattern batching no longer has to be proved safe and
|
|
57
|
+
ripgrep's byte columns no longer need converting.
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- ripgrep's per-file errors are no longer discarded. `--no-messages` meant a
|
|
62
|
+
file that could not be opened produced no match, no error, and no way to tell
|
|
63
|
+
it apart from one that was read and found clean.
|
|
64
|
+
- `comments="include"` no longer changes whether binary files are searched. An
|
|
65
|
+
attribute about comments decided that, because it selected a different code
|
|
66
|
+
path through the engine.
|
|
67
|
+
|
|
68
|
+
## 0.3.0
|
|
69
|
+
|
|
70
|
+
### Added
|
|
71
|
+
|
|
72
|
+
- **`exclude` attribute.** Most real rules are "nowhere except one place", not
|
|
73
|
+
"not here": `exclude="src/config/**"` expresses that in one assertion instead
|
|
74
|
+
of a hand-maintained list of every directory that is not `src/config`. Follows
|
|
75
|
+
gitignore semantics — deliberately different from `glob`'s basename matching —
|
|
76
|
+
and a parity matrix asserts both engines agree.
|
|
77
|
+
- **Import assertions:** `@assert-import-absence` and `@assert-import-count`.
|
|
78
|
+
"The UI layer must not depend on the database layer" is a dependency claim,
|
|
79
|
+
not a text claim, and approximating it with a string search fails in both
|
|
80
|
+
directions. A zero-dependency tokenizer resolves `import`, `export … from`,
|
|
81
|
+
`import type`, dynamic `import()` with a literal, and `require()`. Module
|
|
82
|
+
references it cannot resolve statically are reported, never counted as clean.
|
|
83
|
+
See [ADR-0005](docs/adr/0005-import-assertions.md).
|
|
84
|
+
- **`comments` attribute.** See Changed, below.
|
|
85
|
+
- **`--allow-missing-targets`** to restore the previous handling of a `target`
|
|
86
|
+
path that does not exist.
|
|
87
|
+
- **`commentMatches` and `unclassifiedFiles`** on every result in `--json`.
|
|
88
|
+
|
|
89
|
+
### Changed
|
|
90
|
+
|
|
91
|
+
- **Matches inside comments no longer count**, so the note recording that a
|
|
92
|
+
symbol was deleted is no longer read as an occurrence of that symbol. This is
|
|
93
|
+
the defect where documenting a decision defeated the assertion enforcing it.
|
|
94
|
+
Comment syntax is known for 59 extensions across 9 families; strings are
|
|
95
|
+
tracked too, since `//` inside a URL is not a comment. Where classification is
|
|
96
|
+
uncertain the text counts as code, and every run reports how many matches it
|
|
97
|
+
excluded — a pass caused by comment exclusion is never silent.
|
|
98
|
+
`comments="include"` restores counting per assertion.
|
|
99
|
+
See [ADR-0006](docs/adr/0006-comment-classification.md).
|
|
100
|
+
- **A `target` path that does not exist now fails the run** instead of warning
|
|
101
|
+
and searching what remained. An assertion pointed at a renamed directory used
|
|
102
|
+
to search nothing, find nothing, and report success. `--allow-missing-targets`
|
|
103
|
+
restores the old behaviour.
|
|
104
|
+
- **`--strict`** now means "treat analysis that could not be completed as a
|
|
105
|
+
failure" — it no longer carries the missing-target meaning, which is now the
|
|
106
|
+
default.
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
|
|
110
|
+
- `search()` on the ripgrep engine was not comment-aware while `searchBatch()`
|
|
111
|
+
was, so a spec containing exactly one assertion kept its comment matches.
|
|
112
|
+
- The two engines reported different columns for the same match on a line
|
|
113
|
+
containing non-ASCII text: ripgrep counts bytes, the scanner counts
|
|
114
|
+
characters, and editors count characters. ripgrep's column is now converted,
|
|
115
|
+
so a reported location points at the match in both engines.
|
|
116
|
+
- Leftover `*.test.ts` files in scratch fixture directories were collected by
|
|
117
|
+
Vitest, breaking a run with "no test suite found" because of debris from a
|
|
118
|
+
previous one.
|
|
119
|
+
|
|
120
|
+
### Notes
|
|
121
|
+
|
|
122
|
+
The version in `package.json` was bumped to 0.3.0 during development but never
|
|
123
|
+
published; npm went 0.1.0 -> 0.2.0. Everything above ships together as the
|
|
124
|
+
first published 0.3.0.
|
|
125
|
+
|
|
126
|
+
## 0.2.0
|
|
127
|
+
|
|
128
|
+
### Added
|
|
129
|
+
|
|
130
|
+
- **Adaptive engine selection.** `--engine auto` now measures rather than
|
|
131
|
+
guesses: a budgeted, stat-only enumeration decides whether a tree is small
|
|
132
|
+
enough for the JavaScript scanner to beat a ripgrep process spawn. The probe
|
|
133
|
+
is the work — the enumeration it performs is reused by the scanner, so
|
|
134
|
+
choosing costs nothing when the scanner wins.
|
|
135
|
+
See [ADR-0004](docs/adr/0004-adaptive-engine.md).
|
|
136
|
+
- **Mutation testing** with Stryker, wired into CI on every push, plus the tests
|
|
137
|
+
that closed the gaps it exposed. See [ADR-0003](docs/adr/0003-mutation-testing.md).
|
|
138
|
+
|
|
139
|
+
### Changed
|
|
140
|
+
|
|
141
|
+
- Published as `@descent-vtt/spec-guard`. The binary is still plain
|
|
142
|
+
`spec-guard`.
|
|
143
|
+
|
|
144
|
+
## 0.1.0
|
|
145
|
+
|
|
146
|
+
Initial release: `@assert-absence`, `@assert-count` and `@assert-present`,
|
|
147
|
+
written as HTML comments that are invisible in every Markdown renderer. ripgrep
|
|
148
|
+
primary with a pure-JavaScript fallback of identical semantics, safe assertion
|
|
149
|
+
batching, `--json`, and exit code 2 for "spec-guard could not run" as distinct
|
|
150
|
+
from "your specs failed".
|
package/README.md
CHANGED
|
@@ -120,6 +120,132 @@ 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
|
+
|
|
202
|
+
## What gets searched
|
|
203
|
+
|
|
204
|
+
An assertion is worth exactly as much as the set of files behind it, so
|
|
205
|
+
spec-guard is explicit about that set and never quietly narrows it.
|
|
206
|
+
|
|
207
|
+
**Four directory names are skipped**, and nothing else:
|
|
208
|
+
|
|
209
|
+
| Skipped | Why |
|
|
210
|
+
| --- | --- |
|
|
211
|
+
| `.git`, `.hg`, `.svn` | version-control stores hold compressed copies of code you deleted on purpose |
|
|
212
|
+
| `node_modules` | code you did not write, which your architecture rules are not about |
|
|
213
|
+
|
|
214
|
+
Everything else is searched. That includes **hidden directories** - `.github`,
|
|
215
|
+
`.husky`, `.claude-rules`, `.agents` - because that is where CI, hooks and agent
|
|
216
|
+
rules live, and a rule that cannot see your workflow files is not enforcing much.
|
|
217
|
+
It also includes `dist`, `build`, `out` and `coverage`, because spec-guard
|
|
218
|
+
cannot tell build output from a directory of build scripts, and guessing wrong
|
|
219
|
+
means a rule silently stops covering anything.
|
|
220
|
+
|
|
221
|
+
`.gitignore` is not consulted. It describes what git should carry, not what a
|
|
222
|
+
rule covers - and ripgrep applies it only inside a git repository, so honouring
|
|
223
|
+
it made the same tree answer differently depending on whether a `.git` directory
|
|
224
|
+
happened to exist above it.
|
|
225
|
+
|
|
226
|
+
To narrow scope, say so in the assertion:
|
|
227
|
+
|
|
228
|
+
```md
|
|
229
|
+
<!-- @assert-absence target="src" symbol="TODO" exclude="dist coverage" -->
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`--no-default-skips` removes even those four, for a run that has to be certain.
|
|
233
|
+
|
|
234
|
+
### Nothing is skipped quietly
|
|
235
|
+
|
|
236
|
+
A file spec-guard could not read, or one whose bytes are not text but which
|
|
237
|
+
contained the symbol anyway, is a gap in the answer rather than a detail of it.
|
|
238
|
+
Those are reported, and `--strict` fails on them:
|
|
239
|
+
|
|
240
|
+
```text
|
|
241
|
+
✖ docs/adr.md:3 @assert-absence
|
|
242
|
+
"ApiKey" must not appear in .
|
|
243
|
+
expected no matches, found 0, and 1 file could not be inspected
|
|
244
|
+
⚠ 1 match in 1 binary file not counted: build/app.bin
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The counts are in `--json` too, as `skipped` on each result.
|
|
248
|
+
|
|
123
249
|
### `@assert-count` - this symbol occurs exactly / at least / at most N times
|
|
124
250
|
|
|
125
251
|
```md
|
|
@@ -132,6 +258,67 @@ Fails when the symbol occurs more than `expected` times (default `0`).
|
|
|
132
258
|
Requires `expected`, or `min` and/or `max`. `expected` cannot be combined with
|
|
133
259
|
`min`/`max`.
|
|
134
260
|
|
|
261
|
+
An exact count is the most brittle assertion in the set: a test that merely
|
|
262
|
+
mentions the symbol will break it. spec-guard does not silently skip test files
|
|
263
|
+
for you - an assertion that quietly ignores part of the tree is the failure mode
|
|
264
|
+
this tool exists to prevent - so say what you mean:
|
|
265
|
+
|
|
266
|
+
```md
|
|
267
|
+
<!-- @assert-count target="src" symbol="UserSessionManager" expected="1" exclude="*.test.ts *.spec.ts" -->
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`min="1"` is often the better rule anyway: it says "this exists" without
|
|
271
|
+
breaking every time someone writes a second test.
|
|
272
|
+
|
|
273
|
+
### `@assert-import-absence` / `@assert-import-count` - dependencies, not text
|
|
274
|
+
|
|
275
|
+
The rules architecture documents actually contain are usually about
|
|
276
|
+
dependencies, and a text search answers those badly in both directions - it
|
|
277
|
+
matches a mention in a comment, and it misses `export * from '../db'`.
|
|
278
|
+
|
|
279
|
+
```md
|
|
280
|
+
<!-- @assert-import-absence target="src/ui" module="src/db" reason="the UI talks to services, not storage" -->
|
|
281
|
+
<!-- @assert-import-count target="src" module="axios" max="1" -->
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
These read the dependency rather than the text. A JavaScript/TypeScript
|
|
285
|
+
tokenizer - comments, strings, template substitutions and regular expressions,
|
|
286
|
+
no parser and no new dependency - extracts module references and counts the
|
|
287
|
+
**files** that depend on the module. It understands:
|
|
288
|
+
|
|
289
|
+
| Form | Counted |
|
|
290
|
+
| --- | --- |
|
|
291
|
+
| `import { A } from 'x'`, `import 'x'`, `import * as ns from 'x'` | yes |
|
|
292
|
+
| `import type { A } from 'x'` | yes, unless `types="ignore"` |
|
|
293
|
+
| `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 |
|
|
294
|
+
| `require('x')` | yes, with a literal argument |
|
|
295
|
+
| `import('x')` | yes, with a literal argument |
|
|
296
|
+
| `import(name)`, `require(expr)` | **no - reported, see below** |
|
|
297
|
+
|
|
298
|
+
Relative specifiers are resolved by path arithmetic, so `module="src/db"`
|
|
299
|
+
matches `../db/client.js` seen from `src/ui/`. There is deliberately no
|
|
300
|
+
filesystem resolution: tsconfig `paths` aliases and package `exports` maps are
|
|
301
|
+
not followed, so an alias is written out as itself (`module="@app/db"`).
|
|
302
|
+
|
|
303
|
+
**It tells you what it could not see.** Roughly 1% of module references in real
|
|
304
|
+
code are dynamic, and about 0.09% of files defeat the tokenizer outright. Both
|
|
305
|
+
are reported rather than counted as clean:
|
|
306
|
+
|
|
307
|
+
```text
|
|
308
|
+
✔ docs/adr.md:1 @assert-import-absence "src/db" (0 matches) in src/core
|
|
309
|
+
⚠ 1 module reference could not be resolved statically
|
|
310
|
+
⚠ src/core/plugin.ts:1 import(name)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
The count is still true of everything that could be seen; the warning is what
|
|
314
|
+
stops it being mistaken for a complete answer. `--strict` turns those warnings
|
|
315
|
+
into failures. Files in scope that are not JavaScript or TypeScript are counted
|
|
316
|
+
and reported too, so a rule pointed at the wrong tree says "analysed 1 of 3
|
|
317
|
+
files" rather than quietly passing.
|
|
318
|
+
|
|
319
|
+
[ADR-0005](docs/adr/0005-import-assertions.md) has the measurements and the
|
|
320
|
+
reasoning behind each boundary.
|
|
321
|
+
|
|
135
322
|
### `@assert-present` - this file exists
|
|
136
323
|
|
|
137
324
|
```md
|
|
@@ -150,10 +337,14 @@ Passes when every listed path exists relative to `--root`. Directories count.
|
|
|
150
337
|
| `file` | present | Comma-separated paths that must exist |
|
|
151
338
|
| `expected` | absence, count | Upper bound for absence; exact count for count |
|
|
152
339
|
| `min` / `max` | count (`max` also on absence) | Inclusive bounds |
|
|
153
|
-
| `glob` | absence, count |
|
|
340
|
+
| `glob` | absence, count | Include-only file filters, e.g. `*.ts,*.tsx` (ripgrep `-g` semantics) |
|
|
341
|
+
| `exclude` | absence, count | Paths to leave out, gitignore-style: `src/config/**`, `tests`, `*.test.ts` |
|
|
154
342
|
| `regex` | absence, count | Treat `symbol` as a regular expression |
|
|
155
343
|
| `word` | absence, count | Require word boundaries, so `Primary` does not match `PrimaryButton` |
|
|
156
344
|
| `ignore-case` | absence, count | Case-insensitive matching |
|
|
345
|
+
| `comments` | absence, count | `ignore` (default) or `include` for matches inside comments |
|
|
346
|
+
| `module` | import assertions | Which dependency, matched like `exclude` |
|
|
347
|
+
| `types` | import assertions | `include` (default) or `ignore` for `import type` |
|
|
157
348
|
| `reason` | all | Human-readable justification, printed on failure |
|
|
158
349
|
|
|
159
350
|
Unknown attributes are an error, not a shrug: `expct="1"` fails the run instead
|
|
@@ -172,7 +363,9 @@ spec-guard [patterns...] [options]
|
|
|
172
363
|
| `--fail-fast` | Stop at the first failing assertion |
|
|
173
364
|
| `--json` | Machine-readable report on stdout |
|
|
174
365
|
| `--engine <auto\|rg\|js>` | Search engine (default `auto`: scanner for small trees, ripgrep for big ones) |
|
|
175
|
-
| `--strict` | Treat
|
|
366
|
+
| `--strict` | Treat analysis that could not be completed as a failure |
|
|
367
|
+
| `--allow-missing-targets` | Warn instead of failing when a `target` path does not exist |
|
|
368
|
+
| `--no-default-skips` | Search `.git`, `.hg`, `.svn` and `node_modules` too |
|
|
176
369
|
| `--include-specs` | Also count matches inside the spec files themselves |
|
|
177
370
|
| `--max-snippets <n>` | Failure snippets per assertion (default 5) |
|
|
178
371
|
| `--concurrency <n>` | Search passes in flight at once (default 8) |
|
|
@@ -247,45 +440,54 @@ parser ──▶ Directive[] ──▶ runner ──▶ Assertion[] ──▶ e
|
|
|
247
440
|
|
|
248
441
|
Scanning a tree costs about the same whether you look for one symbol or twenty,
|
|
249
442
|
so spec-guard groups assertions by target set and flags and answers each group
|
|
250
|
-
in one pass.
|
|
251
|
-
2,000-file, 5.1 MB tree with 8 assertions:
|
|
252
|
-
|
|
253
|
-
| | one pass per assertion | batched (current) |
|
|
254
|
-
| --- | --- | --- |
|
|
255
|
-
| ripgrep engine | ~280 ms | **~72 ms** |
|
|
256
|
-
| JavaScript fallback | ~780 ms | **~283 ms** |
|
|
443
|
+
in one pass.
|
|
257
444
|
|
|
258
445
|
`auto` then picks between the two engines **per search group**, because they
|
|
259
446
|
have different shapes of cost: ripgrep is dominated by process startup and
|
|
260
447
|
barely notices tree size, while the built-in scanner has no startup cost and
|
|
261
448
|
grows linearly. Starting a process to search a handful of files is a bad trade.
|
|
449
|
+
Re-measured for 0.4.0 on Windows 11 / Node 24, median of three, one symbol over
|
|
450
|
+
a synthetic tree:
|
|
451
|
+
|
|
452
|
+
| files | scanner | ripgrep | |
|
|
453
|
+
| --- | --- | --- | --- |
|
|
454
|
+
| 100 | **17 ms** | 214 ms | scanner, 12.9x |
|
|
455
|
+
| 500 | **70 ms** | 263 ms | scanner, 3.8x |
|
|
456
|
+
| 1,000 | 216 ms | **181 ms** | ripgrep, 1.2x |
|
|
457
|
+
| 3,000 | 735 ms | **222 ms** | ripgrep, 3.3x |
|
|
458
|
+
| 5,000 | 1,046 ms | **241 ms** | ripgrep, 4.3x |
|
|
262
459
|
|
|
263
460
|
The decision uses a bounded enumeration as its probe: spec-guard walks the
|
|
264
461
|
target set until it either finishes - in which case the file list is already in
|
|
265
462
|
hand and the scanner runs against it, with no process and no second walk - or
|
|
266
|
-
exceeds a budget, in which case ripgrep takes over.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
463
|
+
exceeds a budget, in which case ripgrep takes over. The crossover above sits
|
|
464
|
+
between 500 and 1,000 files, which is what the Windows budget of 512 encodes;
|
|
465
|
+
on Linux it is far lower, because what is really being measured is process
|
|
466
|
+
spawn cost. `scripts/bench-engines.mjs` reproduces this, and
|
|
467
|
+
[ADR-0004](docs/adr/0004-adaptive-engine.md) has the full tables.
|
|
468
|
+
|
|
469
|
+
Treat single measurements from one machine with suspicion. On the development
|
|
470
|
+
machine used here, an absence assertion over 2,000 files measures 303 ms with
|
|
471
|
+
the scanner and 399 ms with ripgrep, but ripgrep's own spread across five runs
|
|
472
|
+
was 379-1039 ms - wide enough that the two are not really distinguishable at
|
|
473
|
+
that size. The order-of-magnitude differences at the ends of the table are the
|
|
474
|
+
part worth trusting.
|
|
277
475
|
|
|
278
476
|
Two honest caveats:
|
|
279
477
|
|
|
280
478
|
- **The reported engine is the one that ran, not the one available.** On a small
|
|
281
479
|
repository `--json` reports `"engine": "javascript"` even with ripgrep
|
|
282
480
|
installed. That is the optimisation working, not a failure to find `rg`.
|
|
283
|
-
- **
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
481
|
+
- **The engines are one implementation, not two that agree.** This used to say
|
|
482
|
+
that ripgrep was the reference and the scanner merely matched it "on
|
|
483
|
+
everything the test suite covers", with a note that the two treated
|
|
484
|
+
`.gitignore` and ignored directories differently and that you should pin an
|
|
485
|
+
engine if it mattered. It did matter: on a tree with eight copies of a symbol
|
|
486
|
+
the scanner found two and ripgrep found four. Since 0.4.0 ripgrep answers only
|
|
487
|
+
*which files contain this text*, and the scanner does all the counting,
|
|
488
|
+
classification and reporting for both. `--engine` now changes how long a run
|
|
489
|
+
takes and nothing about its verdict, and the suite asserts that on trees built
|
|
490
|
+
from every case that used to split them.
|
|
289
491
|
|
|
290
492
|
## Programmatic API
|
|
291
493
|
|
|
@@ -317,7 +519,10 @@ implementation departs from the obvious reading of the brief, here is why.
|
|
|
317
519
|
"`LegacyGateway` must not appear" contains the string `LegacyGateway`. Without
|
|
318
520
|
this rule, absence assertions would fail on the document asserting them - the
|
|
319
521
|
single most confusing possible first-run experience. `--include-specs` restores
|
|
320
|
-
the naive behaviour
|
|
522
|
+
the naive behaviour, and counts the prose of your specs; the directives
|
|
523
|
+
themselves stay uncounted, because a directive is an HTML comment and a rule
|
|
524
|
+
whose own text trips it can never be satisfied. `comments="include"` counts even
|
|
525
|
+
those.
|
|
321
526
|
|
|
322
527
|
**Fenced code and inline code are masked before parsing.** A README documenting
|
|
323
528
|
the syntax must not execute it. Masking preserves byte offsets, so reported line
|
|
@@ -352,6 +557,26 @@ count.
|
|
|
352
557
|
**Exit code 2 exists.** "Your specs failed" and "spec-guard could not run" are
|
|
353
558
|
different facts, and CI should be able to tell them apart.
|
|
354
559
|
|
|
560
|
+
**A missing `target` fails the run.** It used to warn and search what was left,
|
|
561
|
+
which meant an assertion pointed at a renamed directory searched nothing, found
|
|
562
|
+
nothing, and reported success — the exact shape of a green check that verified
|
|
563
|
+
nothing. `--allow-missing-targets` restores the old behaviour for repositories
|
|
564
|
+
where a path is legitimately optional.
|
|
565
|
+
|
|
566
|
+
**Hidden directories are searched, and `.gitignore` is not consulted.** An
|
|
567
|
+
audit of 0.3.0 found the tool reporting a clean pass on a repository whose
|
|
568
|
+
forbidden symbol sat in `.github/workflows/ci.yml`; the scanner and ripgrep also
|
|
569
|
+
disagreed with each other, finding two matches and four on the same tree. Scope
|
|
570
|
+
is now one policy that both engines are driven by, the skip list is four names
|
|
571
|
+
long, and anything spec-guard could not inspect is reported rather than assumed
|
|
572
|
+
clean ([ADR-0007](docs/adr/0007-search-scope.md)).
|
|
573
|
+
|
|
574
|
+
**Comments are excluded by default, and the exclusion is reported.** Counting
|
|
575
|
+
the note that records a deletion as an occurrence of the thing deleted punishes
|
|
576
|
+
the documentation this tool exists to keep honest ([ADR-0006](docs/adr/0006-comment-classification.md)).
|
|
577
|
+
The reverse risk — a rule that quietly stops checking anything because every
|
|
578
|
+
match now sits in a comment — is why every run says how many matches it dropped.
|
|
579
|
+
|
|
355
580
|
## spec-guard checks itself
|
|
356
581
|
|
|
357
582
|
The invariants in [`docs/adr/0001-invariants.md`](docs/adr/0001-invariants.md)
|
|
@@ -363,7 +588,7 @@ build fails if they stop being true.
|
|
|
363
588
|
|
|
364
589
|
This README is executable too:
|
|
365
590
|
|
|
366
|
-
<!-- @assert-present file="src/parser.ts,src/engine.ts,src/reporter.ts,src/runner.ts,src/cli.ts" -->
|
|
591
|
+
<!-- @assert-present file="src/parser.ts,src/engine.ts,src/reporter.ts,src/runner.ts,src/cli.ts,src/comments.ts" -->
|
|
367
592
|
|
|
368
593
|
## Development
|
|
369
594
|
|
|
@@ -372,7 +597,7 @@ npm install
|
|
|
372
597
|
npm run build # tsc -> dist/
|
|
373
598
|
npm test # vitest
|
|
374
599
|
npm run test:coverage
|
|
375
|
-
npm run test:mutation # stryker (~
|
|
600
|
+
npm run test:mutation # stryker (~16 min in CI; much slower locally)
|
|
376
601
|
npm run lint # tsc --noEmit
|
|
377
602
|
npm run selfcheck # run spec-guard on its own docs
|
|
378
603
|
```
|
|
@@ -386,8 +611,8 @@ prebuilt binary and is never a runtime dependency.
|
|
|
386
611
|
### Mutation testing
|
|
387
612
|
|
|
388
613
|
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,
|
|
614
|
+
behaved differently. This repository measures the difference: **84.63%** of
|
|
615
|
+
2,949 mutants are killed in CI, against high line coverage.
|
|
391
616
|
|
|
392
617
|
That gap is the point. The first run scored 77.23%, and the weakest file was the
|
|
393
618
|
reporter at 65.48% - not because it lacked tests, but because its tests were
|
|
@@ -397,16 +622,17 @@ test still passed. Exact whole-output comparison took it to 87.30%. The engine
|
|
|
397
622
|
and the walker were then rewritten for testability rather than papered over with
|
|
398
623
|
more tests, which is what moved them from 75%/78% to 83%/93%.
|
|
399
624
|
|
|
400
|
-
`npm run test:mutation` runs it locally and takes about
|
|
401
|
-
|
|
625
|
+
`npm run test:mutation` runs it locally and takes about an hour; CI runs it on
|
|
626
|
+
**every push**, gated at 84%. The CI figure is the one
|
|
402
627
|
quoted above, and it is the lower of the two: the same commit scores 88.76% on
|
|
403
628
|
Windows, where three times as many mutants hang and Stryker counts a hang as a
|
|
404
629
|
kill.
|
|
405
630
|
|
|
406
|
-
|
|
631
|
+
Four cautionary tales are in [ADR-0003](docs/adr/0003-mutation-testing.md): a
|
|
407
632
|
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
|
-
|
|
633
|
+
tuning knob that lifted the score six points without adding a test, the platform
|
|
634
|
+
gap above, and the baseline being re-anchored when a tokenizer arrived - along
|
|
635
|
+
with the real bug that chasing the gate uncovered.
|
|
410
636
|
|
|
411
637
|
## Requirements
|
|
412
638
|
|
package/dist/cli.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface CliOptions {
|
|
|
24
24
|
failFast: boolean;
|
|
25
25
|
json: boolean;
|
|
26
26
|
engine: EnginePreference;
|
|
27
|
+
allowMissingTargets: boolean;
|
|
28
|
+
defaultSkips: boolean;
|
|
27
29
|
strictTargets: boolean;
|
|
28
30
|
includeSpecs: boolean;
|
|
29
31
|
allowEmpty: boolean;
|
|
@@ -35,7 +37,7 @@ export interface CliOptions {
|
|
|
35
37
|
}
|
|
36
38
|
export declare class UsageError extends Error {
|
|
37
39
|
}
|
|
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
|
|
40
|
+
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 --no-default-skips Search .git, .hg, .svn and node_modules too\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
41
|
/** Minimal, dependency-free argv parser. Supports `--flag value` and `--flag=value`. */
|
|
40
42
|
export declare function parseArgs(argv: readonly string[], cwd: string): CliOptions;
|
|
41
43
|
/** 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,YAAY,EAAE,OAAO,CAAC;IACtB,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,2xDAmC8D,CAAC;AAgChF,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CA2G1E;AAYD,0DAA0D;AAC1D,wBAAsB,IAAI,CAAC,IAAI,GAAE,SAAS,MAAM,EAA0B,EAAE,EAAE,GAAE,KAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiEpH"}
|
package/dist/cli.js
CHANGED
|
@@ -41,7 +41,10 @@ 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)
|
|
47
|
+
--no-default-skips Search .git, .hg, .svn and node_modules too
|
|
45
48
|
--include-specs Also count matches inside the spec files themselves
|
|
46
49
|
--max-snippets <n> Failure snippets per assertion (default: ${DEFAULT_MAX_SNIPPETS})
|
|
47
50
|
--concurrency <n> Assertions executed in parallel (default: ${DEFAULT_CONCURRENCY})
|
|
@@ -51,10 +54,12 @@ Options
|
|
|
51
54
|
--version Print the version
|
|
52
55
|
|
|
53
56
|
Directives
|
|
54
|
-
<!-- @assert-absence target="src/" symbol="LegacyGateway" -->
|
|
57
|
+
<!-- @assert-absence target="src/" symbol="LegacyGateway" exclude="src/legacy/**" -->
|
|
55
58
|
<!-- @assert-count target="src/" symbol="SessionManager" expected="1" -->
|
|
56
59
|
<!-- @assert-present file="SECURITY.md" -->
|
|
57
60
|
|
|
61
|
+
Matches inside comments do not count; add comments="include" to count them.
|
|
62
|
+
|
|
58
63
|
Exit codes
|
|
59
64
|
0 all assertions passed 1 an assertion failed 2 spec-guard could not run`;
|
|
60
65
|
const ENGINE_ALIASES = {
|
|
@@ -65,6 +70,15 @@ const ENGINE_ALIASES = {
|
|
|
65
70
|
javascript: 'javascript',
|
|
66
71
|
node: 'javascript',
|
|
67
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* Takes the next argv element as a value.
|
|
75
|
+
*
|
|
76
|
+
* A leading dash means the value was omitted and the next option got swallowed
|
|
77
|
+
* ("--root --verbose"). That heuristic only applies here: in the `--opt=value`
|
|
78
|
+
* form there is nothing ambiguous about a leading dash, so "--max-snippets=-1"
|
|
79
|
+
* should be told it wants a non-negative integer rather than that it is missing
|
|
80
|
+
* a value.
|
|
81
|
+
*/
|
|
68
82
|
function requireValue(name, value) {
|
|
69
83
|
if (value === undefined || value.startsWith('-')) {
|
|
70
84
|
throw new UsageError(`Option ${name} requires a value.`);
|
|
@@ -85,6 +99,8 @@ export function parseArgs(argv, cwd) {
|
|
|
85
99
|
failFast: false,
|
|
86
100
|
json: false,
|
|
87
101
|
engine: 'auto',
|
|
102
|
+
allowMissingTargets: false,
|
|
103
|
+
defaultSkips: true,
|
|
88
104
|
strictTargets: false,
|
|
89
105
|
includeSpecs: false,
|
|
90
106
|
allowEmpty: false,
|
|
@@ -109,7 +125,7 @@ export function parseArgs(argv, cwd) {
|
|
|
109
125
|
const inlineValue = equals === -1 ? undefined : argument.slice(equals + 1);
|
|
110
126
|
const nextValue = () => {
|
|
111
127
|
if (inlineValue !== undefined)
|
|
112
|
-
return
|
|
128
|
+
return inlineValue;
|
|
113
129
|
index += 1;
|
|
114
130
|
return requireValue(name, argv[index]);
|
|
115
131
|
};
|
|
@@ -134,6 +150,12 @@ export function parseArgs(argv, cwd) {
|
|
|
134
150
|
case '--strict':
|
|
135
151
|
options.strictTargets = true;
|
|
136
152
|
break;
|
|
153
|
+
case '--allow-missing-targets':
|
|
154
|
+
options.allowMissingTargets = true;
|
|
155
|
+
break;
|
|
156
|
+
case '--no-default-skips':
|
|
157
|
+
options.defaultSkips = false;
|
|
158
|
+
break;
|
|
137
159
|
case '--include-specs':
|
|
138
160
|
options.includeSpecs = true;
|
|
139
161
|
break;
|
|
@@ -209,6 +231,8 @@ export async function main(argv = process.argv.slice(2), io = defaultIO()) {
|
|
|
209
231
|
root: options.root,
|
|
210
232
|
engine: options.engine,
|
|
211
233
|
failFast: options.failFast,
|
|
234
|
+
allowMissingTargets: options.allowMissingTargets,
|
|
235
|
+
defaultSkips: options.defaultSkips,
|
|
212
236
|
strictTargets: options.strictTargets,
|
|
213
237
|
includeSpecs: options.includeSpecs,
|
|
214
238
|
concurrency: options.concurrency,
|