@descent-vtt/spec-guard 0.12.0 → 0.12.2

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,78 @@ 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.2
9
+
10
+ A packaging fix: the tarball no longer carries spec-core's internal README.
11
+ Nothing a command does has changed.
12
+
13
+ ### Fixed
14
+
15
+ - **The package ships one README, its own.** `files` named `README.md`,
16
+ which npm reads as a name at any depth, so the tarball carried spec-core's
17
+ vendored README beside the licence it ships; the entry is `/README.md`.
18
+
19
+ ## 0.12.1
20
+
21
+ The query is fast again. A warm `query` in the MCP server took 27 ms over this
22
+ repository's specs after 0.12.0 moved to spec-core's scanner, past the 20 ms
23
+ ADR-0012 set; it now takes about 6 ms, faster than 0.11.0. The scanner makes
24
+ only what it is asked for, and the server parses a spec again only when its
25
+ bytes change. The shipped README and changelog link to the ADRs by address,
26
+ so the links lead somewhere on npmjs.com and inside `node_modules`.
27
+
28
+ ### Added
29
+
30
+ - **`createDocumentMemo`, and `documents` on `runSpecGuard`, `loadRuleSet`,
31
+ `queryRules` and `impactOf`**: parsed spec documents kept from one call to
32
+ the next, for a process that reads the specs on every request. Every spec
33
+ is still found and read, so an edit is seen by the next call; a document is
34
+ parsed again only when its bytes change. It holds one parse of each
35
+ document the spec set read last names, and forgets one removed, left out or
36
+ edited at the next read. The MCP server keeps one for its lifetime; a
37
+ command run once takes none, and parses every document and hashes none, as
38
+ before.
39
+
40
+ ### Fixed
41
+
42
+ - **The README's and the changelog's links to the ADRs lead somewhere on
43
+ npmjs.com and in node_modules.** They were relative, and the package ships
44
+ neither `docs/` nor `.github/`, so outside the repository the 66 links to
45
+ ADRs and the one to the release workflow led nowhere. Each is an absolute
46
+ URL to the file on GitHub now, and a link to a file the package ships stays
47
+ relative. `tests/package.test.ts` holds every Markdown document the package
48
+ ships to that, and each absolute link to a file the repository holds.
49
+
50
+ ### Changed
51
+
52
+ - `npm publish` in a checkout refuses to run outside GitHub Actions, so a
53
+ version cannot reach npm from a workstation by mistake, without provenance;
54
+ spec-harness 0.1.0 did. The release never runs it: it stages a tarball it
55
+ packed.
56
+ - **spec-core is copied again, from `8840d36`**, `markdown` alone, and
57
+ verified by hash. Its scanner makes links, list items and the directives
58
+ mask the first time each is read, and keeps them, where every scan made all
59
+ three; spec-guard reads the directives mask and neither of the others. It
60
+ also lists an image inside a link's text - a badge wrapped in a link - as a
61
+ link of its own, after the link. Nothing here reads links: over the 145
62
+ Markdown files of the five spec-* repositories every directive, error,
63
+ status, title and warning reads as it did, and a run, `prove` and `cites` of
64
+ this repository answer as they did.
65
+ [ADR-0015](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0015-globs-from-spec-core.md).
66
+ - **A warm query in the MCP server meets its 20 ms budget again**, over
67
+ this repository's 19 specs as over its 18 ADRs. 0.12.0 had lost it to
68
+ spec-core's scanner, which costs about three times what the parser it
69
+ replaced did. Measured together in one process, a query of one path took a
70
+ median of 6.3 ms, against 23.8 ms with `8840d36`'s lazy scan alone,
71
+ 27.4 ms for 0.12.0 and 7.9 ms for 0.11.0; over the ADRs alone, 6.0 ms
72
+ against 18.7, 22.4 and 6.3 ms. The lazy scan takes 13 to 21% off, run to
73
+ run; the rest is the server parsing only a document whose bytes changed. A server's first
74
+ request, and one after an edit, still parse what they read: 24.3 ms for a
75
+ first request here. The 23 ms recorded for 0.12.0 was most likely measured
76
+ over the ADRs alone; over the specs this repository names it was 29 ms.
77
+ [ADR-0012](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0012-query-and-mcp.md)'s
78
+ amendment has both measurements.
79
+
8
80
  ## 0.12.0
9
81
 
10
82
  The family's core, and three commands. Patterns and Markdown are read through
@@ -55,7 +127,7 @@ counts. What can differ, and where this changelog says why:
55
127
  `--ignore-status`, and `--json` with `formatVersion`. A path that does not
56
128
  exist is exit 2, and so is a run in which no spec matched, as for `query`:
57
129
  the dependents are written, and `--allow-empty` exits 0 with them. `impactOf`
58
- is the API. [ADR-0018](docs/adr/0018-impact.md).
130
+ is the API. [ADR-0018](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0018-impact.md).
59
131
  - **`get_dependents` on the MCP server**, a third tool beside
60
132
  `get_architectural_rules` and `check_architecture`: `impact` for a list of
61
133
  paths, with `depth` and `include_inactive`. Its structured content is the
@@ -85,7 +157,7 @@ counts. What can differ, and where this changelog says why:
85
157
  read, and are counted. Human, `--json` (with `formatVersion`), SARIF, GitHub
86
158
  and GitLab output; exit 1 on a ghost, 2 for a family whose files match no
87
159
  document. `findCitations` is the API.
88
- [ADR-0017](docs/adr/0017-citations-in-comments.md).
160
+ [ADR-0017](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0017-citations-in-comments.md).
89
161
  - **`spec-guard prove`: can each rule actually fail?** A passing rule cannot
90
162
  say whether the code holds or the rule cannot see the code. `prove` shows
91
163
  each rule in force a violation of itself - a file holding the forbidden text,
@@ -100,7 +172,7 @@ counts. What can differ, and where this changelog says why:
100
172
  `proveSpecGuard` is the same for a caller of the API. The first proof of
101
173
  this repository's own rules found one that could not fail as written, and
102
174
  CI now proves every rule the repository states on each change.
103
- [ADR-0016](docs/adr/0016-rules-seen-to-fail.md).
175
+ [ADR-0016](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0016-rules-seen-to-fail.md).
104
176
  - **`RunOptions.io`: a run over a tree that is not on disk.** `runSpecGuard`
105
177
  reads through the `Io` it is given - finding and reading the specs, checking
106
178
  targets exist, walking them, scanning files, reading imports and listing
@@ -110,7 +182,7 @@ counts. What can differ, and where this changelog says why:
110
182
  `engine: 'ripgrep'` with an `io` is refused with a message that says so, and
111
183
  `auto` with one searches with the built-in scanner. A search that fails
112
184
  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).
185
+ scanner that reads the disk. [ADR-0014](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0014-configuration-and-watch.md).
114
186
  - **`--format gitlab` and `--format github`**, for the run, `prove` and
115
187
  `cites`. `gitlab` writes a GitLab Code Quality report - `description`,
116
188
  `check_name`, a `fingerprint`, a `severity` and a `location` - which a
@@ -138,7 +210,7 @@ counts. What can differ, and where this changelog says why:
138
210
  as front matter. Warnings about how a document was read - a front-matter
139
211
  status that cannot be read, a block never closed - are `specWarnings`.
140
212
  Both fields are added, so no `formatVersion` moves.
141
- [ADR-0002](docs/adr/0002-directive-format.md)'s amendment.
213
+ [ADR-0002](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0002-directive-format.md)'s amendment.
142
214
  - **`formatVersion: 1` on the run's `--json` and on `query --json`**, as
143
215
  `prove --json` has had from the start: a field removed or renamed moves it,
144
216
  a field added does not. Both documents are otherwise unchanged; this is one
@@ -163,7 +235,7 @@ counts. What can differ, and where this changelog says why:
163
235
  the same match takes under a millisecond. spec-core is copied into
164
236
  `src/vendor/spec-core` and verified by hash, so `dependencies` stays empty,
165
237
  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).
238
+ [ADR-0015](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0015-globs-from-spec-core.md).
167
239
  - **ripgrep and the scanner still read some patterns differently**, so the same
168
240
  rule could count differently on either side of the tree size at which `auto`
169
241
  changes engine. ripgrep anchored every alternative of `{src/*.ts,*.md}`
@@ -179,7 +251,7 @@ counts. What can differ, and where this changelog says why:
179
251
  rules. It is now decided by spec-core's Markdown scanner, which every spec-*
180
252
  tool reads documents with, copied into `src/vendor/spec-core` from `cbe2223`
181
253
  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.
254
+ mask. [ADR-0002](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0002-directive-format.md)'s amendment.
183
255
 
184
256
  Directives that were hidden, and now execute:
185
257
  - **A backtick fence's info string may not hold a backtick.** A line that
@@ -251,7 +323,7 @@ counts. What can differ, and where this changelog says why:
251
323
  `glob="*.{ts,tsx}"` was always the two patterns `*.{ts` and `tsx}`, and the
252
324
  message now says so. A configuration's `exclude` is a JSON array, where braces
253
325
  keep their commas. Nothing restores the old readings.
254
- [ADR-0015](docs/adr/0015-globs-from-spec-core.md).
326
+ [ADR-0015](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0015-globs-from-spec-core.md).
255
327
  - **`**` inside a name is refused**: `docs/**.md`, `src/**.ts`, `**.ts`,
256
328
  `a**b`. The message says what `**` means and gives the two ways to write
257
329
  what was meant, `docs/**/*.md` for any depth and `*.md` for one level. It
@@ -275,7 +347,7 @@ counts. What can differ, and where this changelog says why:
275
347
  An archived document is still parsed, validated and named in every output
276
348
  format. `--ignore-status` executes it as before. The list stays closed:
277
349
  `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
350
+ [ADR-0010](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0010-spec-status.md)'s amendment says why this word and
279
351
  no other.
280
352
  - **A status is found by the scanner's headings and front-matter reader.**
281
353
  The three spellings, their order and the words are unchanged, and every
@@ -305,7 +377,7 @@ counts. What can differ, and where this changelog says why:
305
377
  `specWarnings` entry in `--json` (run and `prove`), a SARIF notification
306
378
  and a `spec-warning` annotation for GitHub and GitLab.
307
379
 
308
- [ADR-0010](docs/adr/0010-spec-status.md)'s second amendment.
380
+ [ADR-0010](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0010-spec-status.md)'s second amendment.
309
381
  - **A title is the first level-one heading as the scanner reads it.** An
310
382
  underlined one (`===`) is a title and one kept in a comment is not; a code
311
383
  span in it is kept as written, where it was blanked, and a comment in it is
@@ -318,7 +390,7 @@ counts. What can differ, and where this changelog says why:
318
390
  over this repository's 18 ADRs, and 23 ms against 9 ms for a warm query of
319
391
  one path, past the 20 ms ADR-0012 set out to meet. The scanner finds list
320
392
  items, links and tables that nothing here reads.
321
- [ADR-0002](docs/adr/0002-directive-format.md)'s amendment says where the
393
+ [ADR-0002](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0002-directive-format.md)'s amendment says where the
322
394
  remedy lies.
323
395
  - **The MCP server speaks the protocol through spec-core.** Classifying each
324
396
  request, the two eras, dispatch, result shapes and the stdio framing are
@@ -327,7 +399,7 @@ counts. What can differ, and where this changelog says why:
327
399
  client sees is unchanged, and every test of the server passes as it was
328
400
  written. One edge moved: a last line cut inside a UTF-8 character when stdin
329
401
  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).
402
+ 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
403
  - **API:**
332
404
  - `createGlobMatcher`, `createExcludeMatcher` and `expandSpecPatterns` throw
333
405
  for a pattern spec-core refuses - an unclosed `[` or `{`, an extended
@@ -361,7 +433,7 @@ expression can hold a quote — `/'/`, `/["']/`, `replace(/\/\//g, '')` — and
361
433
  without a rule for `/`, that quote opened a string that closed somewhere else
362
434
  entirely. Every text rule over a TypeScript project reads through this
363
435
  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).
436
+ lost its place in. [ADR-0006](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0006-comment-classification.md).
365
437
 
366
438
  ### Fixed
367
439
 
@@ -420,8 +492,8 @@ question asked per character has to be paid for per character.
420
492
  Fixes from running 0.10.2 over a Rust and .NET monorepo, where an import rule
421
493
  missed five `use` declarations in one crate and said nothing, and from probing
422
494
  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).
495
+ [ADR-0006](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0006-comment-classification.md),
496
+ [ADR-0008](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0008-polyglot-imports.md).
425
497
 
426
498
  ### Fixed
427
499
 
@@ -469,8 +541,8 @@ regular expression literal holding a quote, `/["']/` - is recorded in ADR-0006.
469
541
  Fixes from running 0.10.1 over a .NET solution, where a layer rule passed while
470
542
  the domain imported the application, and from reading C# as it is written in
471
543
  2026 while fixing that.
472
- [ADR-0011](docs/adr/0011-layers-and-cycles.md),
473
- [ADR-0008](docs/adr/0008-polyglot-imports.md).
544
+ [ADR-0011](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0011-layers-and-cycles.md),
545
+ [ADR-0008](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0008-polyglot-imports.md).
474
546
 
475
547
  ### Fixed
476
548
 
@@ -543,7 +615,7 @@ the domain imported the application, and from reading C# as it is written in
543
615
 
544
616
  Fixes from running 0.10.0 over a large monorepo, and from testing each exclude
545
617
  shape under both engines while making them.
546
- [ADR-0014](docs/adr/0014-configuration-and-watch.md).
618
+ [ADR-0014](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0014-configuration-and-watch.md).
547
619
 
548
620
  ### Fixed
549
621
 
@@ -619,8 +691,8 @@ shape under both engines while making them.
619
691
  Improvements from running 0.9.2 over a large polyglot monorepo: a
620
692
  configuration a Rust, Go or .NET root can hold, exclusions a whole project
621
693
  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).
694
+ [ADR-0014](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0014-configuration-and-watch.md),
695
+ [ADR-0011](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0011-layers-and-cycles.md).
624
696
 
625
697
  ### Added
626
698
 
@@ -684,7 +756,7 @@ shares, and a cycle rule that can leave lazy loading out.
684
756
  unanalysable, as does `/*` inside JSX text.
685
757
  - Tracking JSX text in general was turned down: a TypeScript cast or a
686
758
  generic arrow read as markup would skip real code without a word.
687
- [ADR-0005](docs/adr/0005-import-assertions.md).
759
+ [ADR-0005](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0005-import-assertions.md).
688
760
 
689
761
  ## 0.9.1
690
762
 
@@ -700,13 +772,13 @@ shares, and a cycle rule that can leave lazy loading out.
700
772
  - `/>` anywhere else still starts a regular expression, so
701
773
  `replace(/>/g, '>')` reads as before.
702
774
 
703
- [ADR-0005](docs/adr/0005-import-assertions.md).
775
+ [ADR-0005](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0005-import-assertions.md).
704
776
 
705
777
  ## 0.9.0
706
778
 
707
779
  A project's policy can live in its `package.json`, and `spec-guard --watch`
708
780
  reports again as the tree changes, re-executing only the rules a change
709
- affected. [ADR-0014](docs/adr/0014-configuration-and-watch.md).
781
+ affected. [ADR-0014](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0014-configuration-and-watch.md).
710
782
 
711
783
  ### Added
712
784
 
@@ -774,7 +846,7 @@ affected. [ADR-0014](docs/adr/0014-configuration-and-watch.md).
774
846
  reported every other shard's files with the previous sweep's verdicts. The
775
847
  merge refused that sweep.
776
848
  - 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).
849
+ is unchanged. [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md).
778
850
 
779
851
  ### Fixed
780
852
 
@@ -796,7 +868,7 @@ affected. [ADR-0014](docs/adr/0014-configuration-and-watch.md).
796
868
 
797
869
  An architecture document can now state conventions about names and layout: what
798
870
  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).
871
+ come in pairs. [ADR-0013](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0013-structure-assertions.md).
800
872
 
801
873
  ### Added
802
874
 
@@ -847,7 +919,7 @@ come in pairs. [ADR-0013](docs/adr/0013-structure-assertions.md).
847
919
  had, less one in `glob.ts` that the new tests turned into a genuine hang. A
848
920
  local sweep had reported four survivors in the new listing cache as killed; CI
849
921
  found them, and they are tested. See
850
- [ADR-0003](docs/adr/0003-mutation-testing.md).
922
+ [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md).
851
923
 
852
924
  ## 0.7.0
853
925
 
@@ -877,7 +949,7 @@ than every release, and it is fixed below.
877
949
  `types="ignore"` asks the runtime question. JavaScript and TypeScript only,
878
950
  because a cycle needs to know which file an import *is*; imports that should
879
951
  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,
952
+ See [ADR-0011](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0011-layers-and-cycles.md) for the resolution table,
881
953
  and for why the other four languages are not in the graph.
882
954
  - `buildGraph`, `resolveReference`, `stronglyConnected`, `cyclicComponents`,
883
955
  `witness`, `checkLayers` and `referenceForms` are exported, with their types.
@@ -889,7 +961,7 @@ than every release, and it is fixed below.
889
961
  scripts. Rules in documents that are not in force are counted and named, and
890
962
  listed with `--ignore-status`. The arithmetic behind it is tested against the
891
963
  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.
964
+ [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
965
  - **`spec-guard mcp`** - a Model Context Protocol server on stdio, with no
894
966
  dependency on the MCP SDK. Tools `get_architectural_rules` (the query) and
895
967
  `check_architecture` (a run, narrowed to the rules governing given paths, each
@@ -939,7 +1011,7 @@ than every release, and it is fixed below.
939
1011
  mutated build against the real one on thousands of random inputs. The query
940
1012
  and the server (`query.ts`, `rules.ts`, `mcp.ts`, `specs.ts`) are all at 100%;
941
1013
  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
1014
+ was deleted. See [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md) for both
943
1015
  accounts, and for why one sweep's count is good to about three.
944
1016
  - `version(manifest?)` takes the manifest to read, so its fallback for a broken
945
1017
  install - `0.0.0` rather than a crash - is tested.
@@ -984,7 +1056,7 @@ making sure a rule that stops running can never look like a rule that passed.
984
1056
  are still checked for typos, bad attributes and unresolvable values, so a
985
1057
  draft's mistake is found on the day it is written rather than on the day
986
1058
  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
1059
+ See [ADR-0010](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0010-spec-status.md), which also records why there is
988
1060
  no per-directive `if-status` attribute.
989
1061
  - `--ignore-status` executes every directive whatever its document declares -
990
1062
  how to ask whether a draft would pass if you accepted it today.
@@ -1008,7 +1080,7 @@ making sure a rule that stops running can never look like a rule that passed.
1008
1080
  the whole suite per mutant, so on a loaded machine an unrelated test timing
1009
1081
  out is scored as a kill. Those patterns are now verified by applying every
1010
1082
  mutant by hand, `scripts/mutation-regex.mjs`, and the account is in
1011
- [ADR-0003](docs/adr/0003-mutation-testing.md).
1083
+ [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md).
1012
1084
 
1013
1085
  ## 0.5.1
1014
1086
 
@@ -1057,7 +1129,7 @@ new features, and nothing a user has to change.
1057
1129
  individually and produce byte-identical output, so that figure is the ceiling
1058
1130
  rather than a way-point — and 29 that were assumed equivalent turned out not
1059
1131
  to be, and are now tested. See
1060
- [ADR-0003](docs/adr/0003-mutation-testing.md), which also records a Stryker
1132
+ [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md), which also records a Stryker
1061
1133
  limitation found on the way: a mutant that stops a test file *loading* is
1062
1134
  reported as survived even though the suite is killing it.
1063
1135
  - **`src/imports.ts` went from 73.74% to 95.89%.** The tokenizer was tested as a
@@ -1095,13 +1167,13 @@ scope holds no files at all.
1095
1167
  Not four new tokenizers: the comment and string lexer from ADR-0006 masks the
1096
1168
  source and the readers work on what is left. Tree-sitter measured 94 MB
1097
1169
  unpacked against this package's 0.33 MB. See
1098
- [ADR-0008](docs/adr/0008-polyglot-imports.md).
1170
+ [ADR-0008](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0008-polyglot-imports.md).
1099
1171
  - **`baseline` and `ratchet`** on the absence assertions, for adopting a strict
1100
1172
  rule on a codebase that already breaks it. `baseline="src/legacy/a.ts:2"`
1101
1173
  names the debt by file and count. The ratchet is two-sided: new violations
1102
1174
  fail, and so does an entry the code no longer supports, because a baseline
1103
1175
  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).
1176
+ the second half. See [ADR-0009](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0009-debt-baselines.md).
1105
1177
  - **`--print-baseline`** prints the attribute that would exempt today's
1106
1178
  violations, for a human to paste. It prints; it does not edit. That is the
1107
1179
  whole answer to `--fix`, and ADR-0009 argues it.
@@ -1131,7 +1203,7 @@ scope holds no files at all.
1131
1203
  starts finding dependencies it previously reported as unanalysable.
1132
1204
  - **Mutation testing in CI runs in two tiers**: incremental on branches,
1133
1205
  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).
1206
+ both. See [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md).
1135
1207
 
1136
1208
  ### Fixed
1137
1209
 
@@ -1179,7 +1251,7 @@ found two and ripgrep found four, and neither said anything about the rest.
1179
1251
  - **The engines are one implementation.** ripgrep now answers only *which files
1180
1252
  contain this text*; the scanner does all counting, comment classification,
1181
1253
  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).
1254
+ takes, not what it concludes. See [ADR-0007](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0007-search-scope.md).
1183
1255
 
1184
1256
  ### Added
1185
1257
 
@@ -1218,7 +1290,7 @@ found two and ripgrep found four, and neither said anything about the rest.
1218
1290
  directions. A zero-dependency tokenizer resolves `import`, `export … from`,
1219
1291
  `import type`, dynamic `import()` with a literal, and `require()`. Module
1220
1292
  references it cannot resolve statically are reported, never counted as clean.
1221
- See [ADR-0005](docs/adr/0005-import-assertions.md).
1293
+ See [ADR-0005](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0005-import-assertions.md).
1222
1294
  - **`comments` attribute.** See Changed, below.
1223
1295
  - **`--allow-missing-targets`** to restore the previous handling of a `target`
1224
1296
  path that does not exist.
@@ -1234,7 +1306,7 @@ found two and ripgrep found four, and neither said anything about the rest.
1234
1306
  uncertain the text counts as code, and every run reports how many matches it
1235
1307
  excluded — a pass caused by comment exclusion is never silent.
1236
1308
  `comments="include"` restores counting per assertion.
1237
- See [ADR-0006](docs/adr/0006-comment-classification.md).
1309
+ See [ADR-0006](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0006-comment-classification.md).
1238
1310
  - **A `target` path that does not exist now fails the run** instead of warning
1239
1311
  and searching what remained. An assertion pointed at a renamed directory used
1240
1312
  to search nothing, find nothing, and report success. `--allow-missing-targets`
@@ -1270,9 +1342,9 @@ first published 0.3.0.
1270
1342
  enough for the JavaScript scanner to beat a ripgrep process spawn. The probe
1271
1343
  is the work — the enumeration it performs is reused by the scanner, so
1272
1344
  choosing costs nothing when the scanner wins.
1273
- See [ADR-0004](docs/adr/0004-adaptive-engine.md).
1345
+ See [ADR-0004](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0004-adaptive-engine.md).
1274
1346
  - **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).
1347
+ that closed the gaps it exposed. See [ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md).
1276
1348
 
1277
1349
  ### Changed
1278
1350
 
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';