@descent-vtt/spec-guard 0.12.0 → 0.12.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 CHANGED
@@ -5,6 +5,67 @@ 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.12.1
9
+
10
+ The query is fast again. A warm `query` in the MCP server took 27 ms over this
11
+ repository's specs after 0.12.0 moved to spec-core's scanner, past the 20 ms
12
+ ADR-0012 set; it now takes about 6 ms, faster than 0.11.0. The scanner makes
13
+ only what it is asked for, and the server parses a spec again only when its
14
+ bytes change. The shipped README and changelog link to the ADRs by address,
15
+ so the links lead somewhere on npmjs.com and inside `node_modules`.
16
+
17
+ ### Added
18
+
19
+ - **`createDocumentMemo`, and `documents` on `runSpecGuard`, `loadRuleSet`,
20
+ `queryRules` and `impactOf`**: parsed spec documents kept from one call to
21
+ the next, for a process that reads the specs on every request. Every spec
22
+ is still found and read, so an edit is seen by the next call; a document is
23
+ parsed again only when its bytes change. It holds one parse of each
24
+ document the spec set read last names, and forgets one removed, left out or
25
+ edited at the next read. The MCP server keeps one for its lifetime; a
26
+ command run once takes none, and parses every document and hashes none, as
27
+ before.
28
+
29
+ ### Fixed
30
+
31
+ - **The README's and the changelog's links to the ADRs lead somewhere on
32
+ npmjs.com and in node_modules.** They were relative, and the package ships
33
+ neither `docs/` nor `.github/`, so outside the repository the 66 links to
34
+ ADRs and the one to the release workflow led nowhere. Each is an absolute
35
+ URL to the file on GitHub now, and a link to a file the package ships stays
36
+ relative. `tests/package.test.ts` holds every Markdown document the package
37
+ ships to that, and each absolute link to a file the repository holds.
38
+
39
+ ### Changed
40
+
41
+ - `npm publish` in a checkout refuses to run outside GitHub Actions, so a
42
+ version cannot reach npm from a workstation by mistake, without provenance;
43
+ spec-harness 0.1.0 did. The release never runs it: it stages a tarball it
44
+ packed.
45
+ - **spec-core is copied again, from `8840d36`**, `markdown` alone, and
46
+ verified by hash. Its scanner makes links, list items and the directives
47
+ mask the first time each is read, and keeps them, where every scan made all
48
+ three; spec-guard reads the directives mask and neither of the others. It
49
+ also lists an image inside a link's text - a badge wrapped in a link - as a
50
+ link of its own, after the link. Nothing here reads links: over the 145
51
+ Markdown files of the five spec-* repositories every directive, error,
52
+ status, title and warning reads as it did, and a run, `prove` and `cites` of
53
+ this repository answer as they did.
54
+ [ADR-0015](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0015-globs-from-spec-core.md).
55
+ - **A warm query in the MCP server meets its 20 ms budget again**, over
56
+ this repository's 19 specs as over its 18 ADRs. 0.12.0 had lost it to
57
+ spec-core's scanner, which costs about three times what the parser it
58
+ replaced did. Measured together in one process, a query of one path took a
59
+ median of 6.3 ms, against 23.8 ms with `8840d36`'s lazy scan alone,
60
+ 27.4 ms for 0.12.0 and 7.9 ms for 0.11.0; over the ADRs alone, 6.0 ms
61
+ against 18.7, 22.4 and 6.3 ms. The lazy scan takes 13 to 21% off, run to
62
+ run; the rest is the server parsing only a document whose bytes changed. A server's first
63
+ request, and one after an edit, still parse what they read: 24.3 ms for a
64
+ first request here. The 23 ms recorded for 0.12.0 was most likely measured
65
+ over the ADRs alone; over the specs this repository names it was 29 ms.
66
+ [ADR-0012](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0012-query-and-mcp.md)'s
67
+ amendment has both measurements.
68
+
8
69
  ## 0.12.0
9
70
 
10
71
  The family's core, and three commands. Patterns and Markdown are read through
@@ -55,7 +116,7 @@ counts. What can differ, and where this changelog says why:
55
116
  `--ignore-status`, and `--json` with `formatVersion`. A path that does not
56
117
  exist is exit 2, and so is a run in which no spec matched, as for `query`:
57
118
  the dependents are written, and `--allow-empty` exits 0 with them. `impactOf`
58
- is the API. [ADR-0018](docs/adr/0018-impact.md).
119
+ is the API. [ADR-0018](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0018-impact.md).
59
120
  - **`get_dependents` on the MCP server**, a third tool beside
60
121
  `get_architectural_rules` and `check_architecture`: `impact` for a list of
61
122
  paths, with `depth` and `include_inactive`. Its structured content is the
@@ -85,7 +146,7 @@ counts. What can differ, and where this changelog says why:
85
146
  read, and are counted. Human, `--json` (with `formatVersion`), SARIF, GitHub
86
147
  and GitLab output; exit 1 on a ghost, 2 for a family whose files match no
87
148
  document. `findCitations` is the API.
88
- [ADR-0017](docs/adr/0017-citations-in-comments.md).
149
+ [ADR-0017](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0017-citations-in-comments.md).
89
150
  - **`spec-guard prove`: can each rule actually fail?** A passing rule cannot
90
151
  say whether the code holds or the rule cannot see the code. `prove` shows
91
152
  each rule in force a violation of itself - a file holding the forbidden text,
@@ -100,7 +161,7 @@ counts. What can differ, and where this changelog says why:
100
161
  `proveSpecGuard` is the same for a caller of the API. The first proof of
101
162
  this repository's own rules found one that could not fail as written, and
102
163
  CI now proves every rule the repository states on each change.
103
- [ADR-0016](docs/adr/0016-rules-seen-to-fail.md).
164
+ [ADR-0016](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0016-rules-seen-to-fail.md).
104
165
  - **`RunOptions.io`: a run over a tree that is not on disk.** `runSpecGuard`
105
166
  reads through the `Io` it is given - finding and reading the specs, checking
106
167
  targets exist, walking them, scanning files, reading imports and listing
@@ -110,7 +171,7 @@ counts. What can differ, and where this changelog says why:
110
171
  `engine: 'ripgrep'` with an `io` is refused with a message that says so, and
111
172
  `auto` with one searches with the built-in scanner. A search that fails
112
173
  outright through the door fails the run, rather than falling back to the
113
- scanner that reads the disk. [ADR-0014](docs/adr/0014-configuration-and-watch.md).
174
+ scanner that reads the disk. [ADR-0014](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0014-configuration-and-watch.md).
114
175
  - **`--format gitlab` and `--format github`**, for the run, `prove` and
115
176
  `cites`. `gitlab` writes a GitLab Code Quality report - `description`,
116
177
  `check_name`, a `fingerprint`, a `severity` and a `location` - which a
@@ -138,7 +199,7 @@ counts. What can differ, and where this changelog says why:
138
199
  as front matter. Warnings about how a document was read - a front-matter
139
200
  status that cannot be read, a block never closed - are `specWarnings`.
140
201
  Both fields are added, so no `formatVersion` moves.
141
- [ADR-0002](docs/adr/0002-directive-format.md)'s amendment.
202
+ [ADR-0002](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0002-directive-format.md)'s amendment.
142
203
  - **`formatVersion: 1` on the run's `--json` and on `query --json`**, as
143
204
  `prove --json` has had from the start: a field removed or renamed moves it,
144
205
  a field added does not. Both documents are otherwise unchanged; this is one
@@ -163,7 +224,7 @@ counts. What can differ, and where this changelog says why:
163
224
  the same match takes under a millisecond. spec-core is copied into
164
225
  `src/vendor/spec-core` and verified by hash, so `dependencies` stays empty,
165
226
  and its MIT notice ships in the package at `src/vendor/spec-core/LICENSE`.
166
- [ADR-0015](docs/adr/0015-globs-from-spec-core.md).
227
+ [ADR-0015](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0015-globs-from-spec-core.md).
167
228
  - **ripgrep and the scanner still read some patterns differently**, so the same
168
229
  rule could count differently on either side of the tree size at which `auto`
169
230
  changes engine. ripgrep anchored every alternative of `{src/*.ts,*.md}`
@@ -179,7 +240,7 @@ counts. What can differ, and where this changelog says why:
179
240
  rules. It is now decided by spec-core's Markdown scanner, which every spec-*
180
241
  tool reads documents with, copied into `src/vendor/spec-core` from `cbe2223`
181
242
  and verified by hash; `maskCode` is still exported, and is the scanner's
182
- mask. [ADR-0002](docs/adr/0002-directive-format.md)'s amendment.
243
+ mask. [ADR-0002](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0002-directive-format.md)'s amendment.
183
244
 
184
245
  Directives that were hidden, and now execute:
185
246
  - **A backtick fence's info string may not hold a backtick.** A line that
@@ -251,7 +312,7 @@ counts. What can differ, and where this changelog says why:
251
312
  `glob="*.{ts,tsx}"` was always the two patterns `*.{ts` and `tsx}`, and the
252
313
  message now says so. A configuration's `exclude` is a JSON array, where braces
253
314
  keep their commas. Nothing restores the old readings.
254
- [ADR-0015](docs/adr/0015-globs-from-spec-core.md).
315
+ [ADR-0015](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0015-globs-from-spec-core.md).
255
316
  - **`**` inside a name is refused**: `docs/**.md`, `src/**.ts`, `**.ts`,
256
317
  `a**b`. The message says what `**` means and gives the two ways to write
257
318
  what was meant, `docs/**/*.md` for any depth and `*.md` for one level. It
@@ -275,7 +336,7 @@ counts. What can differ, and where this changelog says why:
275
336
  An archived document is still parsed, validated and named in every output
276
337
  format. `--ignore-status` executes it as before. The list stays closed:
277
338
  `done`, `closed`, `archive` and every other near word stay in force, and
278
- [ADR-0010](docs/adr/0010-spec-status.md)'s amendment says why this word and
339
+ [ADR-0010](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0010-spec-status.md)'s amendment says why this word and
279
340
  no other.
280
341
  - **A status is found by the scanner's headings and front-matter reader.**
281
342
  The three spellings, their order and the words are unchanged, and every
@@ -305,7 +366,7 @@ counts. What can differ, and where this changelog says why:
305
366
  `specWarnings` entry in `--json` (run and `prove`), a SARIF notification
306
367
  and a `spec-warning` annotation for GitHub and GitLab.
307
368
 
308
- [ADR-0010](docs/adr/0010-spec-status.md)'s second amendment.
369
+ [ADR-0010](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0010-spec-status.md)'s second amendment.
309
370
  - **A title is the first level-one heading as the scanner reads it.** An
310
371
  underlined one (`===`) is a title and one kept in a comment is not; a code
311
372
  span in it is kept as written, where it was blanked, and a comment in it is
@@ -318,7 +379,7 @@ counts. What can differ, and where this changelog says why:
318
379
  over this repository's 18 ADRs, and 23 ms against 9 ms for a warm query of
319
380
  one path, past the 20 ms ADR-0012 set out to meet. The scanner finds list
320
381
  items, links and tables that nothing here reads.
321
- [ADR-0002](docs/adr/0002-directive-format.md)'s amendment says where the
382
+ [ADR-0002](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0002-directive-format.md)'s amendment says where the
322
383
  remedy lies.
323
384
  - **The MCP server speaks the protocol through spec-core.** Classifying each
324
385
  request, the two eras, dispatch, result shapes and the stdio framing are
@@ -327,7 +388,7 @@ counts. What can differ, and where this changelog says why:
327
388
  client sees is unchanged, and every test of the server passes as it was
328
389
  written. One edge moved: a last line cut inside a UTF-8 character when stdin
329
390
  closes is read as far as it goes rather than dropped. The protocol's names
330
- are still exported from the package. [ADR-0015](docs/adr/0015-globs-from-spec-core.md).
391
+ are still exported from the package. [ADR-0015](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0015-globs-from-spec-core.md).
331
392
  - **API:**
332
393
  - `createGlobMatcher`, `createExcludeMatcher` and `expandSpecPatterns` throw
333
394
  for a pattern spec-core refuses - an unclosed `[` or `{`, an extended
@@ -361,7 +422,7 @@ expression can hold a quote — `/'/`, `/["']/`, `replace(/\/\//g, '')` — and
361
422
  without a rule for `/`, that quote opened a string that closed somewhere else
362
423
  entirely. Every text rule over a TypeScript project reads through this
363
424
  classifier, and this repository's own `src/parser.ts` was one of the files it
364
- lost its place in. [ADR-0006](docs/adr/0006-comment-classification.md).
425
+ lost its place in. [ADR-0006](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0006-comment-classification.md).
365
426
 
366
427
  ### Fixed
367
428
 
@@ -420,8 +481,8 @@ question asked per character has to be paid for per character.
420
481
  Fixes from running 0.10.2 over a Rust and .NET monorepo, where an import rule
421
482
  missed five `use` declarations in one crate and said nothing, and from probing
422
483
  every other language profile for the same mistake.
423
- [ADR-0006](docs/adr/0006-comment-classification.md),
424
- [ADR-0008](docs/adr/0008-polyglot-imports.md).
484
+ [ADR-0006](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0006-comment-classification.md),
485
+ [ADR-0008](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0008-polyglot-imports.md).
425
486
 
426
487
  ### Fixed
427
488
 
@@ -469,8 +530,8 @@ regular expression literal holding a quote, `/["']/` - is recorded in ADR-0006.
469
530
  Fixes from running 0.10.1 over a .NET solution, where a layer rule passed while
470
531
  the domain imported the application, and from reading C# as it is written in
471
532
  2026 while fixing that.
472
- [ADR-0011](docs/adr/0011-layers-and-cycles.md),
473
- [ADR-0008](docs/adr/0008-polyglot-imports.md).
533
+ [ADR-0011](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0011-layers-and-cycles.md),
534
+ [ADR-0008](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0008-polyglot-imports.md).
474
535
 
475
536
  ### Fixed
476
537
 
@@ -543,7 +604,7 @@ the domain imported the application, and from reading C# as it is written in
543
604
 
544
605
  Fixes from running 0.10.0 over a large monorepo, and from testing each exclude
545
606
  shape under both engines while making them.
546
- [ADR-0014](docs/adr/0014-configuration-and-watch.md).
607
+ [ADR-0014](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0014-configuration-and-watch.md).
547
608
 
548
609
  ### Fixed
549
610
 
@@ -619,8 +680,8 @@ shape under both engines while making them.
619
680
  Improvements from running 0.9.2 over a large polyglot monorepo: a
620
681
  configuration a Rust, Go or .NET root can hold, exclusions a whole project
621
682
  shares, and a cycle rule that can leave lazy loading out.
622
- [ADR-0014](docs/adr/0014-configuration-and-watch.md),
623
- [ADR-0011](docs/adr/0011-layers-and-cycles.md).
683
+ [ADR-0014](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0014-configuration-and-watch.md),
684
+ [ADR-0011](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0011-layers-and-cycles.md).
624
685
 
625
686
  ### Added
626
687
 
@@ -684,7 +745,7 @@ shares, and a cycle rule that can leave lazy loading out.
684
745
  unanalysable, as does `/*` inside JSX text.
685
746
  - Tracking JSX text in general was turned down: a TypeScript cast or a
686
747
  generic arrow read as markup would skip real code without a word.
687
- [ADR-0005](docs/adr/0005-import-assertions.md).
748
+ [ADR-0005](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0005-import-assertions.md).
688
749
 
689
750
  ## 0.9.1
690
751
 
@@ -700,13 +761,13 @@ shares, and a cycle rule that can leave lazy loading out.
700
761
  - `/>` anywhere else still starts a regular expression, so
701
762
  `replace(/>/g, '>')` reads as before.
702
763
 
703
- [ADR-0005](docs/adr/0005-import-assertions.md).
764
+ [ADR-0005](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0005-import-assertions.md).
704
765
 
705
766
  ## 0.9.0
706
767
 
707
768
  A project's policy can live in its `package.json`, and `spec-guard --watch`
708
769
  reports again as the tree changes, re-executing only the rules a change
709
- affected. [ADR-0014](docs/adr/0014-configuration-and-watch.md).
770
+ affected. [ADR-0014](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0014-configuration-and-watch.md).
710
771
 
711
772
  ### Added
712
773
 
@@ -774,7 +835,7 @@ affected. [ADR-0014](docs/adr/0014-configuration-and-watch.md).
774
835
  reported every other shard's files with the previous sweep's verdicts. The
775
836
  merge refused that sweep.
776
837
  - No mutants were left out, the per-mutant timeout is unchanged, and the gate
777
- is unchanged. [ADR-0003](docs/adr/0003-mutation-testing.md).
838
+ is unchanged. [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md).
778
839
 
779
840
  ### Fixed
780
841
 
@@ -796,7 +857,7 @@ affected. [ADR-0014](docs/adr/0014-configuration-and-watch.md).
796
857
 
797
858
  An architecture document can now state conventions about names and layout: what
798
859
  the files in a directory are called, what every package holds, and which files
799
- come in pairs. [ADR-0013](docs/adr/0013-structure-assertions.md).
860
+ come in pairs. [ADR-0013](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0013-structure-assertions.md).
800
861
 
801
862
  ### Added
802
863
 
@@ -847,7 +908,7 @@ come in pairs. [ADR-0013](docs/adr/0013-structure-assertions.md).
847
908
  had, less one in `glob.ts` that the new tests turned into a genuine hang. A
848
909
  local sweep had reported four survivors in the new listing cache as killed; CI
849
910
  found them, and they are tested. See
850
- [ADR-0003](docs/adr/0003-mutation-testing.md).
911
+ [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md).
851
912
 
852
913
  ## 0.7.0
853
914
 
@@ -877,7 +938,7 @@ than every release, and it is fixed below.
877
938
  `types="ignore"` asks the runtime question. JavaScript and TypeScript only,
878
939
  because a cycle needs to know which file an import *is*; imports that should
879
940
  have become an edge and did not are reported, and fail under `--strict`.
880
- See [ADR-0011](docs/adr/0011-layers-and-cycles.md) for the resolution table,
941
+ See [ADR-0011](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0011-layers-and-cycles.md) for the resolution table,
881
942
  and for why the other four languages are not in the graph.
882
943
  - `buildGraph`, `resolveReference`, `stronglyConnected`, `cyclicComponents`,
883
944
  `witness`, `checkLayers` and `referenceForms` are exported, with their types.
@@ -889,7 +950,7 @@ than every release, and it is fixed below.
889
950
  scripts. Rules in documents that are not in force are counted and named, and
890
951
  listed with `--ignore-status`. The arithmetic behind it is tested against the
891
952
  files a real run searches, under both engines and on random trees; see
892
- [ADR-0012](docs/adr/0012-query-and-mcp.md) for the three things it cannot see.
953
+ [ADR-0012](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0012-query-and-mcp.md) for the three things it cannot see.
893
954
  - **`spec-guard mcp`** - a Model Context Protocol server on stdio, with no
894
955
  dependency on the MCP SDK. Tools `get_architectural_rules` (the query) and
895
956
  `check_architecture` (a run, narrowed to the rules governing given paths, each
@@ -939,7 +1000,7 @@ than every release, and it is fixed below.
939
1000
  mutated build against the real one on thousands of random inputs. The query
940
1001
  and the server (`query.ts`, `rules.ts`, `mcp.ts`, `specs.ts`) are all at 100%;
941
1002
  most of the survivors found on the way were code nothing could observe, and it
942
- was deleted. See [ADR-0003](docs/adr/0003-mutation-testing.md) for both
1003
+ was deleted. See [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md) for both
943
1004
  accounts, and for why one sweep's count is good to about three.
944
1005
  - `version(manifest?)` takes the manifest to read, so its fallback for a broken
945
1006
  install - `0.0.0` rather than a crash - is tested.
@@ -984,7 +1045,7 @@ making sure a rule that stops running can never look like a rule that passed.
984
1045
  are still checked for typos, bad attributes and unresolvable values, so a
985
1046
  draft's mistake is found on the day it is written rather than on the day
986
1047
  everyone agrees the rule is right and stops looking at it.
987
- See [ADR-0010](docs/adr/0010-spec-status.md), which also records why there is
1048
+ See [ADR-0010](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0010-spec-status.md), which also records why there is
988
1049
  no per-directive `if-status` attribute.
989
1050
  - `--ignore-status` executes every directive whatever its document declares -
990
1051
  how to ask whether a draft would pass if you accepted it today.
@@ -1008,7 +1069,7 @@ making sure a rule that stops running can never look like a rule that passed.
1008
1069
  the whole suite per mutant, so on a loaded machine an unrelated test timing
1009
1070
  out is scored as a kill. Those patterns are now verified by applying every
1010
1071
  mutant by hand, `scripts/mutation-regex.mjs`, and the account is in
1011
- [ADR-0003](docs/adr/0003-mutation-testing.md).
1072
+ [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md).
1012
1073
 
1013
1074
  ## 0.5.1
1014
1075
 
@@ -1057,7 +1118,7 @@ new features, and nothing a user has to change.
1057
1118
  individually and produce byte-identical output, so that figure is the ceiling
1058
1119
  rather than a way-point — and 29 that were assumed equivalent turned out not
1059
1120
  to be, and are now tested. See
1060
- [ADR-0003](docs/adr/0003-mutation-testing.md), which also records a Stryker
1121
+ [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md), which also records a Stryker
1061
1122
  limitation found on the way: a mutant that stops a test file *loading* is
1062
1123
  reported as survived even though the suite is killing it.
1063
1124
  - **`src/imports.ts` went from 73.74% to 95.89%.** The tokenizer was tested as a
@@ -1095,13 +1156,13 @@ scope holds no files at all.
1095
1156
  Not four new tokenizers: the comment and string lexer from ADR-0006 masks the
1096
1157
  source and the readers work on what is left. Tree-sitter measured 94 MB
1097
1158
  unpacked against this package's 0.33 MB. See
1098
- [ADR-0008](docs/adr/0008-polyglot-imports.md).
1159
+ [ADR-0008](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0008-polyglot-imports.md).
1099
1160
  - **`baseline` and `ratchet`** on the absence assertions, for adopting a strict
1100
1161
  rule on a codebase that already breaks it. `baseline="src/legacy/a.ts:2"`
1101
1162
  names the debt by file and count. The ratchet is two-sided: new violations
1102
1163
  fail, and so does an entry the code no longer supports, because a baseline
1103
1164
  that only grows is an `exclude` with extra steps. `ratchet="one-way"` relaxes
1104
- the second half. See [ADR-0009](docs/adr/0009-debt-baselines.md).
1165
+ the second half. See [ADR-0009](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0009-debt-baselines.md).
1105
1166
  - **`--print-baseline`** prints the attribute that would exempt today's
1106
1167
  violations, for a human to paste. It prints; it does not edit. That is the
1107
1168
  whole answer to `--fix`, and ADR-0009 argues it.
@@ -1131,7 +1192,7 @@ scope holds no files at all.
1131
1192
  starts finding dependencies it previously reported as unanalysable.
1132
1193
  - **Mutation testing in CI runs in two tiers**: incremental on branches,
1133
1194
  a full authoritative sweep on `main` and nightly. The gate stays at 85 in
1134
- both. See [ADR-0003](docs/adr/0003-mutation-testing.md).
1195
+ both. See [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md).
1135
1196
 
1136
1197
  ### Fixed
1137
1198
 
@@ -1179,7 +1240,7 @@ found two and ripgrep found four, and neither said anything about the rest.
1179
1240
  - **The engines are one implementation.** ripgrep now answers only *which files
1180
1241
  contain this text*; the scanner does all counting, comment classification,
1181
1242
  binary handling and reporting for both. `--engine` changes how long a run
1182
- takes, not what it concludes. See [ADR-0007](docs/adr/0007-search-scope.md).
1243
+ takes, not what it concludes. See [ADR-0007](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0007-search-scope.md).
1183
1244
 
1184
1245
  ### Added
1185
1246
 
@@ -1218,7 +1279,7 @@ found two and ripgrep found four, and neither said anything about the rest.
1218
1279
  directions. A zero-dependency tokenizer resolves `import`, `export … from`,
1219
1280
  `import type`, dynamic `import()` with a literal, and `require()`. Module
1220
1281
  references it cannot resolve statically are reported, never counted as clean.
1221
- See [ADR-0005](docs/adr/0005-import-assertions.md).
1282
+ See [ADR-0005](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0005-import-assertions.md).
1222
1283
  - **`comments` attribute.** See Changed, below.
1223
1284
  - **`--allow-missing-targets`** to restore the previous handling of a `target`
1224
1285
  path that does not exist.
@@ -1234,7 +1295,7 @@ found two and ripgrep found four, and neither said anything about the rest.
1234
1295
  uncertain the text counts as code, and every run reports how many matches it
1235
1296
  excluded — a pass caused by comment exclusion is never silent.
1236
1297
  `comments="include"` restores counting per assertion.
1237
- See [ADR-0006](docs/adr/0006-comment-classification.md).
1298
+ See [ADR-0006](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0006-comment-classification.md).
1238
1299
  - **A `target` path that does not exist now fails the run** instead of warning
1239
1300
  and searching what remained. An assertion pointed at a renamed directory used
1240
1301
  to search nothing, find nothing, and report success. `--allow-missing-targets`
@@ -1270,9 +1331,9 @@ first published 0.3.0.
1270
1331
  enough for the JavaScript scanner to beat a ripgrep process spawn. The probe
1271
1332
  is the work — the enumeration it performs is reused by the scanner, so
1272
1333
  choosing costs nothing when the scanner wins.
1273
- See [ADR-0004](docs/adr/0004-adaptive-engine.md).
1334
+ See [ADR-0004](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0004-adaptive-engine.md).
1274
1335
  - **Mutation testing** with Stryker, wired into CI on every push, plus the tests
1275
- that closed the gaps it exposed. See [ADR-0003](docs/adr/0003-mutation-testing.md).
1336
+ that closed the gaps it exposed. See [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md).
1276
1337
 
1277
1338
  ### Changed
1278
1339
 
package/README.md CHANGED
@@ -331,9 +331,9 @@ These read the dependency rather than the text, in **five languages**:
331
331
  No parser and no new dependency. JavaScript gets a full tokenizer because a
332
332
  module reference can appear anywhere in an expression; the other four are read
333
333
  by masking comments and string literals with the classifier from
334
- [ADR-0006](docs/adr/0006-comment-classification.md) and then reading statements
334
+ [ADR-0006](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0006-comment-classification.md) and then reading statements
335
335
  off what is left. Tree-sitter would have cost 94 MB unpacked against this
336
- package's 0.33 MB - [ADR-0008](docs/adr/0008-polyglot-imports.md) has the
336
+ package's 0.33 MB - [ADR-0008](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0008-polyglot-imports.md) has the
337
337
  measurements, and the list of things a real parser would genuinely see that
338
338
  this does not.
339
339
 
@@ -384,7 +384,7 @@ and reported too, so a rule pointed at the wrong tree says "analysed 2 of 3
384
384
  files; 1 is in a language whose imports spec-guard cannot read (.razor)" rather than quietly passing - and if *none* of them can be read, the
385
385
  assertion fails rather than passing on an empty analysis.
386
386
 
387
- [ADR-0005](docs/adr/0005-import-assertions.md) has the measurements and the
387
+ [ADR-0005](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0005-import-assertions.md) has the measurements and the
388
388
  reasoning behind each boundary.
389
389
 
390
390
  ### `@assert-layers` - dependencies point one way
@@ -546,7 +546,7 @@ did not - a relative path matching no file, or an alias like `@/db` or
546
546
  `--strict`. An alias spelled like a real package, `@app/db` through `tsconfig`
547
547
  paths, is the one thing it cannot see and cannot report: that would mean
548
548
  reading `tsconfig.json`, which is the resolver
549
- [ADR-0011](docs/adr/0011-layers-and-cycles.md) declines to become, for the
549
+ [ADR-0011](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0011-layers-and-cycles.md) declines to become, for the
550
550
  reasons it gives.
551
551
 
552
552
  ### `@assert-structure` - what files are called, and what comes with them
@@ -639,7 +639,7 @@ src/handlers/invoice.ts (does not exist yet)
639
639
  partner: src/handlers/invoice.test.ts
640
640
  ```
641
641
 
642
- [ADR-0013](docs/adr/0013-structure-assertions.md) has the design and what it
642
+ [ADR-0013](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0013-structure-assertions.md) has the design and what it
643
643
  costs.
644
644
 
645
645
  ### `@assert-present` - this file exists
@@ -741,7 +741,7 @@ spec-guard docs/adr.md --print-baseline
741
741
  ```
742
742
 
743
743
  prints the attribute that would exempt exactly today's violations, for you to
744
- paste in. It prints; it does not edit. [ADR-0009](docs/adr/0009-debt-baselines.md)
744
+ paste in. It prints; it does not edit. [ADR-0009](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0009-debt-baselines.md)
745
745
  explains why that distinction is the whole design, and why there is no `--fix`
746
746
  for architecture rules.
747
747
 
@@ -806,7 +806,7 @@ found on the day it is written rather than on the day it is accepted.
806
806
  `--ignore-status` runs everything, which is how you ask whether a draft would
807
807
  pass if you accepted it today.
808
808
 
809
- [ADR-0010](docs/adr/0010-spec-status.md) has the full reasoning, including why
809
+ [ADR-0010](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0010-spec-status.md) has the full reasoning, including why
810
810
  there is no per-directive `if-status` attribute.
811
811
 
812
812
  ## Asking before writing - `query` and the MCP server
@@ -863,7 +863,7 @@ project's does not.
863
863
 
864
864
  The arithmetic that decides whether a rule governs a path is tested against the
865
865
  walk a real run makes, file for file, under both engines and on randomly
866
- generated trees. [ADR-0012](docs/adr/0012-query-and-mcp.md) lists the three
866
+ generated trees. [ADR-0012](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0012-query-and-mcp.md) lists the three
867
867
  things it cannot see: file content (binary, oversized, unreadable), symbolic
868
868
  links, and letter case on case-insensitive filesystems.
869
869
 
@@ -958,7 +958,7 @@ and strictness.
958
958
 
959
959
  A kill means the rule can fail, not that it catches the way the code is
960
960
  written: `module="src/db.ts"` is killed by `import 'src/db.ts'` though every
961
- file imports `./db.js`. [ADR-0016](docs/adr/0016-rules-seen-to-fail.md) has the
961
+ file imports `./db.js`. [ADR-0016](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0016-rules-seen-to-fail.md) has the
962
962
  violations for each kind of rule, and what `unprovable` means.
963
963
 
964
964
  ## Do the comments cite decisions in force? - `cites`
@@ -1026,7 +1026,7 @@ as does a file read in part or a check that looked for nothing. A family whose
1026
1026
  files match no document exits 2. `--json` (versioned by `formatVersion`),
1027
1027
  `--format sarif`, `--format github` and `--format gitlab` carry the same
1028
1028
  findings; `spec-guard cites src lib/a.ts` reads only those paths.
1029
- [ADR-0017](docs/adr/0017-citations-in-comments.md) has the design, and what is
1029
+ [ADR-0017](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0017-citations-in-comments.md) has the design, and what is
1030
1030
  out of scope: links to symbols, which need a parser the family does not ship.
1031
1031
 
1032
1032
  ## Who depends on this? - `impact`
@@ -1063,7 +1063,7 @@ src/db/client.ts
1063
1063
  ```
1064
1064
 
1065
1065
  - **Followed**: relative JavaScript and TypeScript imports, by the table the
1066
- cycle rule uses ([ADR-0011](docs/adr/0011-layers-and-cycles.md)), and relative
1066
+ cycle rule uses ([ADR-0011](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0011-layers-and-cycles.md)), and relative
1067
1067
  Python imports, which name a module beside the importing file.
1068
1068
  - **Counted, not followed**: Go imports, Rust `use`s, C# `using`s and absolute
1069
1069
  Python imports name modules, and which file a module is depends on `go.mod`,
@@ -1081,7 +1081,7 @@ force, which are otherwise counted, as `query` does. `--json` is versioned by
1081
1081
  written, but "no rules" over no specs is nothing measured. `--allow-empty`
1082
1082
  asks for the dependents alone, and exits 0. The MCP server answers the same
1083
1083
  question as `get_dependents`, with the same document.
1084
- [ADR-0018](docs/adr/0018-impact.md) has the table and what it leaves out.
1084
+ [ADR-0018](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0018-impact.md) has the table and what it leaves out.
1085
1085
 
1086
1086
  ## CLI
1087
1087
 
@@ -1220,7 +1220,7 @@ pure function of a file's bytes - tokenizing, comment masking, scanning - is
1220
1220
  remembered by the bytes' hash. Each of those is a way to report a tree that no
1221
1221
  longer exists, so a test changes trees at random and requires every report a
1222
1222
  session gives to equal a fresh run's, with six deliberately broken sessions
1223
- that it has to catch. [ADR-0014](docs/adr/0014-configuration-and-watch.md) has
1223
+ that it has to catch. [ADR-0014](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0014-configuration-and-watch.md) has
1224
1224
  the design, the measurements and the limits.
1225
1225
 
1226
1226
  On this repository, from a change to the report, in the quietest of four runs
@@ -1403,7 +1403,7 @@ exceeds a budget, in which case ripgrep takes over. The crossover above sits
1403
1403
  between 500 and 1,000 files, which is what the Windows budget of 512 encodes;
1404
1404
  on Linux it is far lower, because what is really being measured is process
1405
1405
  spawn cost. `scripts/bench-engines.mjs` reproduces this, and
1406
- [ADR-0004](docs/adr/0004-adaptive-engine.md) has the full tables.
1406
+ [ADR-0004](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0004-adaptive-engine.md) has the full tables.
1407
1407
 
1408
1408
  Treat single measurements from one machine with suspicion. On the development
1409
1409
  machine used here, an absence assertion over 2,000 files measures 303 ms with
@@ -1514,6 +1514,13 @@ and is there for a caller who wants to try a change of its own.
1514
1514
  and take an `io` as well; so do `loadRuleSet` and `queryRules`. Each command's
1515
1515
  JSON is the report its function returns, with a `formatVersion` in front.
1516
1516
 
1517
+ A process that reads the specs again for every request, as the MCP server
1518
+ does, can keep what it parsed: pass one `createDocumentMemo()` as `documents`
1519
+ to `runSpecGuard`, `loadRuleSet`, `queryRules` or `impactOf`, every time. Every
1520
+ spec is still found and read, so an edit is seen by the next call; a document
1521
+ whose bytes have not changed is not parsed again, and the memo holds only the
1522
+ documents of the spec set read last. A single run gains nothing from one.
1523
+
1517
1524
  ## Design decisions
1518
1525
 
1519
1526
  This tool was specified loosely and built opinionatedly. Where the
@@ -1539,7 +1546,7 @@ inside a comment is a character and `<!--` inside a code span is code, and a
1539
1546
  code span ends with its paragraph, so a stray backtick cannot hide the
1540
1547
  directives after it. A fence may be indented with the list item it sits in.
1541
1548
  Masking preserves every offset and line terminator, so reported line numbers
1542
- stay exact. [ADR-0002](docs/adr/0002-directive-format.md)'s amendment lists
1549
+ stay exact. [ADR-0002](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0002-directive-format.md)'s amendment lists
1543
1550
  where this differs from 0.11.0.
1544
1551
 
1545
1552
  What is masked is still counted. A comment shaped like a directive -
@@ -1587,7 +1594,7 @@ pattern - `glob`, `exclude`, `module`, a layer, a spec pattern - is read by the
1587
1594
  glob engine the spec-* tools share, copied from spec-core and checked by hash.
1588
1595
  A `RegExp` backtracks: `*-*-*-*-*-*x` took 55 seconds to fail one long file
1589
1596
  name. The automaton cannot, and ripgrep is handed each pattern spelled as the
1590
- automaton reads it ([ADR-0015](docs/adr/0015-globs-from-spec-core.md)).
1597
+ automaton reads it ([ADR-0015](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0015-globs-from-spec-core.md)).
1591
1598
 
1592
1599
  **Exit code 2 exists.** "Your specs failed" and "spec-guard could not run" are
1593
1600
  different facts, and CI should be able to tell them apart.
@@ -1604,11 +1611,11 @@ forbidden symbol sat in `.github/workflows/ci.yml`; the scanner and ripgrep also
1604
1611
  disagreed with each other, finding two matches and four on the same tree. Scope
1605
1612
  is now one policy that both engines are driven by, the skip list is four names
1606
1613
  long, and anything spec-guard could not inspect is reported rather than assumed
1607
- clean ([ADR-0007](docs/adr/0007-search-scope.md)).
1614
+ clean ([ADR-0007](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0007-search-scope.md)).
1608
1615
 
1609
1616
  **Comments are excluded by default, and the exclusion is reported.** Counting
1610
1617
  the note that records a deletion as an occurrence of the thing deleted punishes
1611
- the documentation this tool exists to keep honest ([ADR-0006](docs/adr/0006-comment-classification.md)).
1618
+ the documentation this tool exists to keep honest ([ADR-0006](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0006-comment-classification.md)).
1612
1619
  The reverse risk — a rule that quietly stops checking anything because every
1613
1620
  match now sits in a comment — is why every run says how many matches it dropped.
1614
1621
 
@@ -1625,19 +1632,19 @@ comment and string lexer that already existed, masking the source with it, and
1625
1632
  reading statements off what is left. Tree-sitter would have been the modern
1626
1633
  answer and costs 94 MB unpacked against this package's 0.33 MB; the four things
1627
1634
  a real parser would genuinely see that this cannot are listed in
1628
- [ADR-0008](docs/adr/0008-polyglot-imports.md) rather than glossed over.
1635
+ [ADR-0008](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0008-polyglot-imports.md) rather than glossed over.
1629
1636
 
1630
1637
  **There is no `--fix`.** Every edit a machine can make to a failing boundary
1631
1638
  assertion is an edit that records the rule no longer holding: widen the bound,
1632
1639
  add an exclusion, append to the baseline, insert an ignore comment. A one-flag
1633
1640
  path from red to green is a bad button for a person and a much worse one for an
1634
1641
  agent whose loop terminates on a green build. `--print-baseline` prints what you
1635
- could paste; it does not paste it ([ADR-0009](docs/adr/0009-debt-baselines.md)).
1642
+ could paste; it does not paste it ([ADR-0009](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0009-debt-baselines.md)).
1636
1643
 
1637
1644
  ## spec-guard checks itself
1638
1645
 
1639
- The invariants in [`docs/adr/0001-invariants.md`](docs/adr/0001-invariants.md)
1640
- and [`docs/adr/0002-directive-format.md`](docs/adr/0002-directive-format.md) are
1646
+ The invariants in [`docs/adr/0001-invariants.md`](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0001-invariants.md)
1647
+ and [`docs/adr/0002-directive-format.md`](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0002-directive-format.md) are
1641
1648
  executed against this repository on every CI run. The CLI never calls
1642
1649
  `console.log`, nothing outside the engine spawns a process, and the parser and
1643
1650
  reporter never touch the filesystem - because those documents say so, and the
@@ -1671,13 +1678,13 @@ prebuilt binary and is never a runtime dependency.
1671
1678
 
1672
1679
  Coverage says a line ran. It does not say an assertion would notice if the line
1673
1680
  behaved differently. This repository measures the difference: line coverage is
1674
- **100%**, and **99.20%** of 11,537 mutants are killed in CI, with 89 survivors and
1681
+ **100%**, and **99.20%** of 11,549 mutants are killed in CI, with 89 survivors and
1675
1682
  no file below 95%. The second number is the one worth reading, and what was done
1676
1683
  about the survivors matters more than the score. At 0.5.1 every surviving mutant
1677
1684
  was checked individually, and the 101 left then produced byte-identical output.
1678
1685
  Each release since has replayed the survivors its own code added before it
1679
1686
  shipped: they were killed, deleted as dead code, or shown equivalent and
1680
- recorded in [ADR-0003](docs/adr/0003-mutation-testing.md) with the evidence.
1687
+ recorded in [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md) with the evidence.
1681
1688
 
1682
1689
  That gap is the point. The first run scored 77.23%, and the weakest file was the
1683
1690
  reporter at 65.48% - not because it lacked tests, but because its tests were
@@ -1706,7 +1713,7 @@ applied one at a time and run through a fingerprint of 4,176 observations, and
1706
1713
  that only matters when a second statement follows, a type-only test that marks
1707
1714
  `import A, { B } from 'x'` type-only when loosened, a comment check that opens a
1708
1715
  block comment on `2*3`. All 29 are now tested and all 29 die. All of it is in
1709
- [ADR-0003](docs/adr/0003-mutation-testing.md), including a Stryker limitation
1716
+ [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md), including a Stryker limitation
1710
1717
  found on the way: a mutant that stops a test file *loading* is reported as
1711
1718
  survived even though the suite is in fact killing it.
1712
1719
 
@@ -1734,7 +1741,7 @@ suite takes hours against 43 minutes of hosted time, and it scores *higher*,
1734
1741
  because far more mutants hang there and Stryker counts a hang as a kill. Linux
1735
1742
  CI is the measurement.
1736
1743
 
1737
- Eight cautionary tales are in [ADR-0003](docs/adr/0003-mutation-testing.md): a
1744
+ Eight cautionary tales are in [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md): a
1738
1745
  run whose score was pure fiction because the mutants were never activated, a
1739
1746
  tuning knob that lifted the score six points without adding a test, the platform
1740
1747
  gap above, the baseline being re-anchored when a tokenizer arrived, and a score
@@ -1757,7 +1764,7 @@ npm version minor # or patch / major - writes package.json and makes the tag
1757
1764
  git push origin main --follow-tags
1758
1765
  ```
1759
1766
 
1760
- [`release.yml`](.github/workflows/release.yml) then:
1767
+ [`release.yml`](https://github.com/DescentVTT/spec-guard/blob/main/.github/workflows/release.yml) then:
1761
1768
 
1762
1769
  1. refuses the release unless the tag, `package.json` and a `## <version>`
1763
1770
  heading in `CHANGELOG.md` all agree;
package/dist/index.d.ts CHANGED
@@ -50,8 +50,8 @@ export type { Listing, StructureCheck, StructureRequest, StructureViolation, Tre
50
50
  export { lineStarts, locate, maskRanges } from './text.js';
51
51
  export { maskCode, parseAttributes, parseDirectives, parseDocument, parseStatus, parseTitle, INACTIVE_STATUSES } from './parser.js';
52
52
  export type { ParseContext, ParsedDocument } from './parser.js';
53
- export { readSpecs, specPath } from './specs.js';
54
- export type { SpecDocument, SpecSet } from './specs.js';
53
+ export { createDocumentMemo, readSpecs, specPath } from './specs.js';
54
+ export type { DocumentMemo, SpecDocument, SpecSet } from './specs.js';
55
55
  export { governs, layerPosition, leftOutByOwnExclude, viewRule, within } from './rules.js';
56
56
  export type { DocumentView, LayerPosition, PathShape, QueryPath, RuleView } from './rules.js';
57
57
  export { answerQuery, formatQuery, formatQueryJson, inQueriedPaths, loadRuleSet, queryRules, resolveQueryPath, viewDocument, QueryPathError, QUERY_FORMAT_VERSION, } from './query.js';