@descent-vtt/spec-graph 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,190 @@
3
3
  Notable changes, newest first. Versions follow [semver](https://semver.org):
4
4
  a patch fixes behaviour without asking anything of a repository that upgrades.
5
5
 
6
+ ## 0.7.0
7
+
8
+ Three ways a run handed back something other than what it said it was: a
9
+ configuration that did not load reporting a clean graph, a `--baseline` that
10
+ read nothing accepting nothing, and `--verbose` breaking the document it was
11
+ printed above. The first two were found on an 885-document repository, and the
12
+ third while fixing them.
13
+
14
+ A minor rather than a patch. Two of the three turn a build that was passing
15
+ into one that fails, which is the whole point of them and exactly what the rule
16
+ at the top of this file says a patch may not do.
17
+
18
+ ### Changed
19
+
20
+ **A configuration that did not load stops the run, with exit `2`.** Invalid
21
+ JSON, an unknown key, a value of the wrong type, a project rule that does not
22
+ compile: each was printed to stderr, and the run then went on with defaults -
23
+ reporting `the specification graph is consistent`, exiting `0`, and writing
24
+ `"ok": true` into JSON, SARIF and Markdown. Under `--strict` as well. A
25
+ `{1.phse}` for `{1.phase}` in one rule's message was a green build over exactly
26
+ the files that rule fails.
27
+
28
+ It was the one place the exit codes disagreed with themselves, too:
29
+ `--rule not-a-rule=off` on the command line has always exited `2`, and the same
30
+ misspelling inside `"severities"` printed a line and passed. `--no-config`
31
+ checks on defaults, and the exit code then says which run it was. The old
32
+ behaviour was the decision in ADR-0010, and that ADR now carries why it moved.
33
+
34
+ **A `--baseline` that cannot be read stops the run.** Read as an empty baseline,
35
+ a typo in the path reports every accepted finding as new, passes `--ratchet`
36
+ with nothing left to be stale about, and looks like a regression nobody
37
+ introduced. A `"baseline"` named in configuration is unchanged and still
38
+ optional, because a repository declares the path before the first run records
39
+ the file - `--verbose` now says when it read nothing. Unreadable, as opposed to
40
+ absent, is an error wherever the path was written. A baseline that cannot be
41
+ *parsed* is still reported and ignored: it only ever suppresses, so a row that
42
+ is dropped is a finding reported. See
43
+ [ADR-0012](docs/adr/0012-a-baseline-is-a-ratchet.md).
44
+
45
+ ### Fixed
46
+
47
+ **`--verbose` no longer breaks the format it was combined with.** The line
48
+ naming which configuration file was read, and the one naming a project rule's
49
+ selector under `query`, went to stdout above the report. With `--format json`
50
+ that made the output unparseable; with `--format sarif` it made a file the
51
+ code-scanning uploader rejects over a schema rather than over the command that
52
+ was run. Both now go to stderr, with everything else a run says about itself.
53
+ [ADR-0015](docs/adr/0015-feedback-goes-where-the-tools-already-look.md) already
54
+ had the rule - rows "cannot go on stdout beside a JSON document without
55
+ breaking the parse" - and these two lines were the places that did not follow
56
+ it.
57
+
58
+ **An absolute baseline path is no longer resolved under the root.**
59
+ `--baseline /tmp/b.json` read `<root>/tmp/b.json` and `--record-baseline`
60
+ wrote there: the recording landed inside the corpus, or failed naming a
61
+ directory nobody had typed, and the read found nothing and said nothing. Both
62
+ platforms' spellings are absolute on either platform, since one repository is
63
+ read on a Windows checkout and in Linux CI from the same file.
64
+
65
+ - **A `package.json` configuration problem names `package.json`.** It reported
66
+ `"spec-graph" must be an object` with no file in it, and a problem that names
67
+ no file sends the reader to a `.spec-graph.json` that is not there.
68
+
69
+ ### Verified
70
+
71
+ | | |
72
+ | --- | --- |
73
+ | `npm run lint` | pass |
74
+ | `npm test` | 935 passing, 23 files |
75
+ | `npm run selfcheck` | pass, over 23 documents and 184 relations |
76
+ | focused sweep, every changed region | 100% |
77
+ | `src/cli.ts` alone, before and after | 80.43% over 797 mutants, then 81.11% over 826 |
78
+ | `break` | unchanged at 70 |
79
+
80
+ Each behaviour was also run against the built binary rather than only through
81
+ `main()`, including the absolute paths, which is where a Windows drive letter
82
+ either survives `toPosix` or does not.
83
+
84
+ Moving two lines from stdout to stderr cost two mutants that a JSON parse used
85
+ to kill for free, and both were replaced with an assertion: a flag that is only
86
+ ever asserted when it is passed stops meaning anything. Both `cli.ts` figures
87
+ above are single-file runs on one machine with `--coverageAnalysis all`, which
88
+ is the comparison ADR-0007 asks for and not the hosted sweep that governs.
89
+
90
+ ## 0.6.0
91
+
92
+ A diff of two graph exports that names only the changes it can tell apart,
93
+ every glob moved onto the automaton `~=` already ran on, and a directive that
94
+ annotates one item where it used to annotate every item in reach. The mutation
95
+ figure is lower than 0.5.0's, and it is the first a release has carried from
96
+ tests that no longer race each other on disk.
97
+
98
+ ### Fixed
99
+
100
+ **A glob can no longer keep a run busy for minutes.** 0.5.0 moved `~=` onto an
101
+ automaton that cannot backtrack and left globs on `RegExp`, on the argument
102
+ that a glob has no nested quantifiers. Nobody had timed it. Stars that are not
103
+ nested still divide a failing subject between them every possible way:
104
+
105
+ | glob | subject | `RegExp` | now |
106
+ | --- | --- | ---: | ---: |
107
+ | `**/*-*-*-*.md` | a 643-character hyphenated file name | 2.3s | 0.2ms |
108
+ | `*-*-*-x` | a 10,000-character reference target | 120s | 0.9ms |
109
+
110
+ The second is the one that matters: `--ignore-ref` and `ignoreReferences` are
111
+ matched against targets read out of documents, which nothing limits the length
112
+ of. Every glob - the patterns to check, `--ignore`, `--ignore-ref`, `--history` -
113
+ now runs on the same automaton as `~=`, verified against the `RegExp` it
114
+ replaces on every glob and path in a differential corpus. See
115
+ [ADR-0017](docs/adr/0017-a-predicate-must-finish.md).
116
+
117
+ - **An invalid glob names itself.** `docs/{a` used to report
118
+ `Invalid regular expression: /^docs\/(?:a$/i: Unterminated group`, about a
119
+ parenthesis nobody typed. It now reports `invalid glob "docs/{a": unclosed "{"`.
120
+ - **`--ignore-ref` reads case the same way on every platform.** It lower-cased
121
+ both sides, and on Windows alone also folded case the way the `i` flag does,
122
+ which made a handful of distinct characters - the micro sign and the Greek
123
+ mu - one target there and two everywhere else.
124
+ - **A `@spec-item` directive annotates one item.** Each item used to look for
125
+ its own, so a directive was found by every item within 200 characters below
126
+ it and by every item enclosing it. The question after a declared one took the
127
+ same id and vanished from the graph without a word, and a parent could take
128
+ its child's directive in place of its own. A directive now binds to the item
129
+ directly below it, with nothing but blank lines and comments between, or else
130
+ to the innermost item it is written on or indented under. An id two items
131
+ still claim, by declaring it twice or by declaring another item's number, is
132
+ a parse problem, and the first item keeps it. `bindItemDirectives` is
133
+ exported; `directiveFor` is unchanged and still exported.
134
+
135
+ ### Added
136
+
137
+ **`spec-graph diff <before.json> <after.json>` says what a change did to the
138
+ decisions:** documents added, removed, moved or accepted, relations added or
139
+ removed, and obligations resolved or reopened. It names only what it can tell
140
+ apart. An obligation's id is its position in its section, so one inserted
141
+ question renumbers every question below it, and a diff keyed on those ids would
142
+ report a closed question as reopened when nobody touched it. An obligation is
143
+ paired only by a declared id, or by its document, section and title when nothing
144
+ else shares them; anything unpaired is reported as having appeared or
145
+ disappeared. Human, `--format json` or `--format markdown`, and exit 0 either
146
+ way. See [ADR-0020](docs/adr/0020-a-diff-names-what-it-can-tell-apart.md).
147
+
148
+ - The JSON graph export names its `generator`, so a diff can warn when two
149
+ exports came from different versions, and each item says whether its id was
150
+ `declared`. Both are additions; the export is still version 1.
151
+ - `compileGlob(pattern, { ignoreCase })`, the matcher the walk uses, and an
152
+ `ignoreCase` option on `compilePattern` for a pattern that must respect case.
153
+ `globToRegExp` is unchanged and still exported.
154
+
155
+ ### Changed
156
+
157
+ One behaviour moves, and it can turn a run that passed into one that fails.
158
+
159
+ - **A `@spec-item` directive with prose, a heading or code between it and the
160
+ item below binds to nothing.** It used to reach any item starting within 200
161
+ characters. Its id, state and title no longer apply: the item takes its
162
+ numbered id, a `state="moot"` stops closing it, and a link to the declared id
163
+ is a `broken-reference`. Move the directive down to the line above the item.
164
+
165
+ ### Verified
166
+
167
+ | | |
168
+ |---|---|
169
+ | `npm run lint` | pass |
170
+ | `npm test` | 921 passing, 23 files |
171
+ | `npm run selfcheck` | pass, over 23 documents and 184 relations |
172
+ | hosted full sweep, which governs | **75.25%** over 10,519 mutants, four shards of 22 to 33 minutes |
173
+ | `break` | unchanged at 70 |
174
+
175
+ 0.5.0's 76.83% is not the figure to compare with. About three points of it were
176
+ kills no assertion made: tests writing to fixed paths deleted each other's files
177
+ under Stryker's parallel workers. Race-free, the sweep read 73.42% before
178
+ `spec-graph diff` and 75.25% with this release: `diff.ts` at 98.35%, and
179
+ `directives.ts` from 66.88% to 78.01%. Lose every timeout and it reads 71.89%.
180
+ See [ADR-0007](docs/adr/0007-mutation-testing.md) and
181
+ [ADR-0019](docs/adr/0019-the-sweep-runs-in-shards.md).
182
+
183
+ The sweep after the directive fix moved `extract.ts` down 0.18 points, among the
184
+ usual movement in files nobody touched, and this one was real. Four mutants
185
+ that hand an item to every specification in a register had gone from killed to
186
+ surviving: resolution now drops the duplicates they create, and the test that
187
+ caught them only counted items. It now also asserts that no problem is
188
+ reported.
189
+
6
190
  ## 0.5.0
7
191
 
8
192
  One shipped feature turned out to have shipped a false positive, and the
@@ -72,7 +256,7 @@ now a usage error rather than a pattern that silently matches nothing.
72
256
  Verified against the engine it replaces: **1.33 million pattern-subject pairs,
73
257
  zero disagreements**, 19,000 of them committed as a gate. It earned that twice
74
258
  over, on one clause of the language specification read backwards in two
75
- different places - the word characters `` recognises, and whether a character
259
+ different places - the word characters `\b` recognises, and whether a character
76
260
  whose upper case is ASCII may match `[A-Z]`. The second was found by the
77
261
  mutation score pointing at a corpus with a blind spot in it rather than at a
78
262
  test. See [ADR-0017](docs/adr/0017-a-predicate-must-finish.md) and
@@ -245,8 +429,11 @@ points.
245
429
  The incremental run on the same commit read 0.40 points low - the third
246
430
  observation after 3.18 high at 0.3.0 and 1.29 low at 0.4.0, and the reason
247
431
  [ADR-0007](docs/adr/0007-mutation-testing.md) calls it a signal rather than a
248
- figure. The tag's run also saved its own report to the cache, which is the first
249
- time the cache-key fix from 0.4.0 has been seen doing its job.
432
+ figure. The tag's run also logged a cache save, which this entry first read as
433
+ the cache-key fix from 0.4.0 doing its job. *Corrected 2026-09-13:* it saved a
434
+ copy of the stale report it had restored, because the full step never wrote one,
435
+ and an entry saved under a tag cannot be restored on `main` in any case. See
436
+ [ADR-0007](docs/adr/0007-mutation-testing.md).
250
437
 
251
438
  **The rebuild took 168 of its 180 minutes.** 13% more mutants than 0.4.0, and 63%
252
439
  longer on the hosted runner against 15% longer locally. At 0.4.0's old cap of
package/README.md CHANGED
@@ -391,7 +391,11 @@ predicate is linear in the subject whatever the pattern — `^([A-Za-z0-9_]+[ ]?
391
391
  against a fifty-four-character title takes `RegExp` 103 seconds and this 13
392
392
  microseconds. It reads the usual syntax minus backreferences and lookaround,
393
393
  which are not regular; both are refused when the selector is read, with the
394
- character pointed at. See [ADR-0017](docs/adr/0017-a-predicate-must-finish.md).
394
+ character pointed at. Globs - the patterns to check, `--ignore`, `--ignore-ref`,
395
+ `--history` - are matched by the same automaton, because a glob with three stars
396
+ in it is
397
+ enough to keep `RegExp` busy for two minutes over a long reference target. See
398
+ [ADR-0017](docs/adr/0017-a-predicate-must-finish.md).
395
399
 
396
400
  On a register, a region answers its file's front matter for every descriptive
397
401
  key — `fm.owner` on a decision inside a register is the register's owner — while
@@ -479,6 +483,11 @@ Directives are ordinary HTML comments — invisible in every Markdown renderer:
479
483
  A directive always wins, and the report says the state came from a directive, so
480
484
  an override is visible rather than mysterious.
481
485
 
486
+ A `@spec-item` annotates one item: the one directly below it, with nothing but
487
+ blank lines or other comments between, or else the one it is written on or
488
+ indented under. An id that two items end up with is a parse problem, listed by
489
+ `--verbose`, and the first item keeps it.
490
+
482
491
  ## Journals, changelogs and minutes
483
492
 
484
493
  A 2024 journal noting *"decision deferred to ADR-002"* is not delegating
@@ -552,8 +561,15 @@ spec-graph check --baseline .spec-graph-baseline.json --ratchet
552
561
 
553
562
  Now an undeclared finding fails **and** a declared one that no longer occurs
554
563
  fails, each named, with `--record-baseline` as the fix — and the diff of that
555
- file is the record of what was paid off. See
556
- [ADR-0012](docs/adr/0012-a-baseline-is-a-ratchet.md).
564
+ file is the record of what was paid off.
565
+
566
+ **A `--baseline` you typed has to be there.** A path that reads nothing accepts
567
+ nothing, reports every accepted finding as new, and passes `--ratchet` with
568
+ nothing left to be stale about, so a typo in it looks exactly like a regression
569
+ nobody introduced: it exits `2` instead. A `"baseline"` in the configuration is
570
+ the other case and stays optional, because a repository declares the path before
571
+ the first run records the file — `--verbose` says when it read nothing. Either
572
+ may be absolute. See [ADR-0012](docs/adr/0012-a-baseline-is-a-ratchet.md).
557
573
 
558
574
  ## In CI
559
575
 
@@ -624,11 +640,19 @@ a path typed on the command line stays relative to where you typed it. See
624
640
  A flag always wins over the file, and list flags **add** to it rather than
625
641
  replacing it — a `--ignore-ref` on the command line is one more exclusion, not a
626
642
  decision to discard what the repository already declared. `--verbose` prints
627
- which file was read; `--no-config` skips the mechanism entirely.
628
-
629
- A broken config is reported and the run continues on defaults, including an
630
- unknown key: a silently ignored `ignoreReference` is a configuration that looks
631
- applied and is not.
643
+ which file was read, on stderr with everything else a run says about itself, so
644
+ it can be combined with any `--format` and leave the document on stdout intact.
645
+ `--no-config` skips the mechanism entirely.
646
+
647
+ A broken config stops the run with exit `2`, each problem named on stderr —
648
+ invalid JSON, a value of the wrong type, an unknown key, a rule that does not
649
+ compile. A configuration that did not load checks a different repository than
650
+ the one configured, and would report *that* one as consistent: a `{1.phse}` for
651
+ `{1.phase}` in one rule's message is a green build over the files the correctly
652
+ spelled rule fails. An unknown key counts, for the same reason it is reported at
653
+ all — a silently ignored `ignoreReference` is a configuration that looks applied
654
+ and is not. `--no-config` checks on defaults instead, and the exit code then
655
+ says which run it was.
632
656
 
633
657
  ### Family rules
634
658
 
@@ -675,6 +699,7 @@ spec-graph query project:<rule> Run a registered rule by its id.
675
699
  spec-graph graph [patterns...] Export for Graphviz, Mermaid or JSON.
676
700
  spec-graph rules [rule-id] List the diagnostics; --explain adds
677
701
  each selector and the ADR behind it.
702
+ spec-graph diff <before> <after> Compare two JSON graph exports.
678
703
 
679
704
  --root <dir> Directory patterns resolve against, and the root of the
680
705
  run. Without it the config file is discovered upward and
@@ -688,7 +713,7 @@ spec-graph rules [rule-id] List the diagnostics; --explain adds
688
713
  --record-baseline <f> Write today's findings as accepted debt, exit 0
689
714
  --ratchet Also fail when a baseline entry no longer occurs
690
715
  --no-config Ignore .spec-graph.json and the package.json key
691
- --format <fmt> human | json | sarif | markdown
716
+ --format <fmt> human | json | sarif (check) | markdown (check, diff)
692
717
  --graph-format <fmt> dot | mermaid | json
693
718
  --documents-only Hide items; their relations lift onto their documents
694
719
  --rule <id>=<severity> error | warn | info | off (repeatable)
@@ -701,8 +726,9 @@ spec-graph rules [rule-id] List the diagnostics; --explain adds
701
726
  ```
702
727
 
703
728
  Exit codes: `0` clean, `1` findings, `2` the tool could not run. A mistyped flag
704
- never masquerades as a passing build, and a pattern matching nothing is an error
705
- rather than a silent success.
729
+ never masquerades as a passing build, and neither does a pattern that matches
730
+ nothing, a configuration file that did not load, or a `--baseline` that is not
731
+ there.
706
732
 
707
733
  ## Continuous integration
708
734
 
@@ -748,6 +774,28 @@ every finding, so a bot can annotate a diff:
748
774
  }
749
775
  ```
750
776
 
777
+ A check says what is wrong. `spec-graph diff` says what a pull request changed:
778
+ documents added, removed, moved or accepted, relations added or removed, and
779
+ obligations resolved or reopened. It compares two exports made by the same
780
+ binary, so CI makes one from the base branch and one from the pull request:
781
+
782
+ ```yaml
783
+ - run: git fetch --depth 1 origin "$GITHUB_BASE_REF" && git worktree add ../base FETCH_HEAD
784
+ - run: npx spec-graph graph --root ../base --graph-format json > base.json
785
+ - run: npx spec-graph graph --graph-format json > head.json
786
+ - run: npx spec-graph diff base.json head.json --format markdown >> "$GITHUB_STEP_SUMMARY"
787
+ ```
788
+
789
+ It names only what it can tell apart. A document keeps its id through a rename,
790
+ so a moved document reads as moved. An obligation's id is its position in its
791
+ section, so inserting a question renumbers every question below it; an
792
+ obligation is therefore paired only by an id its author declared, or by its
793
+ document, section and title when nothing else shares them. Anything unpaired
794
+ is reported as having appeared or disappeared, never as resolved or reopened.
795
+ Findings are left to `--baseline`, which already reports what is new. The exit
796
+ code is `0` whether anything changed or not: a diff describes, and `check`
797
+ gates. See [ADR-0020](docs/adr/0020-a-diff-names-what-it-can-tell-apart.md).
798
+
751
799
  ## Visualising
752
800
 
753
801
  ```bash
@@ -783,7 +831,7 @@ exported. Reporters, editor extensions and custom rules are all first-class.
783
831
 
784
832
  ## Design
785
833
 
786
- Eighteen ADRs, which `spec-graph` validates on every CI run:
834
+ Twenty ADRs, which `spec-graph` validates on every CI run:
787
835
 
788
836
  - [ADR-0001 — A hand-written Markdown scanner](docs/adr/0001-hand-written-markdown-scanner.md)
789
837
  - [ADR-0002 — A four-phase lifecycle lattice](docs/adr/0002-lifecycle-lattice.md)
@@ -803,6 +851,8 @@ Eighteen ADRs, which `spec-graph` validates on every CI run:
803
851
  - [ADR-0016 — A query needs a sentence before it is a rule](docs/adr/0016-a-query-needs-a-sentence.md)
804
852
  - [ADR-0017 — A predicate must finish](docs/adr/0017-a-predicate-must-finish.md)
805
853
  - [ADR-0018 — The configuration file is the root](docs/adr/0018-the-configuration-file-is-the-root.md)
854
+ - [ADR-0019 — The sweep runs in shards](docs/adr/0019-the-sweep-runs-in-shards.md)
855
+ - [ADR-0020 — A diff names only what it can tell apart](docs/adr/0020-a-diff-names-what-it-can-tell-apart.md)
806
856
 
807
857
  **Zero runtime dependencies.** Node 22+, native ESM, TypeScript strict with
808
858
  `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes`. The Markdown
package/dist/cli.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * The command line.
3
3
  *
4
- * Four verbs, because there are four questions worth asking of a specification
4
+ * Five verbs, because there are five questions worth asking of a specification
5
5
  * graph: is it consistent (`check`), what does it contain (`query`), what does
6
- * it look like (`graph`), and what will you check for me (`rules`).
6
+ * it look like (`graph`), what will you check for me (`rules`), and what did a
7
+ * change do to it (`diff`).
7
8
  *
8
9
  * Exit codes are the contract with CI: `0` clean, `1` findings, `2` the tool
9
10
  * itself could not run. A usage mistake never masquerades as a passing build.
@@ -16,7 +17,7 @@ export declare const EXIT_ERROR = 2;
16
17
  export declare class UsageError extends Error {
17
18
  constructor(message: string);
18
19
  }
19
- export type Command = 'check' | 'query' | 'graph' | 'rules';
20
+ export type Command = 'check' | 'query' | 'graph' | 'rules' | 'diff';
20
21
  export interface CliOptions {
21
22
  readonly command: Command;
22
23
  readonly patterns: readonly string[];
@@ -76,7 +77,7 @@ export interface CliIO {
76
77
  readonly env?: Readonly<Record<string, string | undefined>> | undefined;
77
78
  readonly isTTY?: boolean | undefined;
78
79
  }
79
- export declare const HELP = "spec-graph - turn Markdown specifications into a verifiable graph\n\nUSAGE\n spec-graph [check] [patterns...] [options]\n spec-graph query <selector|project:rule> [patterns...] [options]\n spec-graph graph [patterns...] [--graph-format dot|mermaid|json]\n spec-graph rules [rule-id] [--explain]\n\nCOMMANDS\n check Validate the specification graph. The default.\n query Run a selector - or a registered project rule, by its id - and\n print the matching paths.\n graph Export the graph for Graphviz, Mermaid, or another tool.\n rules List the diagnostics that will run, built in and project. Name one\n to see only that one; --explain adds its selector and the ADR that\n decided it.\n\nOPTIONS\n --root <dir> Directory the patterns resolve against. Without it,\n .spec-graph.json is looked for in the working\n directory and then upward as far as the repository,\n and the directory holding it becomes the root - so a\n run from a subdirectory reports what a run from the\n top reports. Paths typed on the command line stay\n relative to where they were typed.\n --ignore <glob> Skip paths. Repeatable.\n --ignore-ref <glob> Do not report these reference targets when they fail\n to resolve, for repositories where [[...]] tags a\n concept rather than naming a file. Repeatable.\n Suppresses findings only, never edges.\n --family <name> Families a bare identifier in prose may name. When\n given, everything else stays prose. Repeatable.\n --ignore-family <name> Families that are never citations - RFC when the repo\n cites RFC 2119 and keeps its own RFCs. Repeatable.\n --history <glob> Files that log what was decided rather than deciding\n it - journals, changelogs, minutes. Their links are\n still checked; their obligations are not. Repeatable.\n --baseline <file> Accept the findings recorded in this file and report\n only what is new since. Missing file = accept nothing.\n --record-baseline <f> Write today's findings to this file as accepted debt,\n and exit 0 without judging them.\n --ratchet Also fail when a baseline entry no longer occurs, so\n a paid-off exemption cannot outlive the defect.\n --no-config Ignore .spec-graph.json and the package.json key.\n --format <fmt> human, json, sarif or markdown. sarif is the\n interchange format GitHub code scanning and editors\n already read; markdown is a table for a pull-request\n comment or $GITHUB_STEP_SUMMARY (default: human)\n --graph-format <fmt> dot, mermaid or json (default: dot)\n --documents-only Leave items out of the exported graph\n --rule <id>=<severity> Override one rule: error, warn, info or off. A project\n rule is named in full: project:no-draft-dependency.\n Repeatable.\n --max <n> Show at most n findings (0 = no limit)\n --max-warnings <n> Fail when warnings exceed n (default: no limit)\n --strict Raise every warning to an error. An explicit --rule\n still wins, so --strict --rule x=warn exempts x.\n --color / --no-color Force colour on or off\n --ascii Use ASCII glyphs only\n --verbose Include parse problems, per-file detail, and every\n reference --ignore-ref or --ignore-family silenced\n -h, --help Show this help\n -v, --version Show the version\n\nSELECTORS\n A selector is a path through the graph.\n\n item[openness=open] -delegates-to-> document[phase=retired]\n document[phase=active] -assumes-> document[phase=retired]\n document =supersedes=> document\n *[path^=docs/adr] -contains-> item[state=accepted-debt]\n\n Node types: document, item, * (any)\n Attributes: id, kind, title, path, file, line, phase, status, receptivity,\n alias, document, state (or disposition), openness, section,\n text, body, evidence, conflicted, fm.<front-matter-key>\n Operators: = != ^= $= *= ~= and [attr] for \"is present\"\n ~= is a regular expression, matched by an automaton that cannot\n backtrack: linear in the subject, whatever the pattern. It reads\n the usual syntax minus backreferences and lookaround, which are\n not regular - both are refused when the selector is read, with\n the character pointed at\n Relations: -kind-> <-kind- =kind=> <=kind= (= forms are transitive)\n\nEXIT CODES\n 0 clean\n 1 findings at error severity (or over --max-warnings)\n 2 the tool could not run\n\nCONFIGURATION\n Anything repeated on every run belongs in the repository rather than in the\n command. spec-graph reads the first of these that exists:\n\n .spec-graph.json, spec-graph.config.json, or a \"spec-graph\" key in\n package.json\n\n { \"patterns\": [\"docs/**/*.md\"],\n \"ignoreReferences\": [\"trap *\"],\n \"ignoreFamilies\": [\"RFC\"],\n \"historyPatterns\": [\"**/JOURNAL_*.md\", \"archive/**\"],\n \"baseline\": \".spec-graph-baseline.json\",\n \"severities\": { \"self-reference\": \"off\" },\n \"strict\": true }\n\n A flag always wins over the file, and list flags add to it rather than\n replacing it.\n\nPROJECT RULES\n A convention spec-graph never anticipated is a selector plus a sentence, and\n belongs in the same file. Every rule declared here runs beside the built-ins\n and is reported, baselined, escalated by --strict and silenced by --rule in\n exactly the same way.\n\n { \"rules\": {\n \"no-draft-dependency\": {\n \"query\": \"document[phase=active] -depends-on-> document[phase=draft]\",\n \"message\": \"{0} depends on {1}, which is still a draft\",\n \"hint\": \"wait for {1} to be accepted, or drop it from {0.path}\",\n \"severity\": \"error\" } } }\n\n query One selector, or a list of them read as a union.\n message The headline. {0} is the first node on the path, {1} the next;\n {1.phase} and {0.fm.owner} read any selector attribute.\n hint The next action. Optional, and templated the same way.\n severity error, warn, info or off. Defaults to warn - a rule a team has\n just written has not yet earned the right to fail their build.\n\n The id is the name with project: in front, which is why it can never collide\n with a built-in. A selector that does not parse, or a {2} the query can never\n reach, is reported when the file is read rather than found missing later.\n\n To see what one matches without copying its selector out of the file:\n\n spec-graph query project:no-draft-dependency --verbose\n\nADOPTING THIS ON AN OLD REPOSITORY\n Record what is already wrong, then report only what happens next:\n\n spec-graph check --record-baseline .spec-graph-baseline.json\n git add .spec-graph-baseline.json\n\n The file is keyed by specification and citation, not by line number, so it\n survives edits, moves and renames. Findings that stop occurring are reported\n so the file can be tightened; nothing new gets in.\n\nEXAMPLES\n spec-graph \"docs/**/*.md\"\n spec-graph check --rule self-reference=off --format json\n spec-graph query 'item[openness=open] -delegates-to-> document[phase=retired]'\n spec-graph check --ignore-ref \"trap *\" # [[trap 55]] tags a concept, not a file\n spec-graph graph --documents-only --graph-format mermaid > graph.mmd\n spec-graph check --history \"**/JOURNAL_*.md\" # a log is not a specification\n spec-graph check --baseline .spec-graph-baseline.json\n spec-graph rules --explain # including this repository's own\n spec-graph rules ghost-handover --explain # and why it exists\n spec-graph check --rule project:no-draft-dependency=off\n spec-graph query project:no-draft-dependency # what does that rule match?\n spec-graph check --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n";
80
+ export declare const HELP = "spec-graph - turn Markdown specifications into a verifiable graph\n\nUSAGE\n spec-graph [check] [patterns...] [options]\n spec-graph query <selector|project:rule> [patterns...] [options]\n spec-graph graph [patterns...] [--graph-format dot|mermaid|json]\n spec-graph rules [rule-id] [--explain]\n spec-graph diff <before.json> <after.json> [--format human|json|markdown]\n\nCOMMANDS\n check Validate the specification graph. The default.\n query Run a selector - or a registered project rule, by its id - and\n print the matching paths.\n graph Export the graph for Graphviz, Mermaid, or another tool.\n rules List the diagnostics that will run, built in and project. Name one\n to see only that one; --explain adds its selector and the ADR that\n decided it.\n diff Compare two graph exports: documents added, removed, moved or\n accepted; relations added or removed; and obligations resolved or\n reopened where they can be told apart. Make both exports with the\n same spec-graph. Exits 0 whether anything changed or not.\n\nOPTIONS\n --root <dir> Directory the patterns resolve against. Without it,\n .spec-graph.json is looked for in the working\n directory and then upward as far as the repository,\n and the directory holding it becomes the root - so a\n run from a subdirectory reports what a run from the\n top reports. Paths typed on the command line stay\n relative to where they were typed.\n --ignore <glob> Skip paths. Repeatable.\n --ignore-ref <glob> Do not report these reference targets when they fail\n to resolve, for repositories where [[...]] tags a\n concept rather than naming a file. Repeatable.\n Suppresses findings only, never edges.\n --family <name> Families a bare identifier in prose may name. When\n given, everything else stays prose. Repeatable.\n --ignore-family <name> Families that are never citations - RFC when the repo\n cites RFC 2119 and keeps its own RFCs. Repeatable.\n --history <glob> Files that log what was decided rather than deciding\n it - journals, changelogs, minutes. Their links are\n still checked; their obligations are not. Repeatable.\n --baseline <file> Accept the findings recorded in this file and report\n only what is new since. The file has to be there: a\n path that reads nothing accepts nothing and says so\n nowhere. A \"baseline\" in the configuration may name a\n file not recorded yet; a path typed here may not.\n --record-baseline <f> Write today's findings to this file as accepted debt,\n and exit 0 without judging them.\n --ratchet Also fail when a baseline entry no longer occurs, so\n a paid-off exemption cannot outlive the defect.\n --no-config Ignore .spec-graph.json and the package.json key.\n --format <fmt> human, json, sarif or markdown. sarif is the\n interchange format GitHub code scanning and editors\n already read, for check; markdown is a table for a\n pull-request comment or $GITHUB_STEP_SUMMARY, for check\n or diff (default: human)\n --graph-format <fmt> dot, mermaid or json (default: dot)\n --documents-only Leave items out of the exported graph\n --rule <id>=<severity> Override one rule: error, warn, info or off. A project\n rule is named in full: project:no-draft-dependency.\n Repeatable.\n --max <n> Show at most n findings (0 = no limit)\n --max-warnings <n> Fail when warnings exceed n (default: no limit)\n --strict Raise every warning to an error. An explicit --rule\n still wins, so --strict --rule x=warn exempts x.\n --color / --no-color Force colour on or off\n --ascii Use ASCII glyphs only\n --verbose Include parse problems, per-file detail, and every\n reference --ignore-ref or --ignore-family silenced\n -h, --help Show this help\n -v, --version Show the version\n\nSELECTORS\n A selector is a path through the graph.\n\n item[openness=open] -delegates-to-> document[phase=retired]\n document[phase=active] -assumes-> document[phase=retired]\n document =supersedes=> document\n *[path^=docs/adr] -contains-> item[state=accepted-debt]\n\n Node types: document, item, * (any)\n Attributes: id, kind, title, path, file, line, phase, status, receptivity,\n alias, document, state (or disposition), openness, section,\n text, body, evidence, conflicted, fm.<front-matter-key>\n Operators: = != ^= $= *= ~= and [attr] for \"is present\"\n ~= is a regular expression, matched by an automaton that cannot\n backtrack: linear in the subject, whatever the pattern. It reads\n the usual syntax minus backreferences and lookaround, which are\n not regular - both are refused when the selector is read, with\n the character pointed at\n Relations: -kind-> <-kind- =kind=> <=kind= (= forms are transitive)\n\nEXIT CODES\n 0 clean\n 1 findings at error severity (or over --max-warnings)\n 2 the tool could not run\n\nCONFIGURATION\n Anything repeated on every run belongs in the repository rather than in the\n command. spec-graph reads the first of these that exists:\n\n .spec-graph.json, spec-graph.config.json, or a \"spec-graph\" key in\n package.json\n\n { \"patterns\": [\"docs/**/*.md\"],\n \"ignoreReferences\": [\"trap *\"],\n \"ignoreFamilies\": [\"RFC\"],\n \"historyPatterns\": [\"**/JOURNAL_*.md\", \"archive/**\"],\n \"baseline\": \".spec-graph-baseline.json\",\n \"severities\": { \"self-reference\": \"off\" },\n \"strict\": true }\n\n A flag always wins over the file, and list flags add to it rather than\n replacing it.\n\n A problem in that file stops the run with exit 2, named on stderr: invalid\n JSON, an unknown key, a rule that does not compile. A configuration that did\n not load checks a different repository than the one configured, and would\n report that one as consistent. --no-config runs on defaults instead.\n\nPROJECT RULES\n A convention spec-graph never anticipated is a selector plus a sentence, and\n belongs in the same file. Every rule declared here runs beside the built-ins\n and is reported, baselined, escalated by --strict and silenced by --rule in\n exactly the same way.\n\n { \"rules\": {\n \"no-draft-dependency\": {\n \"query\": \"document[phase=active] -depends-on-> document[phase=draft]\",\n \"message\": \"{0} depends on {1}, which is still a draft\",\n \"hint\": \"wait for {1} to be accepted, or drop it from {0.path}\",\n \"severity\": \"error\" } } }\n\n query One selector, or a list of them read as a union.\n message The headline. {0} is the first node on the path, {1} the next;\n {1.phase} and {0.fm.owner} read any selector attribute.\n hint The next action. Optional, and templated the same way.\n severity error, warn, info or off. Defaults to warn - a rule a team has\n just written has not yet earned the right to fail their build.\n\n The id is the name with project: in front, which is why it can never collide\n with a built-in. A selector that does not parse, or a {2} the query can never\n reach, is reported when the file is read rather than found missing later.\n\n To see what one matches without copying its selector out of the file:\n\n spec-graph query project:no-draft-dependency --verbose\n\nADOPTING THIS ON AN OLD REPOSITORY\n Record what is already wrong, then report only what happens next:\n\n spec-graph check --record-baseline .spec-graph-baseline.json\n git add .spec-graph-baseline.json\n\n The file is keyed by specification and citation, not by line number, so it\n survives edits, moves and renames. Findings that stop occurring are reported\n so the file can be tightened; nothing new gets in.\n\nEXAMPLES\n spec-graph \"docs/**/*.md\"\n spec-graph check --rule self-reference=off --format json\n spec-graph query 'item[openness=open] -delegates-to-> document[phase=retired]'\n spec-graph check --ignore-ref \"trap *\" # [[trap 55]] tags a concept, not a file\n spec-graph graph --documents-only --graph-format mermaid > graph.mmd\n spec-graph check --history \"**/JOURNAL_*.md\" # a log is not a specification\n spec-graph check --baseline .spec-graph-baseline.json\n spec-graph rules --explain # including this repository's own\n spec-graph rules ghost-handover --explain # and why it exists\n spec-graph check --rule project:no-draft-dependency=off\n spec-graph query project:no-draft-dependency # what does that rule match?\n spec-graph check --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n spec-graph diff base.json head.json --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n";
80
81
  /** Parses argv into options. Throws {@link UsageError} on anything malformed. */
81
82
  export declare function parseArgs(argv: readonly string[], cwd: string): CliOptions;
82
83
  /** Runs the CLI and returns the process exit code. Never throws. */