@descent-vtt/spec-graph 0.14.1 → 0.15.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 +40 -0
- package/README.md +47 -17
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +57 -15
- package/dist/cli.js.map +1 -1
- package/dist/diff.js.map +1 -1
- package/dist/directives.js.map +1 -1
- package/dist/extract.js.map +1 -1
- package/dist/glob.js.map +1 -1
- package/dist/graph.js.map +1 -1
- package/dist/select.js +4 -3
- package/dist/select.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,46 @@ 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.15.0
|
|
15
|
+
|
|
16
|
+
A list of families that names one no document belongs to, and leaves out
|
|
17
|
+
one the documents do belong to, stops the check, exit 2: `RFCS` written
|
|
18
|
+
for `RFC` took every citation of an RFC out of the check, which reported a
|
|
19
|
+
consistent graph. And `--no-ascii` asks for the Unicode glyphs outright.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **`--no-ascii` asks for the Unicode glyphs outright.** `--ascii` and
|
|
24
|
+
`SPEC_GRAPH_ASCII` ask for ASCII, and nothing asked for the other set: on
|
|
25
|
+
Windows a console that sets neither `WT_SESSION` nor `TERM_PROGRAM` got
|
|
26
|
+
ASCII whatever was to draw the report. `--no-ascii` draws the Unicode
|
|
27
|
+
glyphs on any platform and wins over the variable and over the console,
|
|
28
|
+
as `--ascii` wins the other way. It is a check's option, as `--color` is:
|
|
29
|
+
the other commands draw no glyph and refuse it by name, `--no-ascii
|
|
30
|
+
belongs to check, not to query`, and take `--ascii` as before. The two
|
|
31
|
+
given together are refused, exit `2`
|
|
32
|
+
([ADR-0010](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0010-configuration-belongs-to-the-repository.md)).
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **A list of families that mistypes one of them stops the check.**
|
|
37
|
+
`--family ADR --family RFCS`, in a repository of ADRs and RFCs, left the
|
|
38
|
+
RFCs' own family off the list, so every citation of an RFC was prose:
|
|
39
|
+
`check` printed `the specification graph is consistent` over a citation of
|
|
40
|
+
an RFC nobody wrote, under `--strict` too, where the run without the list
|
|
41
|
+
exits `1`, and all that said so was a line on stderr. Where a list names a
|
|
42
|
+
family no document here belongs to and leaves out a family the documents
|
|
43
|
+
do belong to, the check is exit `2` now, for `--family` and `families`
|
|
44
|
+
alike, and the line names both. A name with no document on a list that
|
|
45
|
+
leaves no such family out is said on stderr and fails nothing, as before:
|
|
46
|
+
a family listed before its first document, or one whose documents the run
|
|
47
|
+
does not read
|
|
48
|
+
([ADR-0010](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0010-configuration-belongs-to-the-repository.md)).
|
|
49
|
+
Upgrading: write the name as the documents carry it, `RFC` for
|
|
50
|
+
`RFC-0007`. Where the family is left out on purpose and the other name is
|
|
51
|
+
one not started yet, say so with `--ignore-family` or `ignoreFamilies`, or
|
|
52
|
+
leave the new name off the list until its first document is written.
|
|
53
|
+
|
|
14
54
|
## 0.14.1
|
|
15
55
|
|
|
16
56
|
`FORCE_COLOR=false` forced colour on, and a Windows console with
|
package/README.md
CHANGED
|
@@ -1006,15 +1006,34 @@ spec-graph: no document here belongs to a family named by --family (RFC); the do
|
|
|
1006
1006
|
```
|
|
1007
1007
|
|
|
1008
1008
|
A document belongs to a family when its identifier is written with it, as
|
|
1009
|
-
`ADR-0007` is with `ADR`.
|
|
1010
|
-
|
|
1011
|
-
|
|
1009
|
+
`ADR-0007` is with `ADR`. One name with no document, beside names that have
|
|
1010
|
+
them, stops the check where the list also leaves out a family the documents do
|
|
1011
|
+
belong to. `--family ADR --family RFCS`, in a repository of ADRs and RFCs,
|
|
1012
|
+
passes over every citation of an RFC, and the check that fails without the
|
|
1013
|
+
list would report a consistent graph. One name is most likely meant for the
|
|
1014
|
+
other, so the line names both, and the exit is `2`:
|
|
1015
|
+
|
|
1016
|
+
```text
|
|
1017
|
+
spec-graph: no document here belongs to RFCS, named by --family; the documents here belong to ADR and RFC, and the list leaves out RFC
|
|
1018
|
+
name each family as its documents do, or leave it off the list until its first document is written
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
Where every family the documents belong to is on the list, a further name with
|
|
1022
|
+
no document is a family the repository means to start, or one whose documents
|
|
1023
|
+
this run does not read. The list then passes over nothing the run would have
|
|
1024
|
+
read without it, so the name is said on stderr and fails nothing:
|
|
1012
1025
|
|
|
1013
1026
|
```text
|
|
1014
1027
|
spec-graph: no document here belongs to RFC, named by --family; the documents here belong to ADR
|
|
1015
1028
|
```
|
|
1016
1029
|
|
|
1017
|
-
A
|
|
1030
|
+
A family on `ignoreFamilies` was taken out on purpose and is not one the list
|
|
1031
|
+
leaves out. A list that is narrower on purpose, `--family ADR` over ADRs and
|
|
1032
|
+
RFCs, has no name without a document and sees no line; where it also names a
|
|
1033
|
+
family not started yet, `ignoreFamilies` is how it says which family it leaves
|
|
1034
|
+
out on purpose.
|
|
1035
|
+
|
|
1036
|
+
A list whose every name has a document sees none of them. `ignoreFamilies`
|
|
1018
1037
|
needs no such check: a name on it passes over citations of that family alone,
|
|
1019
1038
|
so a mistyped one passes over nothing and the run reports more, not less.
|
|
1020
1039
|
|
|
@@ -1076,7 +1095,7 @@ spec-graph diff <before> <after> Compare two JSON graph exports.
|
|
|
1076
1095
|
--max <n> Show at most n findings
|
|
1077
1096
|
--max-warnings <n> Fail when warnings exceed n
|
|
1078
1097
|
--color / --no-color Force colour
|
|
1079
|
-
--ascii
|
|
1098
|
+
--ascii / --no-ascii ASCII glyphs or Unicode ones; SPEC_GRAPH_ASCII=1 asks for ASCII
|
|
1080
1099
|
--verbose Include parse problems
|
|
1081
1100
|
```
|
|
1082
1101
|
|
|
@@ -1101,12 +1120,14 @@ variable in `--family "$FAMILIES"` is the usual way to write one:
|
|
|
1101
1120
|
- an empty or blank `--baseline`, `--record-baseline`, `--ignore`,
|
|
1102
1121
|
`--ignore-ref`, `--history` or pattern;
|
|
1103
1122
|
- a list of families, `--family` or `families`, none of which a document
|
|
1104
|
-
here belongs to
|
|
1123
|
+
here belongs to, or one that names a family with no document and leaves
|
|
1124
|
+
out a family the documents do belong to ([Family rules](#family-rules));
|
|
1105
1125
|
- a second word after `rules`, which takes one rule id, and an empty name
|
|
1106
1126
|
given to `diff`;
|
|
1107
1127
|
- `--format json` asked of `graph`, whose JSON is `--graph-format json`, or
|
|
1108
1128
|
of `rules`, which has one format;
|
|
1109
1129
|
- an option given to a command that does not read it, in the table below;
|
|
1130
|
+
- `--ascii` beside `--no-ascii`, of which one would go unread;
|
|
1110
1131
|
- a value that begins with `--`, which reads as the option after one whose
|
|
1111
1132
|
value was forgotten: `--ignore --strict` would ignore a directory called
|
|
1112
1133
|
`--strict`, and `--record-baseline --verbose` write a file of that name.
|
|
@@ -1129,15 +1150,17 @@ into the file.
|
|
|
1129
1150
|
| `--format` | `check`, `query`, `diff` |
|
|
1130
1151
|
| `--graph-format`, `--documents-only` | `graph` |
|
|
1131
1152
|
| `--explain` | `rules` |
|
|
1132
|
-
| `--ignore-ref`, `--family`, `--ignore-family`, `--baseline`, `--record-baseline`, `--ratchet`, `--rule`, `--strict`, `--max`, `--max-warnings`, `--color` | `check` |
|
|
1153
|
+
| `--ignore-ref`, `--family`, `--ignore-family`, `--baseline`, `--record-baseline`, `--ratchet`, `--rule`, `--strict`, `--max`, `--max-warnings`, `--color`, `--no-ascii` | `check` |
|
|
1133
1154
|
| `--no-config`, `--no-color`, `--ascii`, `--help`, `--version` | every command |
|
|
1134
1155
|
|
|
1135
1156
|
`--no-config`, `--no-color` and `--ascii` are taken by every command because
|
|
1136
1157
|
each is true already of a command with nothing to read it for: only a check
|
|
1137
1158
|
colours its report or draws a glyph, and `diff` reads no configuration. A
|
|
1138
|
-
script can pass them to whatever it runs.
|
|
1139
|
-
|
|
1140
|
-
draws
|
|
1159
|
+
script can pass them to whatever it runs. `--color` and `--no-ascii` are a
|
|
1160
|
+
check's alone: taken by a query, each would say the query drew what only a
|
|
1161
|
+
check draws. The configuration file is not an option: `ignoreReferences` in
|
|
1162
|
+
`.spec-graph.json` stops no `graph`, which draws the same graph with it as
|
|
1163
|
+
without.
|
|
1141
1164
|
|
|
1142
1165
|
**There is no `--watch`.** A full run on this repository takes 60 ms, so the
|
|
1143
1166
|
loop that would justify a resident process is one line of shell, and it belongs
|
|
@@ -1155,11 +1178,18 @@ npx --no-install @descent-vtt/spec-graph --format json | jq -r '.summary'
|
|
|
1155
1178
|
else that is not empty turns it on; a `TERM` of `dumb` turns it off; otherwise
|
|
1156
1179
|
the report is coloured when stdout is a terminal. `CI` is not read.
|
|
1157
1180
|
|
|
1158
|
-
`--ascii` draws the report in ASCII glyphs
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
the
|
|
1181
|
+
`--ascii` draws the report in ASCII glyphs and `--no-ascii` in the Unicode
|
|
1182
|
+
ones, `✖`, `⚠`, `↳` and the like, on any platform and whatever the
|
|
1183
|
+
environment says. Given together they are refused, exit `2`: one of the two
|
|
1184
|
+
would go unread. `--no-ascii` belongs to `check`, as `--color` does; a format
|
|
1185
|
+
of a check that draws no glyph takes it all the same.
|
|
1186
|
+
|
|
1187
|
+
Without either, `SPEC_GRAPH_ASCII` set to anything but `0`, `false` or nothing
|
|
1188
|
+
asks for ASCII; and without that, a Windows console that sets neither
|
|
1189
|
+
`WT_SESSION` nor `TERM_PROGRAM`, or sets them to nothing, gets ASCII and every
|
|
1190
|
+
other terminal the Unicode glyphs. Where those two variables say nothing of
|
|
1191
|
+
the terminal that draws the report, `--no-ascii` asks for the Unicode glyphs
|
|
1192
|
+
outright.
|
|
1163
1193
|
|
|
1164
1194
|
## In CI
|
|
1165
1195
|
|
|
@@ -1420,9 +1450,9 @@ tools share, whose code is copied into `src/vendor/` and checked by hash rather
|
|
|
1420
1450
|
than installed. So the whole package is auditable in an afternoon — and nothing
|
|
1421
1451
|
in the pipeline can take longer than its input.
|
|
1422
1452
|
|
|
1423
|
-
**Verified, not just covered.** 2,
|
|
1453
|
+
**Verified, not just covered.** 2,231 tests; 99.4% statement and 99.8% line
|
|
1424
1454
|
coverage. Coverage says a line ran, so the suite is also held to a mutation
|
|
1425
|
-
score - 96.
|
|
1455
|
+
score - 96.53% over 8,895 mutants, measured by CI's full sweep, and a build
|
|
1426
1456
|
fails under 93 - because a vocabulary entry or a boundary
|
|
1427
1457
|
condition can be weakened by an ordinary-looking refactor without a single test
|
|
1428
1458
|
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.\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\n --ascii Use ASCII glyphs only. SPEC_GRAPH_ASCII asks for the\n same, unless it is empty, 0 or false\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.\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.\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 --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\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 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.\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";
|
|
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
|
/**
|
package/dist/cli.js
CHANGED
|
@@ -73,7 +73,10 @@ OPTIONS
|
|
|
73
73
|
name each time, the prefix its identifiers are
|
|
74
74
|
written with, ADR for ADR-0007. A document here has
|
|
75
75
|
to belong to one of them, or the check stops: a list
|
|
76
|
-
that names none allows nothing.
|
|
76
|
+
that names none allows nothing. It stops too where
|
|
77
|
+
a name has no document and the list leaves out a
|
|
78
|
+
family the documents do belong to, as RFCS for RFC
|
|
79
|
+
does: one is most likely meant for the other.
|
|
77
80
|
--ignore-family <name> Families that are never citations - RFC when the repo
|
|
78
81
|
cites RFC 2119 and keeps its own RFCs. Repeatable,
|
|
79
82
|
one name each time.
|
|
@@ -109,8 +112,10 @@ OPTIONS
|
|
|
109
112
|
--strict Raise every warning to an error. An explicit --rule
|
|
110
113
|
still wins, so --strict --rule x=warn exempts x.
|
|
111
114
|
--color / --no-color Force colour on or off
|
|
112
|
-
--ascii
|
|
113
|
-
|
|
115
|
+
--ascii / --no-ascii ASCII glyphs or Unicode ones. SPEC_GRAPH_ASCII asks
|
|
116
|
+
for ASCII, unless it is empty, 0 or false. Either
|
|
117
|
+
option wins over it, and over what the terminal is
|
|
118
|
+
taken for. The two together are refused
|
|
114
119
|
--verbose Include parse problems, per-file detail, and every
|
|
115
120
|
reference --ignore-ref or --ignore-family silenced
|
|
116
121
|
--explain With rules: add each rule's selector and the ADR
|
|
@@ -129,7 +134,9 @@ OPTIONS
|
|
|
129
134
|
every other option check
|
|
130
135
|
|
|
131
136
|
--no-config, --no-color and --ascii are taken by every command: each is
|
|
132
|
-
true already of a command with nothing to read it for.
|
|
137
|
+
true already of a command with nothing to read it for. --color and
|
|
138
|
+
--no-ascii are not: each would say that a command drew what only a check
|
|
139
|
+
draws.
|
|
133
140
|
|
|
134
141
|
A value that begins with -- reads as an option, so the option before it is
|
|
135
142
|
refused as missing its value. A name that does begin so is written ./--name
|
|
@@ -263,7 +270,11 @@ const SEVERITIES = ['error', 'warn', 'info', 'off'];
|
|
|
263
270
|
* Each is true already of what the commands that do not read it write - none
|
|
264
271
|
* of them colours or draws a glyph, and `diff` reads no configuration - and
|
|
265
272
|
* a script passes them to every command it runs. `--color` is not one of
|
|
266
|
-
* them: it would promise colour that only a check prints.
|
|
273
|
+
* them: it would promise colour that only a check prints. Nor is
|
|
274
|
+
* `--no-ascii`, for the same reason: taken by a query, it would say the
|
|
275
|
+
* Unicode glyphs were drawn where none is. An option is refused where
|
|
276
|
+
* taking it would say what is not so, and taken where it asks a command to
|
|
277
|
+
* leave out what it never does (spec-core's ADR-0005, 2026-10-11).
|
|
267
278
|
*
|
|
268
279
|
* A function and not a table, so that none of it runs when the module loads:
|
|
269
280
|
* a mutant of a table built at import pays for the whole suite (ADR-0007).
|
|
@@ -294,6 +305,7 @@ function readersOf(option) {
|
|
|
294
305
|
case '--max':
|
|
295
306
|
case '--max-warnings':
|
|
296
307
|
case '--color':
|
|
308
|
+
case '--no-ascii':
|
|
297
309
|
return ['check'];
|
|
298
310
|
}
|
|
299
311
|
return undefined;
|
|
@@ -409,6 +421,9 @@ export function parseArgs(argv, cwd) {
|
|
|
409
421
|
case '--ascii':
|
|
410
422
|
ascii = true;
|
|
411
423
|
break;
|
|
424
|
+
case '--no-ascii':
|
|
425
|
+
ascii = false;
|
|
426
|
+
break;
|
|
412
427
|
case '--documents-only':
|
|
413
428
|
documentsOnly = true;
|
|
414
429
|
break;
|
|
@@ -485,8 +500,10 @@ export function parseArgs(argv, cwd) {
|
|
|
485
500
|
const equals = value.indexOf('=');
|
|
486
501
|
if (equals === -1)
|
|
487
502
|
throw new UsageError(`--rule expects <id>=<severity>, got "${value}"`);
|
|
488
|
-
const
|
|
489
|
-
const
|
|
503
|
+
const before = value.slice(0, equals);
|
|
504
|
+
const after = value.slice(equals + 1);
|
|
505
|
+
const id = before;
|
|
506
|
+
const level = after;
|
|
490
507
|
// A project rule is taken on its namespace here and checked for real
|
|
491
508
|
// once the configuration defining it has been read: arguments are
|
|
492
509
|
// parsed before any file is opened, and a flag has to be usable on that
|
|
@@ -541,6 +558,12 @@ export function parseArgs(argv, cwd) {
|
|
|
541
558
|
}
|
|
542
559
|
}
|
|
543
560
|
}
|
|
561
|
+
// The later of the two would decide and the earlier go unread: a script
|
|
562
|
+
// that adds `--no-ascii` to a line already holding `--ascii` would be told
|
|
563
|
+
// nothing of which it got.
|
|
564
|
+
if (given.has('--ascii') && given.has('--no-ascii') && !help && !version) {
|
|
565
|
+
throw new UsageError('--ascii is given beside --no-ascii, and the glyphs are drawn one way: give one of the two');
|
|
566
|
+
}
|
|
544
567
|
// The second word given to `rules` was dropped, so `rules a b` listed `a`
|
|
545
568
|
// and said nothing of `b`.
|
|
546
569
|
if (command === 'rules' && patterns.length > 1 && !help && !version) {
|
|
@@ -787,8 +810,10 @@ async function run(io, err) {
|
|
|
787
810
|
const patterns = typed.patterns.length > 0 ? typed.patterns : (file.patterns ?? DEFAULT_PATTERNS);
|
|
788
811
|
const severityOverrides = { ...(file.severities ?? {}), ...options.severities };
|
|
789
812
|
const { severities, escalated } = resolveStrict(severityOverrides, options.strict || (file.strict ?? false), projectRules);
|
|
790
|
-
// The list the check is held against, once it has read the documents
|
|
813
|
+
// The list the check is held against, once it has read the documents, and
|
|
814
|
+
// the families taken out on purpose, which that list is not held to.
|
|
791
815
|
const families = [...(file.families ?? []), ...options.families];
|
|
816
|
+
const ignoreFamilies = [...(file.ignoreFamilies ?? []), ...options.ignoreFamilies];
|
|
792
817
|
const analyseOptions = {
|
|
793
818
|
root,
|
|
794
819
|
patterns,
|
|
@@ -800,7 +825,7 @@ async function run(io, err) {
|
|
|
800
825
|
ignore: [...(file.ignore ?? []), ...typed.ignore],
|
|
801
826
|
ignoreReferences: [...(file.ignoreReferences ?? []), ...options.ignoreReferences],
|
|
802
827
|
families,
|
|
803
|
-
ignoreFamilies
|
|
828
|
+
ignoreFamilies,
|
|
804
829
|
historyPatterns: [...(file.historyPatterns ?? []), ...typed.history],
|
|
805
830
|
severities,
|
|
806
831
|
projectRules,
|
|
@@ -871,10 +896,19 @@ async function run(io, err) {
|
|
|
871
896
|
// has: every citation is then prose, and the run that failed on a
|
|
872
897
|
// dangling one reported a consistent graph - for `--family ADRS` as it
|
|
873
898
|
// did for `--family ""`. Nothing was measured, which is not clean
|
|
874
|
-
// (spec-core's ADR-0005), so the run stops before it says anything.
|
|
875
|
-
//
|
|
876
|
-
//
|
|
877
|
-
//
|
|
899
|
+
// (spec-core's ADR-0005), so the run stops before it says anything.
|
|
900
|
+
//
|
|
901
|
+
// One such name beside names that have documents costs the run a
|
|
902
|
+
// finding only where the list also leaves out a family the documents
|
|
903
|
+
// do belong to: `ADR` and `RFCS` over ADRs and RFCs pass over every
|
|
904
|
+
// citation of an RFC, and the run that fails without the list reports
|
|
905
|
+
// a consistent graph. One name is most likely meant for the other, and
|
|
906
|
+
// the run stops there too. Where every family the corpus has is on the
|
|
907
|
+
// list, the name is a family the repository means to start, or one
|
|
908
|
+
// whose documents this run does not read; the list passes over nothing
|
|
909
|
+
// the run would have read without it, so it is said, and nothing
|
|
910
|
+
// fails. A family on `--ignore-family` was taken out on purpose, and
|
|
911
|
+
// is not one the list leaves out (ADR-0010, 2026-10-12).
|
|
878
912
|
const allowed = [...new Set(families.map((family) => family.trim().toUpperCase()))];
|
|
879
913
|
const absent = allowed.filter((family) => !result.corpus.families.includes(family));
|
|
880
914
|
if (absent.length > 0) {
|
|
@@ -884,11 +918,19 @@ async function run(io, err) {
|
|
|
884
918
|
if (file.families !== undefined && file.families.length > 0)
|
|
885
919
|
lists.push(`"families" in ${loaded.source}`);
|
|
886
920
|
const here = `the documents here belong to ${result.corpus.families.length > 0 ? listed(result.corpus.families, 'and') : 'no family'}`;
|
|
921
|
+
const named = lists.join(' and ');
|
|
887
922
|
if (absent.length === allowed.length) {
|
|
888
|
-
err(`spec-graph: no document here belongs to a family named by ${
|
|
923
|
+
err(`spec-graph: no document here belongs to a family named by ${named} (${allowed.join(', ')}); ${here}\n name a family the documents belong to, or leave the list out\n`);
|
|
924
|
+
return EXIT_ERROR;
|
|
925
|
+
}
|
|
926
|
+
const ignored = ignoreFamilies.map((family) => family.trim().toUpperCase());
|
|
927
|
+
const left = result.corpus.families.filter((family) => !allowed.includes(family) && !ignored.includes(family));
|
|
928
|
+
const said = `spec-graph: no document here belongs to ${listed(absent, 'or')}, named by ${named}; ${here}`;
|
|
929
|
+
if (left.length > 0) {
|
|
930
|
+
err(`${said}, and the list leaves out ${listed(left, 'and')}\n name each family as its documents do, or leave it off the list until its first document is written\n`);
|
|
889
931
|
return EXIT_ERROR;
|
|
890
932
|
}
|
|
891
|
-
err(
|
|
933
|
+
err(`${said}\n`);
|
|
892
934
|
}
|
|
893
935
|
// Recording is not checking. It writes down what is wrong today so that
|
|
894
936
|
// tomorrow can be compared against it, and says nothing about whether
|