@descent-vtt/spec-guard 0.7.0 → 0.9.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/README.md +224 -27
  3. package/dist/cli.d.ts +37 -2
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +275 -59
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config.d.ts +66 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +165 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/engine.d.ts +22 -8
  12. package/dist/engine.d.ts.map +1 -1
  13. package/dist/engine.js +118 -67
  14. package/dist/engine.js.map +1 -1
  15. package/dist/facts.d.ts +106 -0
  16. package/dist/facts.d.ts.map +1 -0
  17. package/dist/facts.js +188 -0
  18. package/dist/facts.js.map +1 -0
  19. package/dist/glob.d.ts +17 -21
  20. package/dist/glob.d.ts.map +1 -1
  21. package/dist/glob.js +13 -24
  22. package/dist/glob.js.map +1 -1
  23. package/dist/imports.d.ts +6 -1
  24. package/dist/imports.d.ts.map +1 -1
  25. package/dist/imports.js +9 -5
  26. package/dist/imports.js.map +1 -1
  27. package/dist/index.d.ts +20 -8
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +11 -5
  30. package/dist/index.js.map +1 -1
  31. package/dist/io.d.ts +51 -0
  32. package/dist/io.d.ts.map +1 -0
  33. package/dist/io.js +44 -0
  34. package/dist/io.js.map +1 -0
  35. package/dist/mcp.d.ts +11 -0
  36. package/dist/mcp.d.ts.map +1 -1
  37. package/dist/mcp.js +23 -18
  38. package/dist/mcp.js.map +1 -1
  39. package/dist/memo.d.ts +53 -0
  40. package/dist/memo.d.ts.map +1 -0
  41. package/dist/memo.js +67 -0
  42. package/dist/memo.js.map +1 -0
  43. package/dist/parser.d.ts.map +1 -1
  44. package/dist/parser.js +17 -0
  45. package/dist/parser.js.map +1 -1
  46. package/dist/query.d.ts +5 -2
  47. package/dist/query.d.ts.map +1 -1
  48. package/dist/query.js +12 -3
  49. package/dist/query.js.map +1 -1
  50. package/dist/reporter.d.ts +10 -0
  51. package/dist/reporter.d.ts.map +1 -1
  52. package/dist/reporter.js +33 -4
  53. package/dist/reporter.js.map +1 -1
  54. package/dist/rules.d.ts +12 -1
  55. package/dist/rules.d.ts.map +1 -1
  56. package/dist/rules.js +45 -1
  57. package/dist/rules.js.map +1 -1
  58. package/dist/runner.d.ts +42 -2
  59. package/dist/runner.d.ts.map +1 -1
  60. package/dist/runner.js +261 -76
  61. package/dist/runner.js.map +1 -1
  62. package/dist/specs.d.ts +3 -1
  63. package/dist/specs.d.ts.map +1 -1
  64. package/dist/specs.js +6 -5
  65. package/dist/specs.js.map +1 -1
  66. package/dist/structure.d.ts +104 -0
  67. package/dist/structure.d.ts.map +1 -0
  68. package/dist/structure.js +254 -0
  69. package/dist/structure.js.map +1 -0
  70. package/dist/types.d.ts +50 -5
  71. package/dist/types.d.ts.map +1 -1
  72. package/dist/watch.d.ts +125 -0
  73. package/dist/watch.d.ts.map +1 -0
  74. package/dist/watch.js +321 -0
  75. package/dist/watch.js.map +1 -0
  76. package/package.json +8 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,153 @@ 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.0
9
+
10
+ A project's policy can live in its `package.json`, and `spec-guard --watch`
11
+ reports again as the tree changes, re-executing only the rules a change
12
+ affected. [ADR-0014](docs/adr/0014-configuration-and-watch.md).
13
+
14
+ ### Added
15
+
16
+ - **Options in `package.json`, under `"specGuard"`:** `specs`, `engine`,
17
+ `strict`, `allowMissingTargets`, `allowEmptyScope`, `ignoreStatus`,
18
+ `includeSpecs`, `defaultSkips`, `maxSnippets` and `concurrency`.
19
+ - Read from the root's `package.json` only, never a parent directory's.
20
+ - Validated before anything runs. An unknown key, an option that belongs to one
21
+ invocation (`format`, `verbose`, `watch`, ...), a string where a boolean goes
22
+ or a count out of range is exit 2, naming the file and the key. `--help` and
23
+ `--version` still work under a broken `package.json`.
24
+ - The command line wins: patterns replace `specs`, and every on/off option the
25
+ file can set gained its opposite - `--no-strict`,
26
+ `--no-allow-missing-targets`, `--no-allow-empty-scope`, `--no-ignore-status`,
27
+ `--no-include-specs` and `--default-skips`.
28
+ - A report that took anything from the file names it in a line above its
29
+ summary, with what the command line overrode; the JSON report carries
30
+ `config`.
31
+ - `query` applies the keys a query reads. The MCP server reads the file again
32
+ for every request, as it reads the specs.
33
+ - **`spec-guard --watch`.** Reports, then reports again whenever something under
34
+ the root changes, until Ctrl+C, which exits 130.
35
+ - Saves are batched: 50 ms of quiet, or 500 ms after the first change. Enter
36
+ re-runs everything. A change no rule reads updates the status line only.
37
+ - Only the rules whose directive or inputs changed are re-executed. On this
38
+ repository a save in `src` re-executes 21 of 60 rules in a median of 21 ms -
39
+ over the 15 ms it was built to, as ADR-0014 reports - and a save elsewhere
40
+ takes 2-3 ms.
41
+ - One recursive `fs.watch` on the root, and no dependency.
42
+ - Refuses `--engine` - it scans in-process, where it sees what each rule reads -
43
+ and `--json`, `--format json|sarif`, `--print-baseline`, `--fail-fast` and
44
+ `--allow-empty`.
45
+ - A test changes trees at random and requires every report a session gives to
46
+ equal a fresh run's, however the change is reported, and six deliberately
47
+ broken sessions have to fail it.
48
+ - **One door for every read** (`Io`, `nodeIo`, `readText`, `watchTree`), and the
49
+ session behind watch mode (`createSession`, `runWatch`, `createFactCache`,
50
+ `createMemo`) in the programmatic API. `runSpecGuard` is `planRun`, an
51
+ execution and `reportRun`, and those two are exported.
52
+ `createJavaScriptEngine(io, memo)` builds a scanner over a door, and
53
+ `createCachedEngine(engine, fallback)` names what it falls back to.
54
+ - `scripts/bench-watch.mjs`, which measures a session on a copy of this
55
+ repository.
56
+
57
+ ### Changed
58
+
59
+ - **API:** `defaultDirectoryReader` and `statOrNull` are gone for `nodeIo`.
60
+ `WalkOptions.readDirectory` is `WalkOptions.io`, `enumerateCandidates` takes an
61
+ `Io` as its third argument, and `createTreeIndex(root, io)` takes a whole door.
62
+ `Assertion.missingTargets` is gone: only execution ever wrote to it.
63
+ - This repository keeps its specs in `package.json`, and its selfcheck, CI run
64
+ and SARIF upload run `spec-guard` without patterns.
65
+ - **Mutation testing in CI runs in four parallel shards.** The full sweep took
66
+ 42m39s against a 45-minute job limit, and a slower runner was cancelled on the
67
+ same source. Hosted runners vary by a fifth or more on identical work.
68
+ - Each shard mutates its own files, balanced on per-file minutes read from
69
+ sweep logs (`scripts/mutation-timeline.mjs`).
70
+ - A final job merges the reports (`scripts/mutation-shards.mjs`). It refuses a
71
+ missing shard, a file mutated twice or by the wrong shard, and shards that ran
72
+ different tests, then applies the 97% gate to the merged score.
73
+ - Every shard runs every test: Stryker's vitest runner now has `related: false`.
74
+ - A full sweep starts from no incremental file, and a branch's shard from only
75
+ its own part of main's. Stryker reports the verdicts an incremental file holds
76
+ for files a run does not mutate, and on the first sharded sweep every shard
77
+ reported every other shard's files with the previous sweep's verdicts. The
78
+ merge refused that sweep.
79
+ - No mutants were left out, the per-mutant timeout is unchanged, and the gate
80
+ is unchanged. [ADR-0003](docs/adr/0003-mutation-testing.md).
81
+
82
+ ### Fixed
83
+
84
+ - **A binary file was a gap for every rule sharing its pass.** A plain run scans
85
+ every rule over one scope together, and kept one ledger for them: a binary file
86
+ holding one rule's symbol was reported against all of them with their matches
87
+ summed, so `--strict` failed rules over files that did not hold their symbol.
88
+ The count of files whose comments could not be classified leaked the same way.
89
+ Each rule now has its own.
90
+ - **Two runs of one tree could list skipped files in different orders**, and past
91
+ the ledger's cap of 100, name different ones: files entered the ledger as their
92
+ reads finished. They now enter in walk order.
93
+ - Executing one resolved assertion twice named each missing target twice. Nothing
94
+ did so before watch mode.
95
+ - A test of the MCP server's durations that failed about one run in 53, on
96
+ floating-point error in the test.
97
+
98
+ ## 0.8.0
99
+
100
+ An architecture document can now state conventions about names and layout: what
101
+ the files in a directory are called, what every package holds, and which files
102
+ come in pairs. [ADR-0013](docs/adr/0013-structure-assertions.md).
103
+
104
+ ### Added
105
+
106
+ - **`@assert-structure`**, with exactly one of three claims per directive:
107
+ - `pattern="*.entity.ts, index.ts"` - every file in scope is named by one of
108
+ the patterns;
109
+ - `required="package.json, README.md"` - every directory holds each entry.
110
+ The directories are the targets, or with `dirs="*"` their children and with
111
+ `dirs="**"` every directory below them, empty ones included. An entry may be
112
+ a path, may end in a glob, and must be a directory when it ends in `/`;
113
+ - `partner="[name].test.[ext], tests/[dir]/test_[name].py"` - every file has
114
+ one of its partners. Three placeholders and no other grammar: `[name]` and
115
+ `[ext]` split the file name at its last dot, and `[dir]` is the file's
116
+ directory below its target.
117
+
118
+ Names are compared exactly from directory listings, never by looking a path
119
+ up, so `Readme.md` does not satisfy `README.md` on Windows or macOS any more
120
+ than it does on Linux. A scope with no files or no directories fails, as does
121
+ a partner template naming the file itself and a `required` target that is a
122
+ file. When a file has no partner because it *is* one, the report says so and
123
+ suggests the `exclude`. Takes `glob`, `exclude`, `max`, `allow-empty`,
124
+ `baseline` and `ratchet`. Spec files are in scope, and symbolic links are not
125
+ followed. All structure rules in a run share one walk per target and read
126
+ each directory once.
127
+ - `spec-guard query` and the MCP server show what a structure rule asks of a
128
+ path: whether a file's name is allowed, the partners it needs, and - for a
129
+ file in a directory a `required` rule holds - that rule.
130
+ - The JSON report carries `claim` on structure results. A match with `line: 0`
131
+ is a path rather than a place in one, and SARIF annotates a misnamed or
132
+ partnerless file at its first line, and a directory missing an entry at the
133
+ directive, with the directory in the message.
134
+ - `checkStructure`, `createTreeIndex`, `expandPartner`, `partnerTemplateIssue`
135
+ and `requiredEntryIssue` are exported, with their types. `walkPaths` takes an
136
+ `onDirectory` callback.
137
+
138
+ ### Changed
139
+
140
+ - **`executeAssertion` needs a `tree`** in its options: `createTreeIndex(root)`,
141
+ the run's directory listings, beside the `imports` and `hasFiles` caches it
142
+ already takes. Only code calling `executeAssertion` directly is affected;
143
+ `runSpecGuard` and the command line create it themselves.
144
+ - The MCP server's instructions and the `get_architectural_rules` description
145
+ tell an agent that the rules for a path now include what it must be named and
146
+ the partner files it needs.
147
+ - **Mutation score 98.63%** over 6,939 mutants, up from 0.7.0's 98.47%, with 92
148
+ survivors - one fewer than before the 676 mutants this release adds.
149
+ `structure.ts` is at 100%, and every other module has exactly the survivors it
150
+ had, less one in `glob.ts` that the new tests turned into a genuine hang. A
151
+ local sweep had reported four survivors in the new listing cache as killed; CI
152
+ found them, and they are tested. See
153
+ [ADR-0003](docs/adr/0003-mutation-testing.md).
154
+
8
155
  ## 0.7.0
9
156
 
10
157
  Two things an architecture document could not do before. It can state the shape
package/README.md CHANGED
@@ -422,6 +422,99 @@ reading `tsconfig.json`, which is the resolver
422
422
  [ADR-0011](docs/adr/0011-layers-and-cycles.md) declines to become, for the
423
423
  reasons it gives.
424
424
 
425
+ ### `@assert-structure` - what files are called, and what comes with them
426
+
427
+ Architecture documents state conventions over sets of files rather than naming
428
+ files one by one. Each directive makes exactly one of three claims:
429
+
430
+ ```md
431
+ <!-- @assert-structure target="src/domain" exclude="*.test.ts" pattern="*.entity.ts, index.ts" -->
432
+ <!-- @assert-structure target="packages" dirs="*" required="package.json, README.md" -->
433
+ <!-- @assert-structure target="src/handlers" exclude="*.test.ts" partner="[name].test.ts" -->
434
+ ```
435
+
436
+ - **`pattern`** - every file in scope is named by one of the patterns. They are
437
+ globs, read like `glob=`: without a `/` against the file name, with one
438
+ against the path from the root.
439
+ - **`required`** - every directory holds every entry. Without `dirs` the
440
+ directories are the targets; `dirs="*"` means their immediate children and
441
+ `dirs="**"` every directory below them. An entry may be a path
442
+ (`src/index.ts`), may end in a glob (`*.csproj`), and must be a directory
443
+ when it ends in `/`.
444
+ - **`partner`** - every file has a partner, named by a template.
445
+
446
+ Each failure names the file or directory, since there is no line to point at:
447
+
448
+ ```text
449
+ ✖ docs/architecture.md:3 @assert-structure
450
+ files in src/domain must be named *.entity.ts or index.ts (excluding *.test.ts)
451
+ expected no misnamed files, found 1
452
+ src/domain/helpers.ts matches none of *.entity.ts, index.ts
453
+
454
+ ✖ docs/architecture.md:4 @assert-structure
455
+ directories matching * under packages must contain package.json, README.md
456
+ expected no directories missing an entry, found 2
457
+ packages/billing missing README.md
458
+ packages/web missing package.json, README.md
459
+
460
+ ✖ docs/architecture.md:5 @assert-structure
461
+ files in src/handlers must each have a partner [name].test.ts (excluding *.test.ts)
462
+ expected no files without a partner, found 1
463
+ src/handlers/refund.ts has no partner src/handlers/refund.test.ts
464
+ ```
465
+
466
+ A partner template has three placeholders and nothing else - no regular
467
+ expressions, no conditionals - so a reader can expand one by eye:
468
+
469
+ | Placeholder | For `src/api/user.handler.ts`, `target="src"` |
470
+ | --- | --- |
471
+ | `[name]` | `user.handler` - the file name up to its last dot |
472
+ | `[ext]` | `ts` - what follows that dot |
473
+ | `[dir]` | `api` - the file's directory below the target, empty at the top |
474
+
475
+ A template without a `/` names a file beside it. One with a `/` is a path from
476
+ the root, which is how a mirrored test tree is written:
477
+ `partner="tests/[dir]/test_[name].py"`. A comma-separated list gives
478
+ alternatives, any one of which will do.
479
+
480
+ The partners themselves are in scope unless `exclude` says otherwise. Forgetting
481
+ to exclude them is loud rather than silent, and says what probably happened:
482
+
483
+ ```text
484
+ src/handlers/order.test.ts has no partner src/handlers/order.test.test.ts (it is the partner of src/handlers/order.ts - exclude it?)
485
+ ```
486
+
487
+ Some things fail rather than pass, because each is a rule that would otherwise
488
+ check nothing:
489
+ - a scope holding no files, or selecting no directories (`allow-empty` as
490
+ everywhere else);
491
+ - a template that names the file itself (`partner="[name].[ext]"`);
492
+ - a `required` target that is a file.
493
+
494
+ **Names are compared exactly, on every platform.** Whether `README.md` exists is
495
+ decided by reading its directory, never by asking the filesystem for the path.
496
+ That lookup finds `Readme.md` on Windows and macOS and does not find it on Linux,
497
+ and the same rule would pass on a laptop and fail in CI.
498
+
499
+ A structure rule reads names, never contents. It walks with the run's usual
500
+ skips, follows no symbolic link, and counts the spec files - which every other
501
+ rule leaves out, and which a naming rule about `docs/adr` is about. `max`,
502
+ `baseline` and `ratchet` work as they do on `@assert-absence`, and a baseline
503
+ lists files or directories. `spec-guard query` shows what a rule asks of a file
504
+ that does not exist yet:
505
+
506
+ ```text
507
+ src/handlers/invoice.ts (does not exist yet)
508
+ 1 rule from 1 document
509
+
510
+ Architecture (docs/architecture.md)
511
+ :5 @assert-structure files in src/handlers must each have a partner [name].test.ts (excluding *.test.ts)
512
+ partner: src/handlers/invoice.test.ts
513
+ ```
514
+
515
+ [ADR-0013](docs/adr/0013-structure-assertions.md) has the design and what it
516
+ costs.
517
+
425
518
  ### `@assert-present` - this file exists
426
519
 
427
520
  ```md
@@ -435,23 +528,27 @@ Passes when every listed path exists relative to `--root`. Directories count.
435
528
 
436
529
  | Attribute | Applies to | Meaning |
437
530
  | --- | --- | --- |
438
- | `target` | absence, count | Comma-separated paths to search, relative to `--root`. Default `.` |
531
+ | `target` | all but present | Comma-separated paths, relative to `--root`. Default `.` |
439
532
  | `symbol` | absence, count | The literal string to search for (or a regex with `regex="true"`) |
440
533
  | `file` | present | Comma-separated paths that must exist |
441
- | `expected` | absence, count | Upper bound for absence; exact count for count |
442
- | `min` / `max` | count (`max` also on absence) | Inclusive bounds |
443
- | `glob` | absence, count | Include-only file filters, e.g. `*.ts,*.tsx` (ripgrep `-g` semantics) |
444
- | `exclude` | absence, count | Paths to leave out, gitignore-style: `src/config/**`, `tests`, `*.test.ts` |
534
+ | `expected` | all but present | Exact count for count; an upper bound everywhere else |
535
+ | `min` / `max` | count (`max` on all but present) | Inclusive bounds |
536
+ | `glob` | absence, count, structure | Include-only file filters, e.g. `*.ts,*.tsx` (ripgrep `-g` semantics) |
537
+ | `exclude` | all but present | Paths to leave out, gitignore-style: `src/config/**`, `tests`, `*.test.ts` |
445
538
  | `regex` | absence, count | Treat `symbol` as a regular expression |
446
539
  | `word` | absence, count | Require word boundaries, so `Primary` does not match `PrimaryButton` |
447
540
  | `ignore-case` | absence, count | Case-insensitive matching |
448
541
  | `comments` | absence, count | `ignore` (default) or `include` for matches inside comments |
449
542
  | `module` | import-absence, import-count | Which dependency, matched like `exclude` |
450
543
  | `order` | layers | The layers, from the one everything may depend on to the one that may depend on everything |
544
+ | `pattern` | structure | The names every file in scope must match one of |
545
+ | `required` | structure | The entries every chosen directory must hold; a trailing `/` means a directory |
546
+ | `dirs` | structure, with `required` | Which directories below each target: `*` for children, `**` for all |
547
+ | `partner` | structure | Partner templates, any one of which must exist: `[name].test.[ext]` |
451
548
  | `types` | import assertions, layers, cycles | `include` (default) or `ignore` for `import type` |
452
- | `allow-empty` | absence, count, import assertions, layers, cycles | Tolerate a scope that holds no files, or a layer that matches none. Off by default - see below |
453
- | `baseline` | absence, import-absence, layers | Known violations that do not count: `path` or `path:count` |
454
- | `ratchet` | absence, import-absence, layers | `two-sided` (default) or `one-way` - see below |
549
+ | `allow-empty` | all but present | Tolerate a scope that holds no files, a layer that matches none, or no chosen directories. Off by default - see below |
550
+ | `baseline` | absence, import-absence, layers, structure | Known violations that do not count: `path` or `path:count` |
551
+ | `ratchet` | absence, import-absence, layers, structure | `two-sided` (default) or `one-way` - see below |
455
552
  | `reason` | all | Human-readable justification, printed on failure |
456
553
 
457
554
  Unknown attributes are an error, not a shrug: `expct="1"` fails the run instead
@@ -661,6 +758,7 @@ sources they follow, and what is deliberately not implemented.
661
758
 
662
759
  ```bash
663
760
  spec-guard [patterns...] [options] # execute the directives
761
+ spec-guard --watch [patterns...] # execute them again whenever the tree changes
664
762
  spec-guard query <paths...> [options] # the rules in force for files or directories
665
763
  spec-guard mcp [options] # serve the rules over MCP on stdio
666
764
  ```
@@ -670,17 +768,18 @@ spec-guard mcp [options] # serve the rules over MCP on stdio
670
768
  | `-r, --root <path>` | Codebase root that assertions resolve against (default: cwd) |
671
769
  | `--spec <pattern>` | A spec glob or path, repeatable. `query` and `mcp` take their specs only from here |
672
770
  | `-v, --verbose` | Print passing assertions too |
771
+ | `--watch` | Report, then report again as the tree changes, until Ctrl+C |
673
772
  | `--fail-fast` | Stop at the first failing assertion |
674
773
  | `--json` | Machine-readable report on stdout (same as `--format json`) |
675
774
  | `--format <human\|json\|sarif>` | Output format. `sarif` uploads to GitHub code scanning |
676
775
  | `--engine <auto\|rg\|js>` | Search engine (default `auto`: scanner for small trees, ripgrep for big ones) |
677
- | `--strict` | Treat analysis that could not be completed as a failure |
678
- | `--allow-missing-targets` | Warn instead of failing when a `target` path does not exist |
679
- | `--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 |
680
779
  | `--print-baseline` | Print the `baseline="..."` that would exempt today's violations, and exit |
681
- | `--no-default-skips` | Search `.git`, `.hg`, `.svn` and `node_modules` too |
682
- | `--ignore-status` | Execute directives in draft, proposed and superseded documents too |
683
- | `--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 |
684
783
  | `--max-snippets <n>` | Failure snippets per assertion (default 5) |
685
784
  | `--concurrency <n>` | Search passes in flight at once (default 8) |
686
785
  | `--allow-empty` | Exit 0 when no spec file matched the patterns (about the run, not an assertion) |
@@ -688,6 +787,93 @@ spec-guard mcp [options] # serve the rules over MCP on stdio
688
787
 
689
788
  Patterns are expanded by spec-guard itself, so quoted globs behave identically
690
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.
691
877
 
692
878
  ### Exit codes
693
879
 
@@ -695,7 +881,8 @@ on Windows, macOS and Linux. A directory expands to the Markdown files in it.
695
881
  | --- | --- |
696
882
  | `0` | Every assertion held |
697
883
  | `1` | An assertion failed, or a directive was malformed |
698
- | `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 |
699
886
 
700
887
  ## CI integration
701
888
 
@@ -757,9 +944,10 @@ spec-guard's claims are about a whole repository - "this symbol appears nowhere
757
944
  in `src`" - and a language server is handed one buffer at a time. Answering a
758
945
  repository-wide question on every keystroke means rescanning the tree on every
759
946
  keystroke; the alternative is answering a smaller question and calling it the
760
- same one. The CLI does a full run of this repository in about 130 ms, so a
761
- pre-commit hook or a watch loop already closes the feedback gap without a
762
- daemon, an extension per editor, or a protocol version matrix.
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.
763
951
 
764
952
  ## How it works
765
953
 
@@ -1002,7 +1190,7 @@ npm install
1002
1190
  npm run build # tsc -> dist/
1003
1191
  npm test # vitest
1004
1192
  npm run test:coverage
1005
- npm run test:mutation # stryker (~16 min in CI; over two hours locally)
1193
+ npm run test:mutation # stryker (four parallel jobs in CI; hours locally)
1006
1194
  npm run lint # tsc --noEmit
1007
1195
  npm run selfcheck # run spec-guard on its own docs
1008
1196
  ```
@@ -1017,10 +1205,13 @@ prebuilt binary and is never a runtime dependency.
1017
1205
 
1018
1206
  Coverage says a line ran. It does not say an assertion would notice if the line
1019
1207
  behaved differently. This repository measures the difference: line coverage is
1020
- **100%**, and **97.65%** of 4,299 mutants are killed in CI, with no file below
1021
- 95%. The second number is the one worth reading - and it is the ceiling, not a
1022
- way-point: every surviving mutant has been checked individually and the 101 that
1023
- are left produce byte-identical output.
1208
+ **100%**, and **98.84%** of 7,763 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.
1024
1215
 
1025
1216
  That gap is the point. The first run scored 77.23%, and the weakest file was the
1026
1217
  reporter at 65.48% - not because it lacked tests, but because its tests were
@@ -1061,13 +1252,19 @@ quoted above; a full sweep is also what publishes the cache the branches start
1061
1252
  from, so an incremental verdict can never be built on another incremental
1062
1253
  verdict.
1063
1254
 
1064
- The full sweep was 6m54s at 2,118 mutants, 15m51s at 3,493, and is 17m at
1065
- 4,299. That growth is why the tiers exist: a check that gets quietly more
1066
- expensive every release is a check somebody eventually proposes lowering.
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.
1067
1264
 
1068
1265
  Run it locally with `npm run test:mutation` if you like, but do not calibrate
1069
1266
  anything on the result: on the Windows machine this was developed on the same
1070
- suite takes over two hours against 21 minutes hosted, and it scores *higher*,
1267
+ suite takes hours against 43 minutes of hosted time, and it scores *higher*,
1071
1268
  because far more mutants hang there and Stryker counts a hang as a kill. Linux
1072
1269
  CI is the measurement.
1073
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
- /** Where `spec-guard mcp` reads its messages. Only that command reads input. */
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\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;AAM5C,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;IACf,gFAAgF;IAChF,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED,oEAAoE;AACpE,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;AAEhD,yCAAyC;AACzC,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,YAAY,CAAC;IACrB,4EAA4E;IAC5E,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC;IACzB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,OAAO,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;AAExC;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,QAAQ,SAAoB,GAAG,MAAM,CAQ5D;AAED,eAAO,MAAM,IAAI,opGAoD8D,CAAC;AA2DhF,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAqJ1E;AAoED,0DAA0D;AAC1D,wBAAsB,IAAI,CAAC,IAAI,GAAE,SAAS,MAAM,EAA0B,EAAE,EAAE,GAAE,KAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8EpH"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}