@descent-vtt/spec-guard 0.8.0 → 0.9.1
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 +106 -0
- package/README.md +119 -19
- package/dist/cli.d.ts +37 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +274 -59
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +66 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +165 -0
- package/dist/config.js.map +1 -0
- package/dist/engine.d.ts +22 -8
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +118 -67
- package/dist/engine.js.map +1 -1
- package/dist/facts.d.ts +106 -0
- package/dist/facts.d.ts.map +1 -0
- package/dist/facts.js +188 -0
- package/dist/facts.js.map +1 -0
- package/dist/glob.d.ts +9 -21
- package/dist/glob.d.ts.map +1 -1
- package/dist/glob.js +12 -24
- package/dist/glob.js.map +1 -1
- package/dist/imports.d.ts +6 -1
- package/dist/imports.d.ts.map +1 -1
- package/dist/imports.js +20 -8
- package/dist/imports.js.map +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -5
- package/dist/index.js.map +1 -1
- package/dist/io.d.ts +51 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +44 -0
- package/dist/io.js.map +1 -0
- package/dist/mcp.d.ts +11 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +21 -17
- package/dist/mcp.js.map +1 -1
- package/dist/memo.d.ts +53 -0
- package/dist/memo.d.ts.map +1 -0
- package/dist/memo.js +67 -0
- package/dist/memo.js.map +1 -0
- package/dist/query.d.ts +5 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +7 -3
- package/dist/query.js.map +1 -1
- package/dist/reporter.d.ts +10 -0
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +20 -0
- package/dist/reporter.js.map +1 -1
- package/dist/runner.d.ts +39 -2
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +85 -72
- package/dist/runner.js.map +1 -1
- package/dist/specs.d.ts +3 -1
- package/dist/specs.d.ts.map +1 -1
- package/dist/specs.js +6 -5
- package/dist/specs.js.map +1 -1
- package/dist/structure.d.ts +2 -2
- package/dist/structure.d.ts.map +1 -1
- package/dist/structure.js +5 -4
- package/dist/structure.js.map +1 -1
- package/dist/types.d.ts +17 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/watch.d.ts +125 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +321 -0
- package/dist/watch.js.map +1 -0
- package/package.json +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,112 @@ All notable changes to this project are documented here. Versions follow
|
|
|
5
5
|
may change in a minor release — each such change is listed under **Changed**
|
|
6
6
|
with the flag that restores the previous behaviour.
|
|
7
7
|
|
|
8
|
+
## 0.9.1
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **A self-closing JSX element after an expression attribute lost the rest of the
|
|
13
|
+
file's imports.** In `<App x={y} />`, `<App {...props} />` and the like, the
|
|
14
|
+
tokenizer read the `/` after `}` as the start of a regular expression.
|
|
15
|
+
- The scan ended at that line, and the file was reported as unanalysable.
|
|
16
|
+
- Its imports after that line went uncounted by import, layer and cycle
|
|
17
|
+
assertions.
|
|
18
|
+
- `/>` after `}` is now read as the end of the element.
|
|
19
|
+
- `/>` anywhere else still starts a regular expression, so
|
|
20
|
+
`replace(/>/g, '>')` reads as before.
|
|
21
|
+
|
|
22
|
+
[ADR-0005](docs/adr/0005-import-assertions.md).
|
|
23
|
+
|
|
24
|
+
## 0.9.0
|
|
25
|
+
|
|
26
|
+
A project's policy can live in its `package.json`, and `spec-guard --watch`
|
|
27
|
+
reports again as the tree changes, re-executing only the rules a change
|
|
28
|
+
affected. [ADR-0014](docs/adr/0014-configuration-and-watch.md).
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **Options in `package.json`, under `"specGuard"`:** `specs`, `engine`,
|
|
33
|
+
`strict`, `allowMissingTargets`, `allowEmptyScope`, `ignoreStatus`,
|
|
34
|
+
`includeSpecs`, `defaultSkips`, `maxSnippets` and `concurrency`.
|
|
35
|
+
- Read from the root's `package.json` only, never a parent directory's.
|
|
36
|
+
- Validated before anything runs. An unknown key, an option that belongs to one
|
|
37
|
+
invocation (`format`, `verbose`, `watch`, ...), a string where a boolean goes
|
|
38
|
+
or a count out of range is exit 2, naming the file and the key. `--help` and
|
|
39
|
+
`--version` still work under a broken `package.json`.
|
|
40
|
+
- The command line wins: patterns replace `specs`, and every on/off option the
|
|
41
|
+
file can set gained its opposite - `--no-strict`,
|
|
42
|
+
`--no-allow-missing-targets`, `--no-allow-empty-scope`, `--no-ignore-status`,
|
|
43
|
+
`--no-include-specs` and `--default-skips`.
|
|
44
|
+
- A report that took anything from the file names it in a line above its
|
|
45
|
+
summary, with what the command line overrode; the JSON report carries
|
|
46
|
+
`config`.
|
|
47
|
+
- `query` applies the keys a query reads. The MCP server reads the file again
|
|
48
|
+
for every request, as it reads the specs.
|
|
49
|
+
- **`spec-guard --watch`.** Reports, then reports again whenever something under
|
|
50
|
+
the root changes, until Ctrl+C, which exits 130.
|
|
51
|
+
- Saves are batched: 50 ms of quiet, or 500 ms after the first change. Enter
|
|
52
|
+
re-runs everything. A change no rule reads updates the status line only.
|
|
53
|
+
- Only the rules whose directive or inputs changed are re-executed. On this
|
|
54
|
+
repository a save in `src` re-executes 21 of 60 rules in a median of 21 ms -
|
|
55
|
+
over the 15 ms it was built to, as ADR-0014 reports - and a save elsewhere
|
|
56
|
+
takes 2-3 ms.
|
|
57
|
+
- One recursive `fs.watch` on the root, and no dependency.
|
|
58
|
+
- Refuses `--engine` - it scans in-process, where it sees what each rule reads -
|
|
59
|
+
and `--json`, `--format json|sarif`, `--print-baseline`, `--fail-fast` and
|
|
60
|
+
`--allow-empty`.
|
|
61
|
+
- A test changes trees at random and requires every report a session gives to
|
|
62
|
+
equal a fresh run's, however the change is reported, and six deliberately
|
|
63
|
+
broken sessions have to fail it.
|
|
64
|
+
- **One door for every read** (`Io`, `nodeIo`, `readText`, `watchTree`), and the
|
|
65
|
+
session behind watch mode (`createSession`, `runWatch`, `createFactCache`,
|
|
66
|
+
`createMemo`) in the programmatic API. `runSpecGuard` is `planRun`, an
|
|
67
|
+
execution and `reportRun`, and those two are exported.
|
|
68
|
+
`createJavaScriptEngine(io, memo)` builds a scanner over a door, and
|
|
69
|
+
`createCachedEngine(engine, fallback)` names what it falls back to.
|
|
70
|
+
- `scripts/bench-watch.mjs`, which measures a session on a copy of this
|
|
71
|
+
repository.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- **API:** `defaultDirectoryReader` and `statOrNull` are gone for `nodeIo`.
|
|
76
|
+
`WalkOptions.readDirectory` is `WalkOptions.io`, `enumerateCandidates` takes an
|
|
77
|
+
`Io` as its third argument, and `createTreeIndex(root, io)` takes a whole door.
|
|
78
|
+
`Assertion.missingTargets` is gone: only execution ever wrote to it.
|
|
79
|
+
- This repository keeps its specs in `package.json`, and its selfcheck, CI run
|
|
80
|
+
and SARIF upload run `spec-guard` without patterns.
|
|
81
|
+
- **Mutation testing in CI runs in four parallel shards.** The full sweep took
|
|
82
|
+
42m39s against a 45-minute job limit, and a slower runner was cancelled on the
|
|
83
|
+
same source. Hosted runners vary by a fifth or more on identical work.
|
|
84
|
+
- Each shard mutates its own files, balanced on per-file minutes read from
|
|
85
|
+
sweep logs (`scripts/mutation-timeline.mjs`).
|
|
86
|
+
- A final job merges the reports (`scripts/mutation-shards.mjs`). It refuses a
|
|
87
|
+
missing shard, a file mutated twice or by the wrong shard, and shards that ran
|
|
88
|
+
different tests, then applies the 97% gate to the merged score.
|
|
89
|
+
- Every shard runs every test: Stryker's vitest runner now has `related: false`.
|
|
90
|
+
- A full sweep starts from no incremental file, and a branch's shard from only
|
|
91
|
+
its own part of main's. Stryker reports the verdicts an incremental file holds
|
|
92
|
+
for files a run does not mutate, and on the first sharded sweep every shard
|
|
93
|
+
reported every other shard's files with the previous sweep's verdicts. The
|
|
94
|
+
merge refused that sweep.
|
|
95
|
+
- No mutants were left out, the per-mutant timeout is unchanged, and the gate
|
|
96
|
+
is unchanged. [ADR-0003](docs/adr/0003-mutation-testing.md).
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
|
|
100
|
+
- **A binary file was a gap for every rule sharing its pass.** A plain run scans
|
|
101
|
+
every rule over one scope together, and kept one ledger for them: a binary file
|
|
102
|
+
holding one rule's symbol was reported against all of them with their matches
|
|
103
|
+
summed, so `--strict` failed rules over files that did not hold their symbol.
|
|
104
|
+
The count of files whose comments could not be classified leaked the same way.
|
|
105
|
+
Each rule now has its own.
|
|
106
|
+
- **Two runs of one tree could list skipped files in different orders**, and past
|
|
107
|
+
the ledger's cap of 100, name different ones: files entered the ledger as their
|
|
108
|
+
reads finished. They now enter in walk order.
|
|
109
|
+
- Executing one resolved assertion twice named each missing target twice. Nothing
|
|
110
|
+
did so before watch mode.
|
|
111
|
+
- A test of the MCP server's durations that failed about one run in 53, on
|
|
112
|
+
floating-point error in the test.
|
|
113
|
+
|
|
8
114
|
## 0.8.0
|
|
9
115
|
|
|
10
116
|
An architecture document can now state conventions about names and layout: what
|
package/README.md
CHANGED
|
@@ -758,6 +758,7 @@ sources they follow, and what is deliberately not implemented.
|
|
|
758
758
|
|
|
759
759
|
```bash
|
|
760
760
|
spec-guard [patterns...] [options] # execute the directives
|
|
761
|
+
spec-guard --watch [patterns...] # execute them again whenever the tree changes
|
|
761
762
|
spec-guard query <paths...> [options] # the rules in force for files or directories
|
|
762
763
|
spec-guard mcp [options] # serve the rules over MCP on stdio
|
|
763
764
|
```
|
|
@@ -767,17 +768,18 @@ spec-guard mcp [options] # serve the rules over MCP on stdio
|
|
|
767
768
|
| `-r, --root <path>` | Codebase root that assertions resolve against (default: cwd) |
|
|
768
769
|
| `--spec <pattern>` | A spec glob or path, repeatable. `query` and `mcp` take their specs only from here |
|
|
769
770
|
| `-v, --verbose` | Print passing assertions too |
|
|
771
|
+
| `--watch` | Report, then report again as the tree changes, until Ctrl+C |
|
|
770
772
|
| `--fail-fast` | Stop at the first failing assertion |
|
|
771
773
|
| `--json` | Machine-readable report on stdout (same as `--format json`) |
|
|
772
774
|
| `--format <human\|json\|sarif>` | Output format. `sarif` uploads to GitHub code scanning |
|
|
773
775
|
| `--engine <auto\|rg\|js>` | Search engine (default `auto`: scanner for small trees, ripgrep for big ones) |
|
|
774
|
-
| `--strict` | Treat analysis that could not be completed as a failure |
|
|
775
|
-
| `--allow-missing-targets` | Warn instead of failing when a `target` path does not exist |
|
|
776
|
-
| `--allow-empty-scope` | Warn instead of failing when an assertion inspects no files |
|
|
776
|
+
| `--strict` / `--no-strict` | Treat analysis that could not be completed as a failure |
|
|
777
|
+
| `--allow-missing-targets` / `--no-allow-missing-targets` | Warn instead of failing when a `target` path does not exist |
|
|
778
|
+
| `--allow-empty-scope` / `--no-allow-empty-scope` | Warn instead of failing when an assertion inspects no files |
|
|
777
779
|
| `--print-baseline` | Print the `baseline="..."` that would exempt today's violations, and exit |
|
|
778
|
-
| `--no-default-skips` | Search `.git`, `.hg`, `.svn` and `node_modules` too |
|
|
779
|
-
| `--ignore-status` | Execute directives in draft, proposed and superseded documents too |
|
|
780
|
-
| `--include-specs` | Also count matches inside the spec files themselves |
|
|
780
|
+
| `--no-default-skips` / `--default-skips` | Search `.git`, `.hg`, `.svn` and `node_modules` too |
|
|
781
|
+
| `--ignore-status` / `--no-ignore-status` | Execute directives in draft, proposed and superseded documents too |
|
|
782
|
+
| `--include-specs` / `--no-include-specs` | Also count matches inside the spec files themselves |
|
|
781
783
|
| `--max-snippets <n>` | Failure snippets per assertion (default 5) |
|
|
782
784
|
| `--concurrency <n>` | Search passes in flight at once (default 8) |
|
|
783
785
|
| `--allow-empty` | Exit 0 when no spec file matched the patterns (about the run, not an assertion) |
|
|
@@ -785,6 +787,93 @@ spec-guard mcp [options] # serve the rules over MCP on stdio
|
|
|
785
787
|
|
|
786
788
|
Patterns are expanded by spec-guard itself, so quoted globs behave identically
|
|
787
789
|
on Windows, macOS and Linux. A directory expands to the Markdown files in it.
|
|
790
|
+
The second form of each on/off option exists to override a configuration.
|
|
791
|
+
|
|
792
|
+
### Configuration
|
|
793
|
+
|
|
794
|
+
The options that are a project's policy can live in `package.json`, under
|
|
795
|
+
`"specGuard"`, so CI, a pre-commit hook and the MCP server an agent starts all
|
|
796
|
+
hold the same rules the same way:
|
|
797
|
+
|
|
798
|
+
```json
|
|
799
|
+
{
|
|
800
|
+
"specGuard": {
|
|
801
|
+
"specs": ["docs/**/*.md", "README.md"],
|
|
802
|
+
"strict": true
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
| Key | Command line | Value |
|
|
808
|
+
| --- | --- | --- |
|
|
809
|
+
| `specs` | patterns, `--spec` | a non-empty array of globs |
|
|
810
|
+
| `engine` | `--engine` | `"auto"`, `"rg"` or `"js"` |
|
|
811
|
+
| `strict`, `allowMissingTargets`, `allowEmptyScope`, `ignoreStatus`, `includeSpecs`, `defaultSkips` | the flag of that name | `true` or `false` |
|
|
812
|
+
| `maxSnippets` | `--max-snippets` | an integer, 0 or more |
|
|
813
|
+
| `concurrency` | `--concurrency` | an integer, 1 or more |
|
|
814
|
+
|
|
815
|
+
- **Only the root's `package.json`** is read: the `--root` directory, or the
|
|
816
|
+
working directory. Nothing is inherited from a parent directory.
|
|
817
|
+
- **The command line wins, both ways.** Patterns replace `specs`, and
|
|
818
|
+
`--no-strict` beats `"strict": true`.
|
|
819
|
+
- **A malformed configuration is exit 2, before anything runs**, naming the file
|
|
820
|
+
and the key: an unknown key, `"true"` where `true` goes, or an option that
|
|
821
|
+
belongs to one invocation, such as `format` or `verbose`.
|
|
822
|
+
- **Every report says what it took from the file**: a line above the summary
|
|
823
|
+
such as `options from package.json: specs, strict`, and a `config` field in
|
|
824
|
+
JSON. An
|
|
825
|
+
option nobody can see should never decide a result.
|
|
826
|
+
- `query` applies what a query reads, and the MCP server reads the file again
|
|
827
|
+
for every request.
|
|
828
|
+
|
|
829
|
+
### Watch mode
|
|
830
|
+
|
|
831
|
+
```bash
|
|
832
|
+
spec-guard --watch
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
Prints the report, then prints it again whenever something under the root
|
|
836
|
+
changes, until Ctrl+C. Saves are batched: a batch starts after 50 ms without a
|
|
837
|
+
new change, or 500 ms after its first. On a terminal the screen is redrawn;
|
|
838
|
+
otherwise each report is appended under the time. A change that no rule reads
|
|
839
|
+
updates the status line and nothing else:
|
|
840
|
+
|
|
841
|
+
```text
|
|
842
|
+
watching 15 specs · 1 change · 21 of 60 rules re-executed · 21 ms · Enter re-runs everything, Ctrl+C stops
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
A session re-executes only the rules whose directive or inputs changed. Every
|
|
846
|
+
filesystem read goes through one module, so the session can record which rule
|
|
847
|
+
read what. A watcher's event only evicts what it may have changed, and the
|
|
848
|
+
evicted facts are read again and compared, so noise costs one directory read,
|
|
849
|
+
and a renamed directory's contents need no events of their own. Work that is a
|
|
850
|
+
pure function of a file's bytes - tokenizing, comment masking, scanning - is
|
|
851
|
+
remembered by the bytes' hash. Each of those is a way to report a tree that no
|
|
852
|
+
longer exists, so a test changes trees at random and requires every report a
|
|
853
|
+
session gives to equal a fresh run's, with six deliberately broken sessions
|
|
854
|
+
that it has to catch. [ADR-0014](docs/adr/0014-configuration-and-watch.md) has
|
|
855
|
+
the design, the measurements and the limits.
|
|
856
|
+
|
|
857
|
+
On this repository, from a change to the report, in the quietest of four runs
|
|
858
|
+
(a plain warm run took 69 ms in it):
|
|
859
|
+
|
|
860
|
+
| Edit | Rules re-executed | Median |
|
|
861
|
+
| --- | --- | --- |
|
|
862
|
+
| a file in `src` | 21 of 60 | 21 ms |
|
|
863
|
+
| a test file | 1 of 60 | 3.3 ms |
|
|
864
|
+
| a file no rule reads | 0 | 2.0 ms |
|
|
865
|
+
| an ADR's prose | 0 | 2.4 ms |
|
|
866
|
+
|
|
867
|
+
The 15 ms this was built to is missed for a save in `src`, which re-executes the
|
|
868
|
+
two layer rules and every text rule over `src`; ADR-0014 says where the time
|
|
869
|
+
goes.
|
|
870
|
+
|
|
871
|
+
`--watch` scans in-process, where it can see what each rule reads, so it refuses
|
|
872
|
+
`--engine`, and `--json`, `--format json|sarif`, `--print-baseline`,
|
|
873
|
+
`--fail-fast` and `--allow-empty`, which each describe a single run. It exits
|
|
874
|
+
130 when stopped, since a session is neither a pass nor a failure. What it
|
|
875
|
+
cannot see: a change the operating system never reports, a change behind a
|
|
876
|
+
symbolic link, and anything outside the root. CI stays the authority.
|
|
788
877
|
|
|
789
878
|
### Exit codes
|
|
790
879
|
|
|
@@ -792,7 +881,8 @@ on Windows, macOS and Linux. A directory expands to the Markdown files in it.
|
|
|
792
881
|
| --- | --- |
|
|
793
882
|
| `0` | Every assertion held |
|
|
794
883
|
| `1` | An assertion failed, or a directive was malformed |
|
|
795
|
-
| `2` | spec-guard could not run: bad usage, no spec files matched, `--engine rg` with no ripgrep |
|
|
884
|
+
| `2` | spec-guard could not run: bad usage, a malformed configuration, no spec files matched, `--engine rg` with no ripgrep, a watch that could not start |
|
|
885
|
+
| `130` | A `--watch` session was stopped |
|
|
796
886
|
|
|
797
887
|
## CI integration
|
|
798
888
|
|
|
@@ -854,9 +944,10 @@ spec-guard's claims are about a whole repository - "this symbol appears nowhere
|
|
|
854
944
|
in `src`" - and a language server is handed one buffer at a time. Answering a
|
|
855
945
|
repository-wide question on every keystroke means rescanning the tree on every
|
|
856
946
|
keystroke; the alternative is answering a smaller question and calling it the
|
|
857
|
-
same one.
|
|
858
|
-
|
|
859
|
-
daemon, an extension per editor, or a protocol
|
|
947
|
+
same one. `--watch` re-evaluates this repository in milliseconds after a save
|
|
948
|
+
by re-executing only what the save affected, and a pre-commit hook closes the
|
|
949
|
+
rest of the gap, without a daemon, an extension per editor, or a protocol
|
|
950
|
+
version matrix.
|
|
860
951
|
|
|
861
952
|
## How it works
|
|
862
953
|
|
|
@@ -1099,7 +1190,7 @@ npm install
|
|
|
1099
1190
|
npm run build # tsc -> dist/
|
|
1100
1191
|
npm test # vitest
|
|
1101
1192
|
npm run test:coverage
|
|
1102
|
-
npm run test:mutation # stryker (
|
|
1193
|
+
npm run test:mutation # stryker (four parallel jobs in CI; hours locally)
|
|
1103
1194
|
npm run lint # tsc --noEmit
|
|
1104
1195
|
npm run selfcheck # run spec-guard on its own docs
|
|
1105
1196
|
```
|
|
@@ -1114,10 +1205,13 @@ prebuilt binary and is never a runtime dependency.
|
|
|
1114
1205
|
|
|
1115
1206
|
Coverage says a line ran. It does not say an assertion would notice if the line
|
|
1116
1207
|
behaved differently. This repository measures the difference: line coverage is
|
|
1117
|
-
**100%**, and **
|
|
1118
|
-
95%. The second number is the one worth reading
|
|
1119
|
-
|
|
1120
|
-
|
|
1208
|
+
**100%**, and **98.84%** of 7,776 mutants are killed in CI, with 87 survivors and
|
|
1209
|
+
no file below 95%. The second number is the one worth reading, and what was done
|
|
1210
|
+
about the survivors matters more than the score. At 0.5.1 every surviving mutant
|
|
1211
|
+
was checked individually, and the 101 left then produced byte-identical output.
|
|
1212
|
+
Each release since has replayed the survivors its own code added before it
|
|
1213
|
+
shipped: they were killed, deleted as dead code, or shown equivalent and
|
|
1214
|
+
recorded in [ADR-0003](docs/adr/0003-mutation-testing.md) with the evidence.
|
|
1121
1215
|
|
|
1122
1216
|
That gap is the point. The first run scored 77.23%, and the weakest file was the
|
|
1123
1217
|
reporter at 65.48% - not because it lacked tests, but because its tests were
|
|
@@ -1158,13 +1252,19 @@ quoted above; a full sweep is also what publishes the cache the branches start
|
|
|
1158
1252
|
from, so an incremental verdict can never be built on another incremental
|
|
1159
1253
|
verdict.
|
|
1160
1254
|
|
|
1161
|
-
The full sweep was 6m54s at 2,118 mutants, 15m51s at 3,493, and
|
|
1162
|
-
|
|
1163
|
-
|
|
1255
|
+
The full sweep was 6m54s at 2,118 mutants, 15m51s at 3,493, and 42m39s at 7,763.
|
|
1256
|
+
That growth is why the tiers exist: a check that gets quietly more expensive
|
|
1257
|
+
every release is a check somebody eventually proposes lowering. It is also why
|
|
1258
|
+
both tiers now run in four parallel shards. One runner
|
|
1259
|
+
could no longer finish the full sweep reliably inside the job's limit, and the
|
|
1260
|
+
answer was to split the sweep rather than raise the limit or drop mutants.
|
|
1261
|
+
Each shard mutates its own files against every test, and a final job merges the
|
|
1262
|
+
reports. The merge refuses anything that is not exactly one sweep, and applies
|
|
1263
|
+
the 97% gate to the merged score with the same library Stryker's gate uses.
|
|
1164
1264
|
|
|
1165
1265
|
Run it locally with `npm run test:mutation` if you like, but do not calibrate
|
|
1166
1266
|
anything on the result: on the Windows machine this was developed on the same
|
|
1167
|
-
suite takes
|
|
1267
|
+
suite takes hours against 43 minutes of hosted time, and it scores *higher*,
|
|
1168
1268
|
because far more mutants hang there and Stryker counts a hang as a kill. Linux
|
|
1169
1269
|
CI is the measurement.
|
|
1170
1270
|
|
package/dist/cli.d.ts
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* 2 - spec-guard could not run (bad usage, no spec files, missing engine)
|
|
8
8
|
*/
|
|
9
9
|
import type { Readable } from 'node:stream';
|
|
10
|
+
import { type ConfigKey, type ProjectConfig } from './config.js';
|
|
11
|
+
import { watchTree } from './io.js';
|
|
10
12
|
import type { EnginePreference } from './engine.js';
|
|
13
|
+
import type { ConfigUse } from './types.js';
|
|
11
14
|
export declare const EXIT_OK = 0;
|
|
12
15
|
export declare const EXIT_FAILED = 1;
|
|
13
16
|
export declare const EXIT_ERROR = 2;
|
|
@@ -17,8 +20,17 @@ export interface CliIO {
|
|
|
17
20
|
env: NodeJS.ProcessEnv;
|
|
18
21
|
cwd: string;
|
|
19
22
|
isTTY: boolean;
|
|
20
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Where `spec-guard mcp` reads its messages, and where a watch session hears
|
|
25
|
+
* Enter. Nothing else reads input.
|
|
26
|
+
*/
|
|
21
27
|
stdin?: Readable;
|
|
28
|
+
/** Writes to stdout as it is, with no newline added: a watch session redraws. */
|
|
29
|
+
write?: (text: string) => void;
|
|
30
|
+
/** Starts a recursive watch on a directory; a test passes one it drives itself. */
|
|
31
|
+
watch?: typeof watchTree;
|
|
32
|
+
/** Registers what Ctrl+C and SIGTERM do, and returns how to unregister it. */
|
|
33
|
+
onInterrupt?: (handler: () => void) => () => void;
|
|
22
34
|
}
|
|
23
35
|
/** What the command line asked for: a run, a query, or a server. */
|
|
24
36
|
export type Command = 'check' | 'query' | 'mcp';
|
|
@@ -47,8 +59,18 @@ export interface CliOptions {
|
|
|
47
59
|
maxSnippets: number;
|
|
48
60
|
concurrency: number;
|
|
49
61
|
color?: boolean;
|
|
62
|
+
/** Re-run as the tree changes, until stopped. ADR-0014. */
|
|
63
|
+
watch: boolean;
|
|
50
64
|
help: boolean;
|
|
51
65
|
version: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* The configurable options the command line set, either way.
|
|
68
|
+
*
|
|
69
|
+
* A configuration fills in only what is not here, so a flag always wins - and
|
|
70
|
+
* `--strict` given alongside `"strict": true` is still reported as the command
|
|
71
|
+
* line's doing, not the file's.
|
|
72
|
+
*/
|
|
73
|
+
fromCommandLine: Set<ConfigKey>;
|
|
52
74
|
}
|
|
53
75
|
export declare class UsageError extends Error {
|
|
54
76
|
}
|
|
@@ -60,9 +82,22 @@ export declare class UsageError extends Error {
|
|
|
60
82
|
* fallback nobody knows still works.
|
|
61
83
|
*/
|
|
62
84
|
export declare function version(manifest?: string): string;
|
|
63
|
-
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\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 and\n mcp take theirs from --spec.\n\nOptions\n -r, --root <path> Codebase root that assertions are resolved against (default: cwd)\n --spec <pattern> A spec glob or path; repeatable (default: \"docs/**/*.md\")\n -v, --verbose Print passing assertions too\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 (sarif uploads to GitHub code scanning)\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 --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 --ignore-status Execute directives in draft, proposed and superseded documents 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 (about the run, not an assertion)\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 <!-- @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 or superseded\n is reported and not executed; --ignore-status runs it anyway.\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, and a check,\nas the tools get_architectural_rules and check_architecture.\n\nExit codes\n 0 all assertions passed 1 an assertion failed 2 spec-guard could not run";
|
|
85
|
+
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\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 and\n mcp take theirs from --spec.\n\nOptions\n -r, --root <path> Codebase root that assertions are resolved against (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 (sarif uploads to GitHub code scanning)\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 --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 --ignore-status Execute directives in draft, proposed and superseded documents 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 (about the run, not an assertion)\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\nConfiguration\n package.json in the root can hold specs, engine, strict, allowMissingTargets,\n allowEmptyScope, ignoreStatus, includeSpecs, defaultSkips, maxSnippets and\n concurrency under \"specGuard\". A flag wins over the file, and every on/off\n option there also takes its opposite (--no-strict, --default-skips, ...).\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 or superseded\n is reported and not executed; --ignore-status runs it anyway.\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, and a check,\nas the tools get_architectural_rules and check_architecture.\n\nExit codes\n 0 all assertions passed 1 an assertion failed 2 spec-guard could not run\n 130 a --watch session was stopped";
|
|
64
86
|
/** Minimal, dependency-free argv parser. Supports `--flag value` and `--flag=value`. */
|
|
65
87
|
export declare function parseArgs(argv: readonly string[], cwd: string): CliOptions;
|
|
88
|
+
/**
|
|
89
|
+
* Fills in what the command line left unset from a configuration, and says what
|
|
90
|
+
* it took.
|
|
91
|
+
*
|
|
92
|
+
* A key the command does not read is neither applied nor overridden: `strict`
|
|
93
|
+
* is written for the runs, and a query that ignores it has not been told
|
|
94
|
+
* anything about strictness. A watch session scans in-process, so `engine` is
|
|
95
|
+
* not one of its keys either. Returns nothing when the configuration had
|
|
96
|
+
* nothing to say to this command, so a report says nothing about it.
|
|
97
|
+
*/
|
|
98
|
+
export declare function applyConfig(options: CliOptions, config: ProjectConfig, file?: string): ConfigUse | undefined;
|
|
99
|
+
/** The process's own streams, signals and filesystem watcher. */
|
|
100
|
+
export declare function defaultIO(): CliIO;
|
|
66
101
|
/** Runs the CLI and resolves to the process exit code. */
|
|
67
102
|
export declare function main(argv?: readonly string[], io?: CliIO): Promise<number>;
|
|
68
103
|
//# sourceMappingURL=cli.d.ts.map
|
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;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|