@descent-vtt/spec-guard 0.24.0 → 0.26.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 +102 -0
- package/README.md +59 -22
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +82 -17
- package/dist/cli.js.map +1 -1
- package/dist/comments.d.ts.map +1 -1
- package/dist/comments.js +12 -0
- package/dist/comments.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +2 -4
- package/dist/engine.js.map +1 -1
- package/dist/glob.d.ts.map +1 -1
- package/dist/glob.js +11 -0
- package/dist/glob.js.map +1 -1
- package/dist/polyglot.d.ts +12 -0
- package/dist/polyglot.d.ts.map +1 -1
- package/dist/polyglot.js +150 -36
- package/dist/polyglot.js.map +1 -1
- package/dist/reporter.d.ts +6 -1
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +30 -8
- package/dist/reporter.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js.map +1 -1
- package/dist/text.d.ts +19 -0
- package/dist/text.d.ts.map +1 -1
- package/dist/text.js +21 -0
- package/dist/text.js.map +1 -1
- package/dist/vendor/spec-core/jsonrpc/mcp.js.map +1 -1
- package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -1
- package/dist/vendor/spec-core/markdown/headings.js.map +1 -1
- package/dist/vendor/spec-core/markdown/links.js.map +1 -1
- package/dist/vendor/spec-core/markdown/scan.js.map +1 -1
- package/dist/vendor/spec-core/pattern/automaton.js.map +1 -1
- package/dist/vendor/spec-core/pattern/charset.js.map +1 -1
- package/dist/vendor/spec-core/pattern/regex.js.map +1 -1
- package/dist/vendor/spec-core/text/lines.js.map +1 -1
- package/dist/vendor/spec-core/text/width.js.map +1 -1
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +6 -3
- package/dist/watch.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,108 @@ what to do when upgrading; the family's policy, including how a flag or field
|
|
|
10
10
|
is deprecated before it goes, is spec-core's
|
|
11
11
|
[ADR-0009](https://github.com/DescentVTT/spec-core/blob/main/docs/adr/0009-versions-before-1-0.md).
|
|
12
12
|
|
|
13
|
+
## 0.26.0
|
|
14
|
+
|
|
15
|
+
An option that takes one value is refused given two, exit 2, and so is
|
|
16
|
+
`--json` beside a `--format` that names another format, where the later
|
|
17
|
+
was read with no word of the earlier. And a Python import statement is
|
|
18
|
+
read as Python reads it: `from a import(b)` was read as no import, and an
|
|
19
|
+
import rule passed over it.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **An option that takes one value is refused where it is given two, and
|
|
24
|
+
`--json` beside a `--format` that names another format.** `--json
|
|
25
|
+
--format sarif` wrote SARIF and `--format sarif --json` wrote JSON, with
|
|
26
|
+
no word of the one that lost; and so it was for `--format`, `--root`,
|
|
27
|
+
`--engine`, `--max-snippets`, `--concurrency` and `--depth` given twice
|
|
28
|
+
with two values, on every command that takes them. Each is now exit 2 and
|
|
29
|
+
one line that names both as they were written. The same value given twice
|
|
30
|
+
is read as once, however it is spelled, and so is `--json` beside
|
|
31
|
+
`--format json`; and `--spec` and `--exclude` are lists, as they were.
|
|
32
|
+
Such a line is refused with `--help` or `--version` on it too, as a line
|
|
33
|
+
with a pair has been since 0.25.0: `--json --format sarif --help` printed
|
|
34
|
+
the help and is exit 2
|
|
35
|
+
([ADR-0014](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0014-configuration-and-watch.md)).
|
|
36
|
+
Upgrading: give the option once. A script that adds `--format` or `--root`
|
|
37
|
+
to a line it was handed, to override the one already there, takes that
|
|
38
|
+
one off the line instead.
|
|
39
|
+
- **A Python import statement is read as Python reads it.** `from a
|
|
40
|
+
import(b)`, with no blank before the parenthesis, was read as no import,
|
|
41
|
+
and so were `from a import*`, `from.a import b`, `from . a import b`, a
|
|
42
|
+
dotted name with blanks about its dots, a form feed between two words,
|
|
43
|
+
and a `from` statement whose module has a letter beyond ASCII: an import
|
|
44
|
+
rule passed over each. A statement is now read by Python's own grammar,
|
|
45
|
+
token by token, and reported on the line it stands on, which was another
|
|
46
|
+
for a statement after a `;` that followed one of several lines. Python
|
|
47
|
+
3.15's `lazy import` and `lazy from ... import` are read as the imports
|
|
48
|
+
they are. And a statement Python refuses is read as nothing, where
|
|
49
|
+
`from a import b,`, `import (a)`, `import a b` and `import os, , sys`
|
|
50
|
+
were each read as an import. Of 322 spellings given to Python 3.11.9 and
|
|
51
|
+
to spec-guard, 0.25.0 read 59 otherwise than Python does and found an
|
|
52
|
+
import in 78 that Python refuses; over the 4,341 files of that Python's
|
|
53
|
+
own library both read all 32,777 imports alike
|
|
54
|
+
([ADR-0008](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0008-polyglot-imports.md)).
|
|
55
|
+
Upgrading: an `@assert-import-absence`, `@assert-import-count`,
|
|
56
|
+
`@assert-layers` or `@assert-import-cycle` rule over Python that passed
|
|
57
|
+
may now fail for an import written one of those ways; mend the import it
|
|
58
|
+
names, or give the rule a `baseline`. A rule that failed only for a
|
|
59
|
+
statement Python refuses now passes over it. `impact` lists the
|
|
60
|
+
dependents those imports make, and `logicalLines`, from the package, also
|
|
61
|
+
says where each physical line of a statement begins, as `starts`.
|
|
62
|
+
|
|
63
|
+
## 0.25.0
|
|
64
|
+
|
|
65
|
+
A Python file with CRLF line endings is read as it is read with LF: an
|
|
66
|
+
import rule over one passed in a Windows checkout where the same file
|
|
67
|
+
fails it anywhere else. An option given beside its own opposite is
|
|
68
|
+
refused, exit 2, where the last of the two won with no word of the other.
|
|
69
|
+
And a report asked for in ASCII is ASCII.
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- **An option given beside its own opposite is refused, where the last of
|
|
74
|
+
the two won.** `--strict --no-strict` ran as `--no-strict` alone, and
|
|
75
|
+
`--no-color --color` in colour, with no word of the option that lost: so
|
|
76
|
+
it was for the colour pair and for each on/off option a configuration can
|
|
77
|
+
set, `--strict`, `--allow-missing-targets`, `--allow-empty-scope`,
|
|
78
|
+
`--default-skips`, `--ignore-status` and `--include-specs`, on every
|
|
79
|
+
command that takes both. Each pair is now exit 2 and one line that names
|
|
80
|
+
both, in either order, as `--ascii` beside `--no-ascii` has been since
|
|
81
|
+
0.24.0; that pair's line now reads as the others do. The same option
|
|
82
|
+
given twice is read as once, an opposite alone still wins over a
|
|
83
|
+
configuration, and a command that takes only one of a pair refuses the
|
|
84
|
+
other as it did
|
|
85
|
+
([ADR-0014](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0014-configuration-and-watch.md)).
|
|
86
|
+
Upgrading: give one of the two. A script that adds an opposite to a line
|
|
87
|
+
it was handed, to turn an option off, takes the option off the line
|
|
88
|
+
instead; a configuration is overridden by the opposite alone, as before.
|
|
89
|
+
- **A Python file with CRLF line endings is read as it is read with LF.**
|
|
90
|
+
A line ended at LF alone, so in a CRLF file every line kept its carriage
|
|
91
|
+
return: no `from x import y` was read from the file, and a backslash at
|
|
92
|
+
the end of a line continued nothing, so `import a, \` named a module `\`.
|
|
93
|
+
An import rule over such a file passed where the same file with LF fails
|
|
94
|
+
it, as in a Windows checkout with `core.autocrlf` on. A line now ends
|
|
95
|
+
where Python ends one, at LF, at CRLF and at a lone carriage return, and
|
|
96
|
+
each import is reported on the line it was on. Go, Rust, C#, JavaScript
|
|
97
|
+
and TypeScript were read right already
|
|
98
|
+
([ADR-0008](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0008-polyglot-imports.md)).
|
|
99
|
+
Upgrading: an `@assert-import-absence`, `@assert-import-count`,
|
|
100
|
+
`@assert-layers` or `@assert-import-cycle` rule over Python that passed
|
|
101
|
+
in a CRLF checkout may now fail there, as it does in an LF one; mend the
|
|
102
|
+
import it names, or give the rule a `baseline`. `impact` lists the
|
|
103
|
+
dependents those imports make.
|
|
104
|
+
|
|
105
|
+
### Fixed
|
|
106
|
+
|
|
107
|
+
- A report asked for in ASCII is ASCII. Under `--ascii`, `SPEC_GUARD_ASCII`
|
|
108
|
+
or a Windows console that names no terminal, the lines of a run, `prove`,
|
|
109
|
+
`cites` and a watch session still held `·` between their counts, and a
|
|
110
|
+
snippet whose line was cut still ended in `…`. They are ` - ` and `...`
|
|
111
|
+
now, and so in the text of the server's `check_architecture`, which asks
|
|
112
|
+
for its report in ASCII. Where the marks are drawn nothing changes, and
|
|
113
|
+
`--json` and `--format sarif` carry a cut snippet as they did.
|
|
114
|
+
|
|
13
115
|
## 0.24.0
|
|
14
116
|
|
|
15
117
|
A `cites --strict` that fails for a file read in part, or for a family that
|
package/README.md
CHANGED
|
@@ -231,7 +231,7 @@ These read the dependency rather than the text, in **five languages**:
|
|
|
231
231
|
| Language | Extensions | Reads |
|
|
232
232
|
| --- | --- | --- |
|
|
233
233
|
| JavaScript / TypeScript | `.js .mjs .cjs .jsx .ts .mts .cts .tsx` | `import`, `export ... from`, `require()`, `import()` |
|
|
234
|
-
| Python | `.py .pyi` | `import a.b
|
|
234
|
+
| Python | `.py .pyi` | `import a.b` and `from .rel import x` in every spelling Python's grammar takes, 3.15's `lazy import` among them, and `importlib.import_module("x")` |
|
|
235
235
|
| Go | `.go` | `import "x"` and grouped `import ( ... )`, including aliases and `_` |
|
|
236
236
|
| Rust | `.rs` | `use a::{b, c}` with nested groups, `pub use`, `extern crate` |
|
|
237
237
|
| C# | `.cs .csx` | `using`, `using static`, `global using`, `using X = A.B`, `global::` and extern-alias qualifiers, and a using relative to its namespace |
|
|
@@ -1301,8 +1301,8 @@ spec-guard impact <paths...> # the files that depend on each path, and
|
|
|
1301
1301
|
| `-v, --verbose` | Print passing assertions too |
|
|
1302
1302
|
| `--watch` | Report, then report again as the tree changes, until Ctrl+C |
|
|
1303
1303
|
| `--fail-fast` | Stop at the first failing assertion |
|
|
1304
|
-
| `--json` | Machine-readable report on stdout (same as `--format json`), versioned by `formatVersion` |
|
|
1305
|
-
| `--format <human\|json\|sarif\|github\|gitlab>` | Output format. `sarif` uploads to GitHub code scanning, `github` annotates a pull request from the job's log, `gitlab` is a GitLab Code Quality report |
|
|
1304
|
+
| `--json` | Machine-readable report on stdout (same as `--format json`), versioned by `formatVersion`. Beside a `--format` that names another format it is refused (below) |
|
|
1305
|
+
| `--format <human\|json\|sarif\|github\|gitlab>` | Output format. `sarif` uploads to GitHub code scanning, `github` annotates a pull request from the job's log, `gitlab` is a GitLab Code Quality report. Given twice with two formats it is refused, as every option that takes one value is (below) |
|
|
1306
1306
|
| `--engine <auto\|rg\|js>` | Search engine (default `auto`: scanner for small trees, ripgrep for big ones). ripgrep is `rg` on `PATH`, or the program `SPEC_GUARD_RG` names; `rg` with no ripgrep that runs is exit 2 ([below](#ci-integration)) |
|
|
1307
1307
|
| `--strict` / `--no-strict` | Treat analysis that could not be completed as a failure, and a run that executed no assertion: its specs matched and state no rule in force |
|
|
1308
1308
|
| `--allow-missing-targets` / `--no-allow-missing-targets` | Warn instead of failing when a `target` path does not exist |
|
|
@@ -1317,28 +1317,31 @@ spec-guard impact <paths...> # the files that depend on each path, and
|
|
|
1317
1317
|
| `--depth <n>` | `impact` only: follow dependents at most `n` imports away (default: all) |
|
|
1318
1318
|
| `--allow-empty` | Let a run, `prove` or `impact` through when no spec file matched the patterns, or when one pattern matched none beside others that did, each of which is exit 2 without it; such a pattern is still named on stderr (about the run, not an assertion) |
|
|
1319
1319
|
| `--color` / `--no-color` | Force colour on or off. Without either flag, `NO_COLOR` set to anything turns it off; otherwise `FORCE_COLOR` turns it on, and `FORCE_COLOR=0` or `FORCE_COLOR=false` off; otherwise `TERM=dumb` turns it off; otherwise a terminal has colour. `NO_COLOR` or `FORCE_COLOR` set to nothing is as unset |
|
|
1320
|
-
| `--ascii` / `--no-ascii` |
|
|
1320
|
+
| `--ascii` / `--no-ascii` | Write a report in ASCII, or with the marks, on any platform. Either wins over the environment. Without either, `SPEC_GUARD_ASCII` set to anything but nothing, `0` or `false` asks for ASCII; otherwise Windows decides as below |
|
|
1321
1321
|
|
|
1322
1322
|
Patterns are expanded by spec-guard itself, so quoted globs behave identically
|
|
1323
1323
|
on Windows, macOS and Linux. A directory expands to the Markdown files in it.
|
|
1324
|
-
The second form of each on/off option exists to override a configuration
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
to something: Windows Terminal sets the first, and VS Code's terminal
|
|
1329
|
-
mintty the second. `TERM` is not read for it, since Git for Windows and
|
|
1330
|
-
Cygwin and MSYS2 runtimes set it for whatever they start, in a console
|
|
1331
|
-
fonts hold none of the first three marks. Anywhere else the marks are
|
|
1332
|
-
drawn.
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1324
|
+
The second form of each on/off option exists to override a configuration;
|
|
1325
|
+
beside the first, on one command line, it is refused (below).
|
|
1326
|
+
|
|
1327
|
+
On Windows a report is written in ASCII unless `WT_SESSION` or `TERM_PROGRAM`
|
|
1328
|
+
is set to something: Windows Terminal sets the first, and VS Code's terminal
|
|
1329
|
+
and mintty the second. `TERM` is not read for it, since Git for Windows and
|
|
1330
|
+
the Cygwin and MSYS2 runtimes set it for whatever they start, in a console
|
|
1331
|
+
whose fonts hold none of the first three marks. Anywhere else the marks are
|
|
1332
|
+
always drawn. In ASCII a report holds no character of its own beyond it:
|
|
1333
|
+
`+`, `x`, `!` and `o` for `✔`, `✖`, `⚠` and `○`, `...` for the `…` of what is
|
|
1334
|
+
left out, a cut line among it, and `-` for the `·` between the counts of a
|
|
1335
|
+
line. A path, a line of source or a reason is written as the project wrote
|
|
1336
|
+
it.
|
|
1337
|
+
|
|
1338
|
+
Either can be asked for outright. `--ascii` writes a report in ASCII and
|
|
1339
|
+
`--no-ascii` with the marks, on any platform and whatever the environment
|
|
1340
|
+
says: an ssh session into Windows names no terminal, and `--no-ascii` is how
|
|
1341
|
+
it gets the marks its own terminal draws. `SPEC_GUARD_ASCII=1` asks for ASCII
|
|
1338
1342
|
for every run that inherits it; set to nothing, `0` or `false`, in any case,
|
|
1339
1343
|
it asks for nothing, and it never asks for the marks. An option wins over
|
|
1340
|
-
the variable
|
|
1341
|
-
two would go unread.
|
|
1344
|
+
the variable.
|
|
1342
1345
|
|
|
1343
1346
|
An option that means nothing to a command is refused rather than ignored, so
|
|
1344
1347
|
`spec-guard query --strict` does not look like a strict query. `query` does take
|
|
@@ -1356,6 +1359,40 @@ strict and `--allow-empty=false` let an empty run through. An option a
|
|
|
1356
1359
|
configuration can set has an opposite to turn it off, `--no-strict`; any
|
|
1357
1360
|
other is left out.
|
|
1358
1361
|
|
|
1362
|
+
So is an option given beside its own opposite, in either order: `--strict`
|
|
1363
|
+
beside `--no-strict`, `--color` beside `--no-color`, and every other pair of
|
|
1364
|
+
the table. The last of the two used to win, and nothing said the other had
|
|
1365
|
+
been given; a line that asks for both was not meant, and one of the two would
|
|
1366
|
+
go unread:
|
|
1367
|
+
|
|
1368
|
+
```text
|
|
1369
|
+
spec-guard: Option --strict is given beside --no-strict, and one of the two would go unread. Give one of the two.
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1372
|
+
The same option given twice is read as once. An opposite alone still wins
|
|
1373
|
+
over a configuration, which is what it is for. And where a command takes
|
|
1374
|
+
only one of a pair, as `query` takes `--no-color` and not `--color`, the
|
|
1375
|
+
other is refused for that, as it was.
|
|
1376
|
+
|
|
1377
|
+
So is an option that takes one value and is given two: `--root`, `--format`,
|
|
1378
|
+
`--engine`, `--max-snippets`, `--concurrency` and `--depth`. The later of the
|
|
1379
|
+
two used to be read, and nothing said the earlier had been given. And so is
|
|
1380
|
+
`--json` beside a `--format` that names another format, since `--json` is
|
|
1381
|
+
`--format json`: `--json --format sarif` wrote SARIF, and
|
|
1382
|
+
`--format sarif --json` wrote JSON. Each is named as it was written, in the
|
|
1383
|
+
order the two were given:
|
|
1384
|
+
|
|
1385
|
+
```text
|
|
1386
|
+
spec-guard: Option --json is given beside --format "sarif", and one of the two would go unread. Give one of the two.
|
|
1387
|
+
```
|
|
1388
|
+
|
|
1389
|
+
The same value given twice is read as once, however it is spelled: `--root .`
|
|
1390
|
+
beside `--root ./`, `--format JSON` beside `--format json`, and `--json`
|
|
1391
|
+
beside `--format json`. `--spec` and `--exclude` are lists, and each use adds
|
|
1392
|
+
to its list; `--exclude=` adds nothing to it, and the configuration's list is
|
|
1393
|
+
replaced either way. Such a line is refused with `--help` or `--version` on
|
|
1394
|
+
it too, as a line with a pair is.
|
|
1395
|
+
|
|
1359
1396
|
And so is a spec pattern that matches no file beside others that do. After
|
|
1360
1397
|
`old-docs` was renamed, `spec-guard docs old-docs` ran the specs under `docs`
|
|
1361
1398
|
and said nothing of the rest: every rule in the renamed directory had stopped
|
|
@@ -1941,8 +1978,8 @@ build fails if they stop being true. CI proves every one of those rules can
|
|
|
1941
1978
|
fail, and `spec-guard cites` holds every ADR a comment in the code cites to
|
|
1942
1979
|
existing and being in force.
|
|
1943
1980
|
|
|
1944
|
-
Line coverage is 100%, CI's full mutation sweep kills 99.
|
|
1945
|
-
(0.
|
|
1981
|
+
Line coverage is 100%, CI's full mutation sweep kills 99.26% of 12,993 mutants
|
|
1982
|
+
(0.26.0), and every survivor a release adds is replayed before it ships
|
|
1946
1983
|
([ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md));
|
|
1947
1984
|
[CONTRIBUTING.md](https://github.com/DescentVTT/spec-guard/blob/main/CONTRIBUTING.md)
|
|
1948
1985
|
has how to build, test and release, and
|
package/dist/cli.d.ts
CHANGED
|
@@ -101,7 +101,7 @@ export declare class UsageError extends Error {
|
|
|
101
101
|
* fallback nobody knows still works.
|
|
102
102
|
*/
|
|
103
103
|
export declare function version(manifest?: string): string;
|
|
104
|
-
export declare const HELP = "spec-guard - Executable architecture assertions for Markdown specs & ADRs\n\nUsage\n spec-guard [patterns...] [options] execute the directives in the specs\n spec-guard query <paths...> [options] list the rules in force for files or directories\n spec-guard mcp [options] serve the rules to an AI agent over MCP on stdio\n spec-guard prove [patterns...] [options]\n show each rule a violation of itself, in memory, and report any that pass\n spec-guard cites [paths...] [options] check that each spec a code comment cites exists and is in force\n spec-guard impact <paths...> [options] the files that depend on each path, and the rules in play there\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. query,\n mcp, cites and impact take theirs from --spec.\n\nOptions\n -r, --root <path> Codebase root that assertions are resolved against, a directory (default: cwd)\n --spec <pattern> A spec glob or path; repeatable (default: \"docs/**/*.md\")\n -v, --verbose Print passing assertions too\n --watch Run again as the tree changes, until Ctrl+C (human output only)\n --fail-fast Stop at the first failing assertion\n --json Emit a machine-readable JSON report (same as --format json)\n --format <name> human | json | sarif | github | gitlab (sarif uploads to code scanning,\n github annotates a pull request from the log, gitlab is Code Quality)\n --engine <name> auto | rg | js (default: auto - scanner for small trees, ripgrep for big ones)\n ripgrep is rg on PATH, or the program SPEC_GUARD_RG names; where that\n is no ripgrep that runs, rg is exit 2 and auto leaves it to the scanner\n --strict Treat analysis that could not be completed, and a run that\n executed no assertion, as a failure\n --allow-missing-targets\n Tolerate target paths that do not exist (they fail by default)\n --allow-empty-scope Tolerate assertions whose scope holds no files (they fail by default)\n --print-baseline Print the baseline=\"...\" that would exempt today's violations, and exit\n --no-default-skips Search .git, .hg, .svn and node_modules too\n --exclude <globs> Paths no assertion looks at, beside each directive's exclude; repeatable\n --ignore-status Execute directives in documents not in force too (see Directives)\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, 1 or more (default: 8)\n --depth <n> impact: follow dependents at most n imports away (default: all)\n --allow-empty Let a run, prove or impact through when no spec file matched, or when\n one pattern matched none beside others that did (not an assertion)\n --color/--no-color Force colour on or off (NO_COLOR, FORCE_COLOR and TERM=dumb are honoured)\n --ascii/--no-ascii
|
|
104
|
+
export declare const HELP = "spec-guard - Executable architecture assertions for Markdown specs & ADRs\n\nUsage\n spec-guard [patterns...] [options] execute the directives in the specs\n spec-guard query <paths...> [options] list the rules in force for files or directories\n spec-guard mcp [options] serve the rules to an AI agent over MCP on stdio\n spec-guard prove [patterns...] [options]\n show each rule a violation of itself, in memory, and report any that pass\n spec-guard cites [paths...] [options] check that each spec a code comment cites exists and is in force\n spec-guard impact <paths...> [options] the files that depend on each path, and the rules in play there\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. query,\n mcp, cites and impact take theirs from --spec.\n\nOptions\n -r, --root <path> Codebase root that assertions are resolved against, a directory (default: cwd)\n --spec <pattern> A spec glob or path; repeatable (default: \"docs/**/*.md\")\n -v, --verbose Print passing assertions too\n --watch Run again as the tree changes, until Ctrl+C (human output only)\n --fail-fast Stop at the first failing assertion\n --json Emit a machine-readable JSON report (same as --format json)\n --format <name> human | json | sarif | github | gitlab (sarif uploads to code scanning,\n github annotates a pull request from the log, gitlab is Code Quality)\n --engine <name> auto | rg | js (default: auto - scanner for small trees, ripgrep for big ones)\n ripgrep is rg on PATH, or the program SPEC_GUARD_RG names; where that\n is no ripgrep that runs, rg is exit 2 and auto leaves it to the scanner\n --strict Treat analysis that could not be completed, and a run that\n executed no assertion, as a failure\n --allow-missing-targets\n Tolerate target paths that do not exist (they fail by default)\n --allow-empty-scope Tolerate assertions whose scope holds no files (they fail by default)\n --print-baseline Print the baseline=\"...\" that would exempt today's violations, and exit\n --no-default-skips Search .git, .hg, .svn and node_modules too\n --exclude <globs> Paths no assertion looks at, beside each directive's exclude; repeatable\n --ignore-status Execute directives in documents not in force too (see Directives)\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, 1 or more (default: 8)\n --depth <n> impact: follow dependents at most n imports away (default: all)\n --allow-empty Let a run, prove or impact through when no spec file matched, or when\n one pattern matched none beside others that did (not an assertion)\n --color/--no-color Force colour on or off (NO_COLOR, FORCE_COLOR and TERM=dumb are honoured)\n --ascii/--no-ascii Write a report in ASCII, its marks as + x ! o, or with their Unicode\n forms, on any platform. Without either, SPEC_GUARD_ASCII asks for\n ASCII unless it is empty, 0 or false\n -h, --help Show this help\n --version Print the version\n\n An option that is set and names nothing is refused, exit 2, never read as if\n it were not there: an empty --root or spec pattern, --exclude \",\" (--exclude=\n with nothing clears the list), --concurrency 0, a root that is no directory,\n and a value given to an option that takes none, as in --strict=false.\n\n An option given beside its own opposite is refused the same way, in either\n order, since one of the two would go unread: --strict beside --no-strict,\n --color beside --no-color, and each other pair. The same option given twice\n is read as once.\n\n So is an option that takes one value and is given two, since the first of\n them would go unread: --root a --root b, --format json --format sarif, and\n --json beside a --format that names another format. The same value given\n twice is read as once, as --json beside --format json is. --spec and\n --exclude are lists, and each use adds to its list. Such a line is refused\n with --help or --version on it too, as a line with a pair is.\n\n A spec pattern that matches no file, beside others that do, is refused the\n same way and by name, since none of its rules was read: a run, prove, impact\n and query exit 2. --allow-empty lets the first three through and still names\n it; --watch and mcp carry on, and say which pattern it is; cites names it\n and goes on, and exits 1 for it under --strict (see cites, below).\n\nConfiguration\n package.json in the root can hold specs, exclude, engine, strict,\n allowMissingTargets, allowEmptyScope, ignoreStatus, includeSpecs, defaultSkips,\n maxSnippets and concurrency under \"specGuard\"; a root with no package.json can\n keep them in .spec-guard.json instead, but not in both. A flag wins over the\n file, every on/off option there also takes its opposite (--no-strict,\n --default-skips, ...), and --exclude= with nothing clears exclude.\n \"cites\": [{ \"id\": \"ADR-{n}\", \"files\": \"docs/adr/{n}-*.md\" }] names the\n documents code comments cite; without it, cites reads them off the titles of\n numbered specs.\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 <!-- @assert-import-cycle target=\"src/\" types=\"ignore\" -->\n <!-- @assert-layers target=\"src/\" order=\"domain, application, infrastructure\" -->\n <!-- @assert-structure target=\"src/\" exclude=\"*.test.ts\" partner=\"[name].test.ts\" -->\n\n Matches inside comments do not count; add comments=\"include\" to count them.\n An assertion whose scope holds no files fails; add allow-empty=\"true\" to allow it.\n A document whose status is draft, proposed, rejected, deprecated, superseded\n or archived is reported and not executed; --ignore-status runs it anyway.\n The status is read in English, whatever language the document is written in:\n status: superseded.\n\nprove adds, changes or removes files in memory only - never on disk - to make\nthe violation each rule forbids, and runs the rule over that tree. A rule that\nstill passes survived, and exits 1; one no violation could be made for is\nunprovable, and exits 1 under --strict.\n\ncites reads comment text only - not strings, not Markdown - for ids such as\nADR-0007, ADR-7 and ADR-007, which are one document. A ghost citation names no\ndocument, and exits 1; a stale one names a document superseded, deprecated,\nrejected or archived, and exits 1 under --strict. A family whose files match no\ndocument exits 2. A spec pattern that matches no file beside others that do is\nnamed on stderr. Without \"cites\" in the configuration the families are read off\nthe specs, so one kept under that pattern was not read and no citation of it\nwas looked for: exit 1 under --strict. Families \"cites\" names lose nothing.\n\nimpact follows the import graph backwards: relative JavaScript, TypeScript and\nPython imports that name a file. Go, Rust, C# and absolute Python imports name\nmodules, and are counted rather than followed; an import that names no file is\nlisted, never guessed at.\n\nquery answers from the specs alone, without reading the codebase, so it works\nfor a file that does not exist yet. mcp offers the same answer, a check and\nimpact's, as the tools get_architectural_rules, check_architecture and\nget_dependents.\n\nExit codes\n 0 all assertions passed 1 an assertion failed, or none ran under --strict\n 2 spec-guard could not run 130 a --watch session was stopped";
|
|
105
105
|
/** Minimal, dependency-free argv parser. Supports `--flag value` and `--flag=value`. */
|
|
106
106
|
export declare function parseArgs(argv: readonly string[], cwd: string): CliOptions;
|
|
107
107
|
/**
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -109,9 +109,9 @@ Options
|
|
|
109
109
|
--allow-empty Let a run, prove or impact through when no spec file matched, or when
|
|
110
110
|
one pattern matched none beside others that did (not an assertion)
|
|
111
111
|
--color/--no-color Force colour on or off (NO_COLOR, FORCE_COLOR and TERM=dumb are honoured)
|
|
112
|
-
--ascii/--no-ascii
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
--ascii/--no-ascii Write a report in ASCII, its marks as + x ! o, or with their Unicode
|
|
113
|
+
forms, on any platform. Without either, SPEC_GUARD_ASCII asks for
|
|
114
|
+
ASCII unless it is empty, 0 or false
|
|
115
115
|
-h, --help Show this help
|
|
116
116
|
--version Print the version
|
|
117
117
|
|
|
@@ -120,6 +120,18 @@ Options
|
|
|
120
120
|
with nothing clears the list), --concurrency 0, a root that is no directory,
|
|
121
121
|
and a value given to an option that takes none, as in --strict=false.
|
|
122
122
|
|
|
123
|
+
An option given beside its own opposite is refused the same way, in either
|
|
124
|
+
order, since one of the two would go unread: --strict beside --no-strict,
|
|
125
|
+
--color beside --no-color, and each other pair. The same option given twice
|
|
126
|
+
is read as once.
|
|
127
|
+
|
|
128
|
+
So is an option that takes one value and is given two, since the first of
|
|
129
|
+
them would go unread: --root a --root b, --format json --format sarif, and
|
|
130
|
+
--json beside a --format that names another format. The same value given
|
|
131
|
+
twice is read as once, as --json beside --format json is. --spec and
|
|
132
|
+
--exclude are lists, and each use adds to its list. Such a line is refused
|
|
133
|
+
with --help or --version on it too, as a line with a pair is.
|
|
134
|
+
|
|
123
135
|
A spec pattern that matches no file, beside others that do, is refused the
|
|
124
136
|
same way and by name, since none of its rules was read: a run, prove, impact
|
|
125
137
|
and query exit 2. --allow-empty lets the first three through and still names
|
|
@@ -294,7 +306,7 @@ const NOT_WITH_WATCH = [
|
|
|
294
306
|
['--engine', 'a session always scans in-process, where it can see what each rule reads'],
|
|
295
307
|
];
|
|
296
308
|
/** The long name of an option, whichever way it was spelled. */
|
|
297
|
-
const LONG_NAMES = { '-v': '--verbose' };
|
|
309
|
+
const LONG_NAMES = { '-v': '--verbose', '-r': '--root' };
|
|
298
310
|
/** Minimal, dependency-free argv parser. Supports `--flag value` and `--flag=value`. */
|
|
299
311
|
export function parseArgs(argv, cwd) {
|
|
300
312
|
const command = COMMANDS.has(argv[0]) ? argv[0] : 'check';
|
|
@@ -328,6 +340,14 @@ export function parseArgs(argv, cwd) {
|
|
|
328
340
|
const set = options.fromCommandLine;
|
|
329
341
|
/** Every option given, by its long name, for the refusals that depend on another option. */
|
|
330
342
|
const given = new Set();
|
|
343
|
+
/**
|
|
344
|
+
* What each thing that is one value was first asked to be: as the option
|
|
345
|
+
* stood on the command line, for the refusal, and as it was read, since
|
|
346
|
+
* `--root .` and `--root ./` name one directory and ask for one thing.
|
|
347
|
+
*/
|
|
348
|
+
const asked = new Map();
|
|
349
|
+
/** The first two options that ask for one thing as two values, as each was written. */
|
|
350
|
+
let unread;
|
|
331
351
|
let onlyPositional = false;
|
|
332
352
|
for (let index = command === 'check' ? 0 : 1; index < argv.length; index++) {
|
|
333
353
|
const argument = argv[index];
|
|
@@ -352,10 +372,23 @@ export function parseArgs(argv, cwd) {
|
|
|
352
372
|
index += 1;
|
|
353
373
|
return requireValue(name, argv[index]);
|
|
354
374
|
};
|
|
355
|
-
|
|
375
|
+
const long = LONG_NAMES[name] ?? name;
|
|
376
|
+
if (command !== 'check' && NOT_FOR[command].has(long)) {
|
|
356
377
|
throw new UsageError(`Option ${name} does not apply to spec-guard ${command}.`);
|
|
357
378
|
}
|
|
358
|
-
given.add(
|
|
379
|
+
given.add(long);
|
|
380
|
+
// Called by an option that takes one value, with what it read: the same
|
|
381
|
+
// thing asked twice is asked once, and a second value is kept for the
|
|
382
|
+
// refusal below. An option is the thing it asks for, by its long name;
|
|
383
|
+
// `--json` asks for a format.
|
|
384
|
+
const asks = (read, value, thing = long) => {
|
|
385
|
+
const written = value === undefined ? name : `${name} ${JSON.stringify(value)}`;
|
|
386
|
+
const first = asked.get(thing);
|
|
387
|
+
if (first === undefined)
|
|
388
|
+
asked.set(thing, { written, read });
|
|
389
|
+
else if (first.read !== read)
|
|
390
|
+
unread ??= [first.written, written];
|
|
391
|
+
};
|
|
359
392
|
switch (name) {
|
|
360
393
|
case '-h':
|
|
361
394
|
case '--help':
|
|
@@ -377,9 +410,11 @@ export function parseArgs(argv, cwd) {
|
|
|
377
410
|
case '--json':
|
|
378
411
|
options.json = true;
|
|
379
412
|
options.format = 'json';
|
|
413
|
+
asks('json', undefined, '--format');
|
|
380
414
|
break;
|
|
381
415
|
case '--format': {
|
|
382
|
-
const
|
|
416
|
+
const raw = nextValue();
|
|
417
|
+
const written = raw.toLowerCase();
|
|
383
418
|
// Whether it is a format at all is decided just below, by the lists.
|
|
384
419
|
const value = written;
|
|
385
420
|
// mcp refused --format above, so every command that gets here writes one.
|
|
@@ -393,6 +428,7 @@ export function parseArgs(argv, cwd) {
|
|
|
393
428
|
}
|
|
394
429
|
options.format = value;
|
|
395
430
|
options.json = value === 'json';
|
|
431
|
+
asks(value, raw);
|
|
396
432
|
break;
|
|
397
433
|
}
|
|
398
434
|
case '--strict':
|
|
@@ -471,13 +507,15 @@ export function parseArgs(argv, cwd) {
|
|
|
471
507
|
throw new UsageError(`Option ${name} expects a directory, got ${JSON.stringify(value)}. Name one, or leave the option out to run in the working directory.`);
|
|
472
508
|
}
|
|
473
509
|
options.root = path.resolve(cwd, value);
|
|
510
|
+
asks(options.root, value);
|
|
474
511
|
break;
|
|
475
512
|
}
|
|
476
513
|
case '--spec':
|
|
477
514
|
specs.push(nextValue());
|
|
478
515
|
break;
|
|
479
516
|
case '--engine': {
|
|
480
|
-
const
|
|
517
|
+
const raw = nextValue();
|
|
518
|
+
const value = raw.toLowerCase();
|
|
481
519
|
try {
|
|
482
520
|
options.engine = engineNamed(value);
|
|
483
521
|
}
|
|
@@ -485,29 +523,37 @@ export function parseArgs(argv, cwd) {
|
|
|
485
523
|
throw new UsageError(error.message);
|
|
486
524
|
}
|
|
487
525
|
set.add('engine');
|
|
526
|
+
asks(options.engine, raw);
|
|
488
527
|
break;
|
|
489
528
|
}
|
|
490
|
-
case '--max-snippets':
|
|
491
|
-
|
|
529
|
+
case '--max-snippets': {
|
|
530
|
+
const value = nextValue();
|
|
531
|
+
options.maxSnippets = positiveInteger(name, value);
|
|
492
532
|
set.add('maxSnippets');
|
|
533
|
+
asks(options.maxSnippets, value);
|
|
493
534
|
break;
|
|
535
|
+
}
|
|
494
536
|
case '--concurrency': {
|
|
495
537
|
// Refused as the configuration's key refuses it. Made 1, it was a run
|
|
496
538
|
// somebody had asked something else of.
|
|
497
|
-
const
|
|
539
|
+
const value = nextValue();
|
|
540
|
+
const concurrency = positiveInteger(name, value);
|
|
498
541
|
if (concurrency === 0)
|
|
499
542
|
throw new UsageError('Option --concurrency expects 1 or more: a concurrency of 0 would run no search.');
|
|
500
543
|
options.concurrency = concurrency;
|
|
501
544
|
set.add('concurrency');
|
|
545
|
+
asks(concurrency, value);
|
|
502
546
|
break;
|
|
503
547
|
}
|
|
504
548
|
case '--depth': {
|
|
505
549
|
if (command !== 'impact')
|
|
506
550
|
throw new UsageError('Option --depth applies only to spec-guard impact.');
|
|
507
|
-
const
|
|
551
|
+
const value = nextValue();
|
|
552
|
+
const depth = positiveInteger(name, value);
|
|
508
553
|
if (depth === 0)
|
|
509
554
|
throw new UsageError('Option --depth expects 1 or more: a depth of 0 would follow no import.');
|
|
510
555
|
options.depth = depth;
|
|
556
|
+
asks(depth, value);
|
|
511
557
|
break;
|
|
512
558
|
}
|
|
513
559
|
default:
|
|
@@ -520,11 +566,30 @@ export function parseArgs(argv, cwd) {
|
|
|
520
566
|
throw new UsageError(`Option ${name} takes no value, got "${inlineValue}". Give it alone or leave it out; an option a configuration can set has an opposite that turns it off, as --no-strict is to --strict.`);
|
|
521
567
|
}
|
|
522
568
|
}
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
|
|
527
|
-
|
|
569
|
+
// An option beside its own opposite, in either order: one of the two would
|
|
570
|
+
// go unread, and a line that asks for both was not meant. The last of them
|
|
571
|
+
// won, with no word of the other, for every pair but the one that draws the
|
|
572
|
+
// marks (ADR-0014, amended 2026-10-12). Named by the option that turns
|
|
573
|
+
// something on, whichever came first. The same option twice asks for one
|
|
574
|
+
// thing and is read as once, and an opposite alone is what overrides a
|
|
575
|
+
// configuration.
|
|
576
|
+
for (const off of given) {
|
|
577
|
+
// `--no-strict` is `--strict` turned off. An option that begins any other
|
|
578
|
+
// way reads back as itself, and is no pair with itself.
|
|
579
|
+
const on = off.replace('--no-', '--');
|
|
580
|
+
if (on !== off && given.has(on)) {
|
|
581
|
+
throw new UsageError(`Option ${on} is given beside ${off}, and one of the two would go unread. Give one of the two.`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
// Two values for a thing that is one: the later was read and the earlier
|
|
585
|
+
// went unread, with no word of it, for each option that takes one value and
|
|
586
|
+
// for `--json` beside a `--format` that names another format (ADR-0014,
|
|
587
|
+
// amended 2026-10-14). Named in the order they were given, each as it was
|
|
588
|
+
// written. Refused with `--help` or `--version` on the line too, as the
|
|
589
|
+
// pair above is: both are a wrong command line, and one tool does not
|
|
590
|
+
// answer the two first for some wrong lines and after others.
|
|
591
|
+
if (unread !== undefined) {
|
|
592
|
+
throw new UsageError(`Option ${unread[0]} is given beside ${unread[1]}, and one of the two would go unread. Give one of the two.`);
|
|
528
593
|
}
|
|
529
594
|
if (options.watch) {
|
|
530
595
|
for (const [option, reason] of NOT_WITH_WATCH) {
|