@descent-vtt/spec-graph 0.17.0 → 0.19.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
@@ -11,6 +11,163 @@ the family's
11
11
  [versioning policy](https://github.com/DescentVTT/spec-core/blob/main/docs/adr/0009-versions-before-1-0.md)
12
12
  records.
13
13
 
14
+ ## 0.19.0
15
+
16
+ A comma ends the clause a negation before a phrase is in, so
17
+ `if not, blocked by [ADR-0002](0002.md)` is the relation it states. A
18
+ library caller's own directory is where a relative `--root` is read
19
+ from, a drive letter names its volume in either case on Windows, and a
20
+ `\` in a root or a baseline is a separator wherever the run starts.
21
+
22
+ ### Changed
23
+
24
+ - **A comma ends the clause a negation before a phrase is in.**
25
+ `if not, blocked by [ADR-0002](0002.md)` and
26
+ `both or neither, because [ADR-0002](0002.md) says so` were citations:
27
+ the comma was blanked, and the negation then stood against the phrase.
28
+ Each is the relation it states now, `blocked-by` and `assumes`, and so
29
+ are `No, blocked by`, `Not yet, deferred to` and
30
+ `Like it or not, superseded by`. A comma still counts toward no phrase's
31
+ 40 characters, and a negation between the phrase and the reference
32
+ cancels it as before, whatever commas stand there. A `ghost-handover`,
33
+ `stale-premise` or supersession such a sentence states is reported, and
34
+ an open question it hands to a live document is no longer an
35
+ `orphaned-obligation`, so a check that passed can fail
36
+ ([ADR-0006](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0006-false-positives-cost-more.md)).
37
+ Upgrading: run `check`; a finding it now reports from such a sentence is
38
+ one the sentence states. A negation set off by commas from the phrase it
39
+ denies, `Never, ever, blocked by`, is read as the relation too: write it
40
+ without the commas.
41
+ - **A relative `--root` is read from the directory `main` is given.** The
42
+ library's `main({ argv, cwd })` compared two values of `--root` from
43
+ `cwd`, and read the one it kept from the process's own directory: `check
44
+ --root docs` checked another tree's `docs`, a `--record-baseline` beside
45
+ it was written there, and where that directory had no `docs` the root was
46
+ not there. The root, its configuration and its baselines are read from
47
+ `cwd` now, and a relative `cwd` is read from the process's directory, so
48
+ the walk for a configuration from it goes up to the repository as it does
49
+ from that directory, where it stopped at the top of the relative path.
50
+ The `spec-graph` command is as it was
51
+ ([ADR-0010](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0010-configuration-belongs-to-the-repository.md)).
52
+ Upgrading: a caller that passed a `cwd` other than the directory it meant
53
+ `--root` from passes that directory, or an absolute `--root`.
54
+ - **On Windows a drive letter names its volume in either case.** `--root
55
+ C:\x` beside `--root c:\x` was refused, exit `2`, as two directories, and
56
+ so were two baselines written so, and `C:docs` beside `c:docs`. Each is
57
+ one value now, taken once; past the letter two names are compared as
58
+ before, so `C:\x` beside `C:\X` is still refused (spec-core's ADR-0005,
59
+ 2026-10-19). Upgrading: nothing to do; a line refused for this runs now.
60
+ - **A `\` in a baseline or a root is a separator wherever the run starts.**
61
+ On Linux `--baseline 'sub\held.json'` typed at the root read a file of
62
+ that name, and `'..\sub\held.json'` typed one directory down read
63
+ `sub/held.json`; a `"baseline"` in the configuration was read as the
64
+ first, and two baselines that differed only so were refused as two. Each
65
+ reads `sub/held.json` now, as an absolute path and a pattern did already,
66
+ and a `--root` with a `\` is asked of the disk as it is walked. Windows
67
+ reads every one as before
68
+ ([ADR-0018](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0018-the-configuration-file-is-the-root.md)).
69
+ Upgrading: on Linux, rename a baseline whose file name holds a `\`; a
70
+ check that read it reads the path the `\` spells now.
71
+
72
+ ## 0.18.0
73
+
74
+ Two values for an option that takes one are compared as each is read, a
75
+ wrong command line is refused with `--help` or `--version` on it, and
76
+ `--help` beside `--version` is refused. A flag that asks for a family the
77
+ configuration ignores reads it, and a recording or a `--history` refuses
78
+ what it would leave unread. And a negation just before a phrase cancels
79
+ it, as one after it did: `Not blocked by [ADR-0002](0002.md)` was a
80
+ `blocked-by` edge, the opposite of its sentence.
81
+
82
+ ### Changed
83
+
84
+ - **Two values for an option that takes one are compared as each is
85
+ read.** They were compared as typed, so `check --root . --root ./` was
86
+ refused, exit `2`, for naming one directory two ways. A path is resolved
87
+ now as the run reads it, a baseline from the root a `--root` names, and a
88
+ count is the number it reads as, so `.` beside `./`, `held.json` beside
89
+ `./held.json` and `1` beside `" 1"` are each one value, taken once. Two
90
+ that read as two are refused as before
91
+ ([ADR-0010](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0010-configuration-belongs-to-the-repository.md)).
92
+ Upgrading: nothing to do; a line refused for this runs now.
93
+ - **A wrong command line is refused with `--help` or `--version` on it.**
94
+ Every refusal the command line makes of itself was answered with the help
95
+ or the version instead where either was on the line, as `--root a --root
96
+ b --help` printed the help, exit `0`: a pair beside its opposite, two
97
+ values, `--baseline` beside `--record-baseline`, a family asked for and
98
+ ignored, an option of another command, a format a command does not write,
99
+ `rules a b` and `diff` given three exports or an empty one. Each is exit
100
+ `2` now with the line it has without them, as an option nobody knows
101
+ always was. `query --help` and `diff --help` still print the help, and so
102
+ does a line only the run would refuse, `--root nowhere --help`. Upgrading:
103
+ where a script asks for the help or the version on a line it builds, mend
104
+ the line the refusal names, or ask with `--help` or `--version` alone.
105
+ - **`--help` beside `--version` is refused.** The help was printed and the
106
+ version went unread, in either order. It is exit `2` now, `--help is given
107
+ beside --version, and a run prints the help or the version: give one of
108
+ the two`; either given twice is that one once. Upgrading: ask for one of
109
+ the two.
110
+ - **`--family` reads a family the configuration's `ignoreFamilies` passes
111
+ over.** The list that ignores was asked first, so `check --family RFC`
112
+ over `"ignoreFamilies": ["RFC"]` passed over every RFC it asked for and
113
+ printed `the specification graph is consistent`, exit `0`, where the file
114
+ alone fails the check. A flag says what the run does where the file says
115
+ otherwise, as for every other key: the family is read now, so a check
116
+ that passed can fail
117
+ ([ADR-0010](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0010-configuration-belongs-to-the-repository.md)).
118
+ `--ignore-family` over `"families"` passes over the family it names, as
119
+ before. Upgrading: run `check` with the `--family` your scripts give; a
120
+ citation it now reports was one the run asked for and never read.
121
+ - **One family under both `families` and `ignoreFamilies` is a problem in
122
+ the configuration.** The name under `families` read as applied and did
123
+ nothing: `"families": ["RFC"]` beside `"ignoreFamilies": ["rfc"]` passed
124
+ over every citation in prose and reported a consistent graph. It stops
125
+ every command now, exit `2`, `"RFC" is in both "families" and
126
+ "ignoreFamilies", and a family's citations are read or they are passed
127
+ over: take it off one of the two`. Upgrading: keep the family on the list
128
+ that says what the repository does with it.
129
+ - **A recording refuses the options it does not read, and writes JSON as
130
+ JSON.** `--record-baseline` beside `--format json` wrote the line
131
+ `recorded ... in ...` where a script expected a document, and `--ratchet`,
132
+ `--strict`, `--max`, `--max-warnings`, `--color`, `--no-ascii` and
133
+ `--format sarif`, `github` or `gitlab` went unread with no word of them.
134
+ Those are exit `2` now, nothing written, `--strict is given beside
135
+ --record-baseline, and a recording writes the findings down and reports
136
+ none: give one of the two`, with `--help` or `--version` on the line too;
137
+ `--format json` writes `{ "version": 1,
138
+ "recorded": { "file", "findings", "entries" } }`; `human` and `markdown`
139
+ write the line as before. Upgrading: record with the options that choose
140
+ the findings alone, and read the document's `recorded` where a script
141
+ parsed the line.
142
+ - **A `--history` whose files an ignore leaves out is refused.** A pattern
143
+ marks the files a run reads, so `--ignore archive --history
144
+ "docs/archive/**"` read no journal, checked none of its links, and said
145
+ nothing. Where a `--history` typed on the line matches no file the run
146
+ reads and matches one an `--ignore`, or the configuration's `"ignore"`,
147
+ leaves out, the run is exit `2` now, naming the pattern and the ignores
148
+ there are. A pattern that matches no file at all is read as before.
149
+ Upgrading: drop the ignore that covers the journals, or the pattern that
150
+ names them.
151
+ - **A negation before the phrase cancels it, as one after it does.** `Not
152
+ blocked by [ADR-0002](0002.md)` was a `blocked-by` edge, `This doesn't
153
+ depend on` a dependency, and `It has not been superseded by [ADR-0002]`
154
+ made ADR-0002 the successor of the document that says it has none. A
155
+ negation as the last word before the phrase, or before a word of tense or
156
+ doubt such as `been`, `yet`, `longer` or `necessarily`, makes the
157
+ reference a citation now, contractions and `cannot` among them. `not
158
+ only`, `nothing but`, a quoted `"never"` and `Not in this release:
159
+ deferred to` state the relation and are read as before. And a negation is
160
+ read whatever mark stands before it, `blocked by:none`; `no-one` is read
161
+ as `no one`; and a code span that holds nothing but the word, ``blocked by
162
+ `none`:``, is read as the word, as `**none**` was. A `ghost-handover`,
163
+ `stale-premise` or a supersession such a sentence caused goes; an open
164
+ question in a retired document read as handed on by such a sentence is an
165
+ `orphaned-obligation`, so a check that passed can fail
166
+ ([ADR-0006](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0006-false-positives-cost-more.md)).
167
+ Upgrading: run `check`; where it now reports an `orphaned-obligation`, the
168
+ question is owed by nobody, as its sentence says: close it, or hand it to a
169
+ live document in so many words.
170
+
14
171
  ## 0.17.0
15
172
 
16
173
  Two things of which one went unread are refused, exit 2: an option that
package/README.md CHANGED
@@ -462,9 +462,20 @@ A phrase governs a reference when it stands no more than 40 characters before
462
462
  it, in the same statement: a sentence, a paragraph, a list item or a table cell.
463
463
 
464
464
  - A negation between the two - `owned by nobody` - cancels it, whatever mark
465
- stands after the word: `blocked by none: [ADR-0002](0002.md)` is a citation
466
- and no more. A hyphen joins the word to the next, which names a thing, so
467
- `blocked by the no-op in` cancels nothing.
465
+ stands before or after the word: `blocked by none: [ADR-0002](0002.md)` and
466
+ `blocked by:none` are each a citation and no more, and so is a code span
467
+ that holds nothing but the word, `` blocked by `none` ``. A hyphen joins the
468
+ word to the next, which names a thing, so `blocked by the no-op in` cancels
469
+ nothing; `no-one` is `no one`, and cancels.
470
+ - So does a negation just before the phrase: `not blocked by`, `never
471
+ deferred to`, `has not been superseded by`, `doesn't depend on`, `no longer
472
+ blocked by`. Only a space and a word of tense or doubt (`be`, `been`, `is`,
473
+ `yet`, `longer`, `necessarily` and a few more) stand between: `not only
474
+ blocked by`, `nothing but blocked by`, the option `"never" blocked by` and
475
+ `Not in this release: deferred to` each state the relation, and are read as
476
+ they state it. So do `if not, blocked by` and `both or neither, because`: a
477
+ comma ends the clause the negation is in, and counts toward no phrase's 40
478
+ characters.
468
479
  - A sentence written in Chinese ends at `。`, `?`, `!` or `;`, with a space
469
480
  after it or none, so `Deferred to later。另見 ADR-0002` hands nothing to
470
481
  ADR-0002. A stop only ends a statement sooner: it reads less, never more.
@@ -623,6 +634,19 @@ silenced the whole lot, including the links, and a journal full of 404s is
623
634
  exactly what this tool is for. See
624
635
  [ADR-0011](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0011-a-record-is-not-a-specification.md).
625
636
 
637
+ So the two are not given together. A `--history` pattern that matches no file
638
+ the run reads, where the files it names are left out by `--ignore` or by
639
+ `"ignore"` in the configuration, stops the run with exit `2`, since no journal
640
+ it names would be read:
641
+
642
+ ```text
643
+ spec-graph: --history "docs/archive/**" names only files --ignore leaves out, and a journal is read or it is left out: give one of the two
644
+ ```
645
+
646
+ A pattern that matches no file at all is read as before, and `historyPatterns`
647
+ in the configuration under an `--ignore` is the flag deciding: the files are
648
+ left out.
649
+
626
650
  ## The query language
627
651
 
628
652
  The two flagship rules are not hand-written traversals. They are selectors,
@@ -783,6 +807,20 @@ git add .spec-graph-baseline.json
783
807
  }
784
808
  ```
785
809
 
810
+ A recording writes every finding down and judges none, so the options that
811
+ judge, show or colour a report are refused beside it, exit `2`: `--ratchet`,
812
+ `--strict`, `--max`, `--max-warnings`, `--color`, `--no-ascii`, and `--format`
813
+ `sarif`, `github` or `gitlab`, each a report of findings, of which a recording
814
+ makes none. `--format json` writes what was recorded as a document,
815
+
816
+ ```json
817
+ { "version": 1, "recorded": { "file": ".spec-graph-baseline.json", "findings": 2, "entries": 2 } }
818
+ ```
819
+
820
+ and `--format markdown` the line, which reads as Markdown already. The options
821
+ that choose what is found, `--rule` among them, are read by a recording as by a
822
+ check.
823
+
786
824
  CI is green from the first commit, every specification written afterwards is
787
825
  checked in full, and a new finding fails the build. When debt is paid, the run
788
826
  says so:
@@ -856,6 +894,9 @@ reports, byte for byte, because every path here is relative to the root.
856
894
  - A path typed on the command line stays relative to where you typed it, brace
857
895
  alternative by brace alternative: `{/docs,x}` typed in `sub` is `/docs` or
858
896
  `sub/x`.
897
+ - A `\` in a root or a baseline is a separator on every host, as in a pattern,
898
+ so `--baseline sub\held.json` typed at the root and `..\sub\held.json`
899
+ typed in `docs` are one file, `sub/held.json`, on Linux as on Windows.
859
900
 
860
901
  See [ADR-0018](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0018-the-configuration-file-is-the-root.md).
861
902
 
@@ -872,7 +913,8 @@ SARIF — and is 8 unless set; `0` lists none.
872
913
 
873
914
  A broken config stops the run with exit `2`, each problem named on stderr:
874
915
  invalid JSON, a value of the wrong type, an unknown key, a rule that does not
875
- compile, a family name no family has, a file that is there and cannot be read.
916
+ compile, a family name no family has, a family in both `families` and
917
+ `ignoreFamilies`, a file that is there and cannot be read.
876
918
  Why a mistake stops the run:
877
919
 
878
920
  - A configuration that did not load checks a different repository than the one
@@ -1040,6 +1082,14 @@ A list whose every name has a document sees none of them. `ignoreFamilies`
1040
1082
  needs no such check: a name on it passes over citations of that family alone,
1041
1083
  so a mistyped one passes over nothing and the run reports more, not less.
1042
1084
 
1085
+ A family is read or passed over, not both. The list that ignores is asked
1086
+ first, so a name on both lists passed over the family whatever the other said:
1087
+ one family under both keys of the file is a problem in it, exit `2`, as
1088
+ `--family` beside `--ignore-family` is on the command line. A flag says what
1089
+ the run does where the file says otherwise: `--family RFC` over
1090
+ `"ignoreFamilies": ["RFC"]` reads the RFCs' citations, and `--ignore-family ADR`
1091
+ over `"families": ["ADR"]` passes over the ADRs'.
1092
+
1043
1093
  Both are consulted only after resolution has already failed, so `RFC 0001` still
1044
1094
  resolves to your local RFC-0001 with `RFC` on the ignore list. No configuration
1045
1095
  can delete an edge. See
@@ -1139,12 +1189,22 @@ variable in `--family "$FAMILIES"` is the usual way to write one:
1139
1189
  would be read and the earlier go unread: `--root nowhere --root .` would
1140
1190
  check `.` and never say that `nowhere` is not there. The line names both,
1141
1191
  `--root is given "nowhere" and ".", and takes one value: give one of the
1142
- two`. The same value twice is that value once, as typed, and an option
1143
- that is repeatable above takes each of its values;
1192
+ two`. Two values are compared as each is read, so one value twice is that
1193
+ value once however it is spelled: `--root .` beside `--root ./` names one
1194
+ directory, a baseline is a path resolved as the root is, and `--max 1`
1195
+ beside `--max " 1"` is one number. On Windows a drive letter names its
1196
+ volume in either case, so `--root C:\x` beside `--root c:\x` is one
1197
+ directory; past the letter two names are two, `C:\x` beside `C:\X`. An
1198
+ option that is repeatable above takes each of its values;
1144
1199
  - `--baseline` beside `--record-baseline`: a recording writes every finding
1145
1200
  of the run and opens no baseline, so the one to read would go unread. A
1146
1201
  `"baseline"` in the configuration is no option, and is recorded over as
1147
- before;
1202
+ before. So are the options a recording does not read: `--ratchet`,
1203
+ `--strict`, `--max`, `--max-warnings`, `--color`, `--no-ascii`, and
1204
+ `--format sarif`, `github` or `gitlab`
1205
+ ([Adopting this on a repository that predates it](#adopting-this-on-a-repository-that-predates-it));
1206
+ - a `--history` pattern whose files an ignore leaves out, so that no journal
1207
+ it names is read ([Journals, changelogs and minutes](#journals-changelogs-and-minutes));
1148
1208
  - one family given to both `--family` and `--ignore-family`, whose citations
1149
1209
  are passed over whatever asked for them: alone on the allowlist it would
1150
1210
  leave every citation as prose. One family asked for beside another ignored
@@ -1155,7 +1215,18 @@ variable in `--family "$FAMILIES"` is the usual way to write one:
1155
1215
  A name that does begin so is written `./--name` for a file, `[-]-name` in
1156
1216
  a pattern and `\--name` for a reference target;
1157
1217
  - a configuration file that is there and cannot be read, a directory called
1158
- `.spec-graph.json` among them.
1218
+ `.spec-graph.json` among them;
1219
+ - `--help` beside `--version`, in either order: one of the two answers would
1220
+ go unread.
1221
+
1222
+ Each of those the command line can tell from itself, an option a recording
1223
+ does not read among them, is refused with `--help` or `--version` on the
1224
+ line too, as an option nobody knows always was: answered first, the help
1225
+ told a script nothing of the line it wrote, and the version answered as if
1226
+ the line were right. What a command still needs, a selector or two exports,
1227
+ is what the help is for, so `query --help` and `diff --help` print it; and
1228
+ so does a line only the run would find wrong, `--root nowhere --help` or a
1229
+ `--history` an ignore leaves out, since the help reads no disk.
1159
1230
 
1160
1231
  An option belongs to the commands that read it. Any other command refuses it
1161
1232
  by name, `--strict belongs to check, not to query`, rather than run as if the
@@ -1217,8 +1288,8 @@ that adds `--no-color` to a line already holding `--color` would be told
1217
1288
  nothing of which it got. Those are the two pairs the command line has; no
1218
1289
  other option has an opposite to be given beside. Either of a pair alone wins
1219
1290
  over the environment, which is what it is for, and the same option given
1220
- twice is that option once. `--help` and `--version` are answered whatever
1221
- else is on the line.
1291
+ twice is that option once. A pair is refused with `--help` or `--version`
1292
+ on the line as well.
1222
1293
 
1223
1294
  Without either, `SPEC_GRAPH_ASCII` set to anything but `0`, `false` or nothing
1224
1295
  asks for ASCII; and without that, a Windows console that sets neither
@@ -1262,13 +1333,13 @@ steps:
1262
1333
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
1263
1334
  with:
1264
1335
  persist-credentials: false
1265
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
1336
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
1266
1337
  with:
1267
1338
  node-version: '24'
1268
1339
  - run: npm ci
1269
1340
  - run: npx --no-install @descent-vtt/spec-graph --format sarif > spec-graph.sarif
1270
1341
  continue-on-error: true
1271
- - uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
1342
+ - uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
1272
1343
  with:
1273
1344
  sarif_file: spec-graph.sarif
1274
1345
  ```
@@ -1449,6 +1520,12 @@ const ghosts = query(graph, 'item[openness=open] -delegates-to-> document[phase=
1449
1520
  The graph, the scanner, the selector engine and every vocabulary table are
1450
1521
  exported. Reporters, editor extensions and custom rules are all first-class.
1451
1522
 
1523
+ The command line is `main`, which takes the line and the directory it was
1524
+ typed in: `await main({ argv: ['check', '--root', 'docs'], cwd })` reads
1525
+ `docs`, its configuration and any baseline beside it from `cwd`, and walks
1526
+ up from `cwd` for a configuration where no `--root` is given. Left out,
1527
+ `cwd` is the process's working directory, as for the `spec-graph` command.
1528
+
1452
1529
  ## Design
1453
1530
 
1454
1531
  Twenty-three ADRs, which `spec-graph` validates on every CI run:
@@ -1486,9 +1563,9 @@ tools share, whose code is copied into `src/vendor/` and checked by hash rather
1486
1563
  than installed. So the whole package is auditable in an afternoon — and nothing
1487
1564
  in the pipeline can take longer than its input.
1488
1565
 
1489
- **Verified, not just covered.** 2,260 tests; 99.4% statement and 99.8% line
1566
+ **Verified, not just covered.** 2,354 tests; 99.4% statement and 99.8% line
1490
1567
  coverage. Coverage says a line ran, so the suite is also held to a mutation
1491
- score - 96.60% over 9,004 mutants, measured by CI's full sweep, and a build
1568
+ score - 96.58% over 9,126 mutants, measured by CI's full sweep, and a build
1492
1569
  fails under 93 - because a vocabulary entry or a boundary
1493
1570
  condition can be weakened by an ordinary-looking refactor without a single test
1494
1571
  going red. [ADR-0007](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0007-mutation-testing.md) is straight about what
package/dist/cli.d.ts CHANGED
@@ -78,7 +78,7 @@ export interface CliIO {
78
78
  readonly env?: Readonly<Record<string, string | undefined>> | undefined;
79
79
  readonly isTTY?: boolean | undefined;
80
80
  }
81
- 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. It has to be a\n directory that is there.\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: one\n name each time, the prefix its identifiers are\n written with, ADR for ADR-0007. A document here has\n to belong to one of them, or the check stops: a list\n that names none allows nothing. It stops too where\n a name has no document and the list leaves out a\n family the documents do belong to, as RFCS for RFC\n does: one is most likely meant for the other.\n --ignore-family <name> Families that are never citations - RFC when the repo\n cites RFC 2119 and keeps its own RFCs. Repeatable,\n one name each time.\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, github, gitlab or markdown. json\n is for check, query or diff. sarif is the interchange\n format GitHub code scanning and editors already read,\n github the workflow commands a GitHub Actions log\n turns into annotations, and gitlab the Code Quality\n report a GitLab merge request reads, all three for\n check; markdown is a table for a pull-request comment\n or $GITHUB_STEP_SUMMARY, for check or diff (default:\n 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, whatever NO_COLOR, FORCE_COLOR\n and the terminal say. The two together are refused\n --ascii / --no-ascii ASCII glyphs or Unicode ones. SPEC_GRAPH_ASCII asks\n for ASCII, unless it is empty, 0 or false. Either\n option wins over it, and over what the terminal is\n taken for. The two together are refused\n --verbose Include parse problems, per-file detail, and every\n reference --ignore-ref or --ignore-family silenced\n --explain With rules: add each rule's selector and the ADR\n that decided it\n -h, --help Show this help\n -v, --version Show the version\n\n An option belongs to the commands that read it, and any other command\n refuses it by name:\n\n --root, --verbose check, query, graph, rules\n --ignore, --history check, query, graph\n --format check, query, diff\n --graph-format, --documents-only graph\n --explain rules\n every other option check\n\n --no-config, --no-color and --ascii are taken by every command: each is\n true already of a command with nothing to read it for. --color and\n --no-ascii are not: each would say that a command drew what only a check\n draws.\n\n An option beside its own opposite is refused, whichever comes first:\n --color beside --no-color, and --ascii beside --no-ascii. The later would\n decide and the earlier go unread. Either alone wins over the environment,\n and the same option twice is that option once.\n\n An option that takes one value is refused given two, as in --root a\n --root b: the later would be read and the earlier go unread. That is each\n option above with a value that is not marked Repeatable, and one rule\n given two severities by --rule. The same value twice is that value once.\n\n A baseline to read is refused beside one to record, --baseline beside\n --record-baseline: a recording writes every finding and opens no\n baseline. So is one family given to both --family and --ignore-family,\n whose citations are passed over whatever asked for them. One family asked\n for beside another ignored is taken.\n\n A value that begins with -- reads as an option, so the option before it is\n refused as missing its value. A name that does begin so is written ./--name\n for a file, [-]-name in a pattern and \\--name for a reference target.\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\n An input that is set and names nothing is a 2, named on stderr, and never\n read as if it had not been given: an empty value, a family name no family\n has, a list of families no document here belongs to, a --root that is no\n directory, a second word after rules, an option of another command. So\n are two things of which one would go unread: an option beside its own\n opposite, an option that takes one value given two, --baseline beside\n --record-baseline, a family both asked for and ignored.\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 \"maxRelated\": 3 }\n\n A flag always wins over the file, and list flags add to it rather than\n replacing it. maxRelated has no flag: it caps how many related locations one\n finding lists, 8 unless set, and 0 lists none.\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 file that is there and\n cannot be read. A configuration that did not load checks a different\n repository than the one configured, and would report that one as consistent.\n --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 check --format github # annotations from an Actions log\n spec-graph check --format gitlab > gl-code-quality-report.json\n spec-graph diff base.json head.json --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n";
81
+ 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. It has to be a\n directory that is there.\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: one\n name each time, the prefix its identifiers are\n written with, ADR for ADR-0007. A document here has\n to belong to one of them, or the check stops: a list\n that names none allows nothing. It stops too where\n a name has no document and the list leaves out a\n family the documents do belong to, as RFCS for RFC\n does: one is most likely meant for the other.\n --ignore-family <name> Families that are never citations - RFC when the repo\n cites RFC 2119 and keeps its own RFCs. Repeatable,\n one name each time.\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 A pattern whose files an ignore leaves out is\n refused: no journal it names would be read.\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. With --format json\n the recording is a JSON document.\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, github, gitlab or markdown. json\n is for check, query or diff. sarif is the interchange\n format GitHub code scanning and editors already read,\n github the workflow commands a GitHub Actions log\n turns into annotations, and gitlab the Code Quality\n report a GitLab merge request reads, all three for\n check; markdown is a table for a pull-request comment\n or $GITHUB_STEP_SUMMARY, for check or diff (default:\n 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, whatever NO_COLOR, FORCE_COLOR\n and the terminal say. The two together are refused\n --ascii / --no-ascii ASCII glyphs or Unicode ones. SPEC_GRAPH_ASCII asks\n for ASCII, unless it is empty, 0 or false. Either\n option wins over it, and over what the terminal is\n taken for. The two together are refused\n --verbose Include parse problems, per-file detail, and every\n reference --ignore-ref or --ignore-family silenced\n --explain With rules: add each rule's selector and the ADR\n that decided it\n -h, --help Show this help\n -v, --version Show the version\n\n An option belongs to the commands that read it, and any other command\n refuses it by name:\n\n --root, --verbose check, query, graph, rules\n --ignore, --history check, query, graph\n --format check, query, diff\n --graph-format, --documents-only graph\n --explain rules\n every other option check\n\n --no-config, --no-color and --ascii are taken by every command: each is\n true already of a command with nothing to read it for. --color and\n --no-ascii are not: each would say that a command drew what only a check\n draws.\n\n An option beside its own opposite is refused, whichever comes first:\n --color beside --no-color, and --ascii beside --no-ascii. The later would\n decide and the earlier go unread. Either alone wins over the environment,\n and the same option twice is that option once.\n\n An option that takes one value is refused given two, as in --root a\n --root b: the later would be read and the earlier go unread. That is each\n option above with a value that is not marked Repeatable, and one rule\n given two severities by --rule. Two values are compared as each is read,\n so one value twice is that value once however it is spelled: --root .\n beside --root ./ names one directory, and so on Windows does C:\\x beside\n c:\\x, a drive letter naming its volume in either case.\n\n A baseline to read is refused beside one to record, --baseline beside\n --record-baseline: a recording writes every finding and opens no\n baseline. So is each option a recording does not read, since it judges,\n shows and colours no finding: --ratchet, --strict, --max, --max-warnings,\n --color, --no-ascii, and --format sarif, github or gitlab. So is one\n family given to both --family and --ignore-family, whose citations are\n passed over whatever asked for them. One family asked for beside another\n ignored is taken.\n\n A value that begins with -- reads as an option, so the option before it is\n refused as missing its value. A name that does begin so is written ./--name\n for a file, [-]-name in a pattern and \\--name for a reference target.\n\n A line that is wrong is refused with --help or --version on it too, an\n option a recording does not read among them, and --help beside --version\n is refused: one of the two answers would go unread. What a command still\n needs, a selector or two exports, is what --help is for: query --help and\n diff --help print it. The help reads no disk, so what only the run finds,\n a --root that is not there or a --history an ignore leaves out, is\n answered by it.\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\n An input that is set and names nothing is a 2, named on stderr, and never\n read as if it had not been given: an empty value, a family name no family\n has, a list of families no document here belongs to, a --root that is no\n directory, a second word after rules, an option of another command. So\n are two things of which one would go unread: an option beside its own\n opposite, an option that takes one value given two, --baseline beside\n --record-baseline, an option a recording does not read, a family both\n asked for and ignored, a --history whose files an ignore leaves out,\n --help beside --version.\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 \"maxRelated\": 3 }\n\n A flag always wins over the file, and list flags add to it rather than\n replacing it: --family RFC reads the citations \"ignoreFamilies\" passes\n over. maxRelated has no flag: it caps how many related locations one\n finding lists, 8 unless set, and 0 lists none.\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, one family in both\n \"families\" and \"ignoreFamilies\", a file that is there and cannot be read. A configuration that did not load checks a different\n repository than the one configured, and would report that one as consistent.\n --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 check --format github # annotations from an Actions log\n spec-graph check --format gitlab > gl-code-quality-report.json\n spec-graph diff base.json head.json --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n";
82
82
  /** Parses argv into options. Throws {@link UsageError} on anything malformed. */
83
83
  export declare function parseArgs(argv: readonly string[], cwd: string): CliOptions;
84
84
  /**