@descent-vtt/spec-graph 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +483 -38
- package/README.md +134 -49
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +38 -19
- package/dist/cli.js.map +1 -1
- package/dist/directives.d.ts +9 -2
- package/dist/directives.js +15 -4
- package/dist/directives.js.map +1 -1
- package/dist/extract.d.ts +1 -1
- package/dist/extract.js +50 -16
- package/dist/extract.js.map +1 -1
- package/dist/glob.d.ts +44 -44
- package/dist/glob.js +152 -116
- package/dist/glob.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.js +10 -2
- package/dist/lifecycle.js.map +1 -1
- package/dist/markdown.d.ts +53 -161
- package/dist/markdown.js +42 -864
- package/dist/markdown.js.map +1 -1
- package/dist/report.d.ts +23 -0
- package/dist/report.js +80 -0
- package/dist/report.js.map +1 -1
- package/dist/rules.js +48 -14
- package/dist/rules.js.map +1 -1
- package/dist/sections.d.ts +1 -1
- package/dist/sections.js +7 -3
- package/dist/sections.js.map +1 -1
- package/dist/select.d.ts +2 -2
- package/dist/select.js +3 -3
- package/dist/select.js.map +1 -1
- package/dist/source.d.ts +6 -19
- package/dist/source.js +4 -70
- package/dist/source.js.map +1 -1
- package/dist/types.d.ts +23 -3
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vendor/spec-core/markdown/frontmatter.d.ts +103 -0
- package/dist/vendor/spec-core/markdown/frontmatter.js +459 -0
- package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -0
- package/dist/vendor/spec-core/markdown/headings.d.ts +45 -0
- package/dist/vendor/spec-core/markdown/headings.js +199 -0
- package/dist/vendor/spec-core/markdown/headings.js.map +1 -0
- package/dist/vendor/spec-core/markdown/index.d.ts +5 -0
- package/dist/vendor/spec-core/markdown/index.js +4 -0
- package/dist/vendor/spec-core/markdown/index.js.map +1 -0
- package/dist/vendor/spec-core/markdown/layout.d.ts +39 -0
- package/dist/vendor/spec-core/markdown/layout.js +28 -0
- package/dist/vendor/spec-core/markdown/layout.js.map +1 -0
- package/dist/vendor/spec-core/markdown/links.d.ts +25 -0
- package/dist/vendor/spec-core/markdown/links.js +394 -0
- package/dist/vendor/spec-core/markdown/links.js.map +1 -0
- package/dist/vendor/spec-core/markdown/lists.d.ts +22 -0
- package/dist/vendor/spec-core/markdown/lists.js +127 -0
- package/dist/vendor/spec-core/markdown/lists.js.map +1 -0
- package/dist/vendor/spec-core/markdown/scan.d.ts +36 -0
- package/dist/vendor/spec-core/markdown/scan.js +463 -0
- package/dist/vendor/spec-core/markdown/scan.js.map +1 -0
- package/dist/vendor/spec-core/markdown/syntax.d.ts +75 -0
- package/dist/vendor/spec-core/markdown/syntax.js +181 -0
- package/dist/vendor/spec-core/markdown/syntax.js.map +1 -0
- package/dist/vendor/spec-core/markdown/tables.d.ts +18 -0
- package/dist/vendor/spec-core/markdown/tables.js +86 -0
- package/dist/vendor/spec-core/markdown/tables.js.map +1 -0
- package/dist/vendor/spec-core/markdown/types.d.ts +290 -0
- package/dist/vendor/spec-core/markdown/types.js +9 -0
- package/dist/vendor/spec-core/markdown/types.js.map +1 -0
- package/dist/vendor/spec-core/path/index.d.ts +1 -0
- package/dist/vendor/spec-core/path/index.js +2 -0
- package/dist/vendor/spec-core/path/index.js.map +1 -0
- package/dist/vendor/spec-core/path/posix.d.ts +83 -0
- package/dist/vendor/spec-core/path/posix.js +150 -0
- package/dist/vendor/spec-core/path/posix.js.map +1 -0
- package/dist/vendor/spec-core/pattern/automaton.d.ts +129 -0
- package/dist/vendor/spec-core/pattern/automaton.js +340 -0
- package/dist/vendor/spec-core/pattern/automaton.js.map +1 -0
- package/dist/vendor/spec-core/pattern/charset.d.ts +58 -0
- package/dist/vendor/spec-core/pattern/charset.js +116 -0
- package/dist/vendor/spec-core/pattern/charset.js.map +1 -0
- package/dist/vendor/spec-core/pattern/glob.d.ts +121 -0
- package/dist/vendor/spec-core/pattern/glob.js +481 -0
- package/dist/vendor/spec-core/pattern/glob.js.map +1 -0
- package/dist/vendor/spec-core/pattern/index.d.ts +6 -0
- package/dist/vendor/spec-core/pattern/index.js +4 -0
- package/dist/vendor/spec-core/pattern/index.js.map +1 -0
- package/dist/{regex.d.ts → vendor/spec-core/pattern/regex.d.ts} +20 -29
- package/dist/{regex.js → vendor/spec-core/pattern/regex.js} +40 -49
- package/dist/vendor/spec-core/pattern/regex.js.map +1 -0
- package/dist/vendor/spec-core/text/index.d.ts +2 -0
- package/dist/vendor/spec-core/text/index.js +2 -0
- package/dist/vendor/spec-core/text/index.js.map +1 -0
- package/dist/vendor/spec-core/text/lines.d.ts +72 -0
- package/dist/vendor/spec-core/text/lines.js +156 -0
- package/dist/vendor/spec-core/text/lines.js.map +1 -0
- package/dist/yaml.d.ts +44 -12
- package/dist/yaml.js +115 -206
- package/dist/yaml.js.map +1 -1
- package/package.json +4 -3
- package/src/vendor/spec-core/LICENSE +21 -0
- package/src/vendor/spec-core/README.md +8 -0
- package/dist/regex.js.map +0 -1
package/README.md
CHANGED
|
@@ -123,12 +123,28 @@ onto four phases, chosen so one question can be answered from the phase alone:
|
|
|
123
123
|
| `draft` | `proposed`, `provisional`, `wip`, `in-review`, `experimental`, … | yes |
|
|
124
124
|
| `active` | `accepted`, `implementable`, `implemented`, `merged`, `adopted`, … | yes |
|
|
125
125
|
| `frozen` | `final`, `ratified`, `locked`, `published`, … | **no** |
|
|
126
|
-
| `retired` | `superseded`, `deprecated`, `rejected`, `withdrawn`,
|
|
126
|
+
| `retired` | `superseded`, `deprecated`, `rejected`, `withdrawn`, … | **no** |
|
|
127
127
|
|
|
128
128
|
The status is found wherever your team writes it — front matter (`status:`,
|
|
129
129
|
`state:`, `stage:`), a `## Status` section, `**Accepted** (2026-03-01) ✅`, or a
|
|
130
130
|
`docs/adr/archive/` directory. `Accepted, later superseded by ADR-0009` is
|
|
131
|
-
retired: retirement is terminal, so a retirement word anywhere wins.
|
|
131
|
+
retired: retirement is terminal, so a retirement word anywhere wins. Front
|
|
132
|
+
matter is read as YAML reads it, and a value YAML would not read -
|
|
133
|
+
`status: Superseded by ADR-9: see notes`, where a plain value cannot hold `: ` -
|
|
134
|
+
is left out rather than guessed at. Every run says how many statuses, ids and
|
|
135
|
+
relations it left out that way, and `--verbose` says where and why: quote the
|
|
136
|
+
value and it is read.
|
|
137
|
+
|
|
138
|
+
`archived` is none of the four. It is what spec-brief writes on a round of work
|
|
139
|
+
it has closed, and every spec-* tool reads it as a **record**: depending on it
|
|
140
|
+
is normal, its unticked boxes are not anyone's work, its links are still
|
|
141
|
+
checked, and new work handed to it is still a ghost handover. See
|
|
142
|
+
[Journals, changelogs and minutes](#journals-changelogs-and-minutes). Unlike a
|
|
143
|
+
journal, it still answers for what it claims: a supersession it declares, a
|
|
144
|
+
cycle it closes and a link to itself are reported as they would be anywhere.
|
|
145
|
+
To retire a decision, say `superseded` or `deprecated` -
|
|
146
|
+
`archived, superseded by ADR-0009` is retired - and a document under an
|
|
147
|
+
`archive/` directory that says nothing about itself is still retired.
|
|
132
148
|
|
|
133
149
|
An unrecognised status becomes `unknown`, and rules treat it permissively. It
|
|
134
150
|
never guesses.
|
|
@@ -242,7 +258,7 @@ Both forms need **two** signals, and that is deliberate: `## Q3 2026 Roadmap`
|
|
|
242
258
|
parses as family `Q`, number 3, and `## v1.2.0` in a changelog parses as family
|
|
243
259
|
`v`, number 1. Neither declares a status, so neither is a specification. A table
|
|
244
260
|
of identifiers and prose is a citation list, not a register. See
|
|
245
|
-
[ADR-0009](docs/adr/0009-a-specification-is-a-region.md).
|
|
261
|
+
[ADR-0009](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0009-a-specification-is-a-region.md).
|
|
246
262
|
|
|
247
263
|
### When `[[...]]` tags a concept
|
|
248
264
|
|
|
@@ -272,7 +288,7 @@ The tool names that flag in the hint, so it costs one run to find:
|
|
|
272
288
|
`--ignore-ref` suppresses *findings*, never edges. A reference that resolves is
|
|
273
289
|
still a relation in the graph no matter what you exclude — not even
|
|
274
290
|
`--ignore-ref "*"` can delete one. See
|
|
275
|
-
[ADR-0008](docs/adr/0008-wiki-links-carry-no-path.md).
|
|
291
|
+
[ADR-0008](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0008-wiki-links-carry-no-path.md).
|
|
276
292
|
|
|
277
293
|
#### A gap that is deliberate
|
|
278
294
|
|
|
@@ -399,16 +415,17 @@ against a fifty-four-character title takes `RegExp` 103 seconds and this 13
|
|
|
399
415
|
microseconds. It reads the usual syntax minus backreferences and lookaround,
|
|
400
416
|
which are not regular; both are refused when the selector is read, with the
|
|
401
417
|
character pointed at. Globs - the patterns to check, `--ignore`, `--ignore-ref`,
|
|
402
|
-
`--history` - are matched by
|
|
403
|
-
in it is
|
|
404
|
-
|
|
405
|
-
|
|
418
|
+
`--history` - are matched by an automaton too, because a glob with three stars
|
|
419
|
+
in it is enough to keep `RegExp` busy for two minutes over a long reference
|
|
420
|
+
target. See [ADR-0017](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0017-a-predicate-must-finish.md). Both automata
|
|
421
|
+
are spec-core's, the library the spec-* tools share, copied into this package
|
|
422
|
+
rather than installed.
|
|
406
423
|
|
|
407
424
|
On a register, a region answers its file's front matter for every descriptive
|
|
408
425
|
key — `fm.owner` on a decision inside a register is the register's owner — while
|
|
409
426
|
the keys a region owns itself, its identifier and status and title, and any key
|
|
410
427
|
that declares a relation, stay on the file. See
|
|
411
|
-
[ADR-0009](docs/adr/0009-a-specification-is-a-region.md).
|
|
428
|
+
[ADR-0009](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0009-a-specification-is-a-region.md).
|
|
412
429
|
|
|
413
430
|
## Your own rules
|
|
414
431
|
|
|
@@ -472,7 +489,7 @@ spec-graph: .spec-graph.json: rules.owner: "{1.phse}" asks for an attribute
|
|
|
472
489
|
nothing has
|
|
473
490
|
```
|
|
474
491
|
|
|
475
|
-
See [ADR-0016](docs/adr/0016-a-query-needs-a-sentence.md).
|
|
492
|
+
See [ADR-0016](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0016-a-query-needs-a-sentence.md).
|
|
476
493
|
|
|
477
494
|
## Directives
|
|
478
495
|
|
|
@@ -508,7 +525,10 @@ had:
|
|
|
508
525
|
{ "historyPatterns": ["**/JOURNAL_*.md", "archive/**"] }
|
|
509
526
|
```
|
|
510
527
|
|
|
511
|
-
or, for one file, `<!-- @spec-history -->` at the top of it.
|
|
528
|
+
or, for one file, `<!-- @spec-history -->` at the top of it. A brief whose
|
|
529
|
+
status is `archived` is a record already, with nothing declared, though not a
|
|
530
|
+
log: the supersessions, cycles and self-references it declares are still
|
|
531
|
+
checked.
|
|
512
532
|
|
|
513
533
|
| | |
|
|
514
534
|
| --- | --- |
|
|
@@ -520,7 +540,7 @@ or, for one file, `<!-- @spec-history -->` at the top of it.
|
|
|
520
540
|
That last row is the point. Excluding the file with `--ignore` would have
|
|
521
541
|
silenced the whole lot, including the links, and a journal full of 404s is
|
|
522
542
|
exactly what this tool is for. See
|
|
523
|
-
[ADR-0011](docs/adr/0011-a-record-is-not-a-specification.md).
|
|
543
|
+
[ADR-0011](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0011-a-record-is-not-a-specification.md).
|
|
524
544
|
|
|
525
545
|
## Adopting this on a repository that predates it
|
|
526
546
|
|
|
@@ -576,7 +596,7 @@ nothing left to be stale about, so a typo in it looks exactly like a regression
|
|
|
576
596
|
nobody introduced: it exits `2` instead. A `"baseline"` in the configuration is
|
|
577
597
|
the other case and stays optional, because a repository declares the path before
|
|
578
598
|
the first run records the file — `--verbose` says when it read nothing. Either
|
|
579
|
-
may be absolute. See [ADR-0012](docs/adr/0012-a-baseline-is-a-ratchet.md).
|
|
599
|
+
may be absolute. See [ADR-0012](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0012-a-baseline-is-a-ratchet.md).
|
|
580
600
|
|
|
581
601
|
## In CI
|
|
582
602
|
|
|
@@ -599,9 +619,34 @@ without drifting, because the `partialFingerprints` spec-graph writes are the
|
|
|
599
619
|
same identity a baseline is keyed on — the rule, the specification, and the
|
|
600
620
|
citation, with no line number in it (ADR-0012).
|
|
601
621
|
|
|
602
|
-
`--format
|
|
603
|
-
|
|
604
|
-
|
|
622
|
+
On GitLab, `--format gitlab` writes the Code Quality report a merge request
|
|
623
|
+
reads, and the merge request shows which findings it introduced and which it
|
|
624
|
+
resolved:
|
|
625
|
+
|
|
626
|
+
```yaml
|
|
627
|
+
spec-graph:
|
|
628
|
+
script:
|
|
629
|
+
- npx spec-graph --format gitlab > gl-code-quality-report.json
|
|
630
|
+
artifacts:
|
|
631
|
+
when: always
|
|
632
|
+
reports:
|
|
633
|
+
codequality: gl-code-quality-report.json
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Each finding is an issue with its rule as `check_name`, its message and hint as
|
|
637
|
+
`description`, and its file and line as `location`. Severities map as `error`
|
|
638
|
+
to `critical` - or `major` when only `--strict` made it an error - `warn` to
|
|
639
|
+
`minor` and `info` to `info`. The `fingerprint` is a SHA-256 of the rule, the
|
|
640
|
+
specification and what within it - a citation's target, or the document at the
|
|
641
|
+
other end - which is what a baseline counts by. There is no line in it, so a
|
|
642
|
+
finding a paragraph moved is not reported as new, and no message, so ticking
|
|
643
|
+
one of three open boxes does not make an `orphaned-obligation` new either; the
|
|
644
|
+
second of two findings identical in all three is told apart by its order. `when: always` keeps the report when the job fails, which
|
|
645
|
+
it does, with exit `1`, whenever there is an error to report.
|
|
646
|
+
|
|
647
|
+
`--format json` is the machine-readable output to parse if you are building
|
|
648
|
+
something of your own: it carries the baseline note, the summary and the
|
|
649
|
+
per-file detail that SARIF and Code Quality have nowhere to put.
|
|
605
650
|
|
|
606
651
|
**There is no `--watch`.** A full run on this repository takes 60 ms, so the
|
|
607
652
|
loop that would justify a resident process is one line of shell, and it belongs
|
|
@@ -642,7 +687,7 @@ the root**. So a run from `packages/auth` reports what a run from the top
|
|
|
642
687
|
reports, byte for byte, because every path here is relative to the root. The
|
|
643
688
|
nearest file wins, `--root` names the root yourself and turns discovery off, and
|
|
644
689
|
a path typed on the command line stays relative to where you typed it. See
|
|
645
|
-
[ADR-0018](docs/adr/0018-the-configuration-file-is-the-root.md).
|
|
690
|
+
[ADR-0018](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0018-the-configuration-file-is-the-root.md).
|
|
646
691
|
|
|
647
692
|
A flag always wins over the file, and list flags **add** to it rather than
|
|
648
693
|
replacing it — a `--ignore-ref` on the command line is one more exclusion, not a
|
|
@@ -661,6 +706,39 @@ all — a silently ignored `ignoreReference` is a configuration that looks appli
|
|
|
661
706
|
and is not. `--no-config` checks on defaults instead, and the exit code then
|
|
662
707
|
says which run it was.
|
|
663
708
|
|
|
709
|
+
### Patterns
|
|
710
|
+
|
|
711
|
+
The patterns to check, `--ignore`, `--history` and `historyPatterns` are the
|
|
712
|
+
glob dialect every spec-* tool reads, so a scope means the same to spec-graph
|
|
713
|
+
as to the tool that wrote it. They are matched against the whole
|
|
714
|
+
repository-relative path, **case-sensitively on every host** — a baseline
|
|
715
|
+
recorded on a Windows checkout holds in Linux CI:
|
|
716
|
+
|
|
717
|
+
| pattern | matches |
|
|
718
|
+
| --- | --- |
|
|
719
|
+
| `docs/**/*.md` | Markdown anywhere under `docs`; `**` is whole directories, none or more |
|
|
720
|
+
| `docs/**.md` | refused: `**` is whole directories or nothing, so write `docs/**/*.md` or `docs/*.md` |
|
|
721
|
+
| `docs` | a file called `docs`, or everything under the directory |
|
|
722
|
+
| `docs/` | everything under `docs`, and not `docs` itself |
|
|
723
|
+
| `{docs,specs}` | two literals, so both directories and what they hold |
|
|
724
|
+
| `adr/[0-9]*.md`, `adr/[!0-9]*.md` | a class, and a negated one; `[^0-9]` negates too |
|
|
725
|
+
| `!docs/drafts/**` | takes back what an earlier pattern matched; the last to match wins |
|
|
726
|
+
| `docs\adr\*.md` | the same as `docs/adr/*.md`: a `\` is a separator |
|
|
727
|
+
|
|
728
|
+
A class never matches a `/`, and a pattern that cannot mean a path under the
|
|
729
|
+
root — an unclosed `[` or `{`, `..`, a lone `.`, an empty one — stops the run
|
|
730
|
+
with exit `2` and names the pattern, wherever it was written. So does an
|
|
731
|
+
extended glob, `+(a|b)`: write `{a,b}`. A parenthesis with no `|` in its group
|
|
732
|
+
is itself, so `C++(notes).md` names that file. A `..` typed below the root,
|
|
733
|
+
`spec-graph "../*.md"` from `docs/deep`, is resolved against where you typed
|
|
734
|
+
it. A bare `--ignore` name, `--ignore drafts`, still prunes that directory at
|
|
735
|
+
any depth.
|
|
736
|
+
|
|
737
|
+
`--ignore-ref` is matched against reference targets rather than paths: a bare
|
|
738
|
+
pattern names one target exactly, case is ignored on every host, a `\` escapes
|
|
739
|
+
the next character, and `.` and `..` are the text of the link. See
|
|
740
|
+
[ADR-0022](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0022-globs-are-the-family-path-dialect.md).
|
|
741
|
+
|
|
664
742
|
### Family rules
|
|
665
743
|
|
|
666
744
|
Every specification cites RFC 2119. In a repository that also keeps its own
|
|
@@ -677,7 +755,7 @@ has — and turns every other noun-number construct back into prose.
|
|
|
677
755
|
Both are consulted only after resolution has already failed, so `RFC 0001` still
|
|
678
756
|
resolves to your local RFC-0001 with `RFC` on the ignore list. No configuration
|
|
679
757
|
can delete an edge. See
|
|
680
|
-
[ADR-0010](docs/adr/0010-configuration-belongs-to-the-repository.md).
|
|
758
|
+
[ADR-0010](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0010-configuration-belongs-to-the-repository.md).
|
|
681
759
|
|
|
682
760
|
`--verbose` lists everything they silenced, so one over-broad glob cannot quietly
|
|
683
761
|
turn the check off and look like a clean repository:
|
|
@@ -691,7 +769,7 @@ The JSON report carries the same rows under `suppressed`, without needing the
|
|
|
691
769
|
flag. What is deliberately *not* listed is a bare identifier spec-graph read as
|
|
692
770
|
prose on its own — `T-1000` was nobody's decision, and listing it would bury the
|
|
693
771
|
entries that were. See
|
|
694
|
-
[ADR-0008](docs/adr/0008-wiki-links-carry-no-path.md).
|
|
772
|
+
[ADR-0008](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0008-wiki-links-carry-no-path.md).
|
|
695
773
|
|
|
696
774
|
Prose like `Phase 1`, `R69`, `Q-120`, `Table 2` and `Step 4` has never needed
|
|
697
775
|
this: an identifier found in prose is only read as a citation when its family
|
|
@@ -720,7 +798,8 @@ spec-graph diff <before> <after> Compare two JSON graph exports.
|
|
|
720
798
|
--record-baseline <f> Write today's findings as accepted debt, exit 0
|
|
721
799
|
--ratchet Also fail when a baseline entry no longer occurs
|
|
722
800
|
--no-config Ignore .spec-graph.json and the package.json key
|
|
723
|
-
--format <fmt> human | json | sarif (check) |
|
|
801
|
+
--format <fmt> human | json | sarif (check) | gitlab (check)
|
|
802
|
+
| markdown (check, diff)
|
|
724
803
|
--graph-format <fmt> dot | mermaid | json
|
|
725
804
|
--documents-only Hide items; their relations lift onto their documents
|
|
726
805
|
--rule <id>=<severity> error | warn | info | off (repeatable)
|
|
@@ -801,7 +880,7 @@ document, section and title when nothing else shares them. Anything unpaired
|
|
|
801
880
|
is reported as having appeared or disappeared, never as resolved or reopened.
|
|
802
881
|
Findings are left to `--baseline`, which already reports what is new. The exit
|
|
803
882
|
code is `0` whether anything changed or not: a diff describes, and `check`
|
|
804
|
-
gates. See [ADR-0020](docs/adr/0020-a-diff-names-what-it-can-tell-apart.md).
|
|
883
|
+
gates. See [ADR-0020](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0020-a-diff-names-what-it-can-tell-apart.md).
|
|
805
884
|
|
|
806
885
|
## Visualising
|
|
807
886
|
|
|
@@ -838,41 +917,47 @@ exported. Reporters, editor extensions and custom rules are all first-class.
|
|
|
838
917
|
|
|
839
918
|
## Design
|
|
840
919
|
|
|
841
|
-
Twenty ADRs, which `spec-graph` validates on every CI run:
|
|
842
|
-
|
|
843
|
-
- [ADR-0001 — A hand-written Markdown scanner](docs/adr/0001-hand-written-markdown-scanner.md)
|
|
844
|
-
- [ADR-0002 — A four-phase lifecycle lattice](docs/adr/0002-lifecycle-lattice.md)
|
|
845
|
-
- [ADR-0003 — Item state is resolved from competing signals](docs/adr/0003-item-state-signals.md)
|
|
846
|
-
- [ADR-0004 — Reference resolution is deliberately asymmetric](docs/adr/0004-reference-resolution.md)
|
|
847
|
-
- [ADR-0005 — The rules and the query language share one engine](docs/adr/0005-rules-are-queries.md)
|
|
848
|
-
- [ADR-0006 — False positives cost more than misses](docs/adr/0006-false-positives-cost-more.md)
|
|
849
|
-
- [ADR-0007 — Mutation testing, and what the score actually means](docs/adr/0007-mutation-testing.md)
|
|
850
|
-
- [ADR-0008 — A wiki link carries a name, not a path](docs/adr/0008-wiki-links-carry-no-path.md)
|
|
851
|
-
- [ADR-0009 — A specification is a region of a file, not a file](docs/adr/0009-a-specification-is-a-region.md)
|
|
852
|
-
- [ADR-0010 — Configuration belongs to the repository](docs/adr/0010-configuration-belongs-to-the-repository.md)
|
|
853
|
-
- [ADR-0011 — A historical record is not a specification](docs/adr/0011-a-record-is-not-a-specification.md)
|
|
854
|
-
- [ADR-0012 — A baseline is a ratchet, keyed on identity](docs/adr/0012-a-baseline-is-a-ratchet.md)
|
|
855
|
-
- [ADR-0013 — When the scanner is unsure, it hands back prose](docs/adr/0013-the-scanner-hands-back-prose.md)
|
|
856
|
-
- [ADR-0014 — A relation is spelled in both directions](docs/adr/0014-a-relation-is-spelled-both-ways.md)
|
|
857
|
-
- [ADR-0015 — Feedback goes where the tools already look](docs/adr/0015-feedback-goes-where-the-tools-already-look.md)
|
|
858
|
-
- [ADR-0016 — A query needs a sentence before it is a rule](docs/adr/0016-a-query-needs-a-sentence.md)
|
|
859
|
-
- [ADR-0017 — A predicate must finish](docs/adr/0017-a-predicate-must-finish.md)
|
|
860
|
-
- [ADR-0018 — The configuration file is the root](docs/adr/0018-the-configuration-file-is-the-root.md)
|
|
861
|
-
- [ADR-0019 — The sweep runs in shards](docs/adr/0019-the-sweep-runs-in-shards.md)
|
|
862
|
-
- [ADR-0020 — A diff names only what it can tell apart](docs/adr/0020-a-diff-names-what-it-can-tell-apart.md)
|
|
920
|
+
Twenty-three ADRs, which `spec-graph` validates on every CI run:
|
|
921
|
+
|
|
922
|
+
- [ADR-0001 — A hand-written Markdown scanner](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0001-hand-written-markdown-scanner.md)
|
|
923
|
+
- [ADR-0002 — A four-phase lifecycle lattice](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0002-lifecycle-lattice.md)
|
|
924
|
+
- [ADR-0003 — Item state is resolved from competing signals](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0003-item-state-signals.md)
|
|
925
|
+
- [ADR-0004 — Reference resolution is deliberately asymmetric](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0004-reference-resolution.md)
|
|
926
|
+
- [ADR-0005 — The rules and the query language share one engine](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0005-rules-are-queries.md)
|
|
927
|
+
- [ADR-0006 — False positives cost more than misses](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0006-false-positives-cost-more.md)
|
|
928
|
+
- [ADR-0007 — Mutation testing, and what the score actually means](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0007-mutation-testing.md)
|
|
929
|
+
- [ADR-0008 — A wiki link carries a name, not a path](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0008-wiki-links-carry-no-path.md)
|
|
930
|
+
- [ADR-0009 — A specification is a region of a file, not a file](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0009-a-specification-is-a-region.md)
|
|
931
|
+
- [ADR-0010 — Configuration belongs to the repository](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0010-configuration-belongs-to-the-repository.md)
|
|
932
|
+
- [ADR-0011 — A historical record is not a specification](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0011-a-record-is-not-a-specification.md)
|
|
933
|
+
- [ADR-0012 — A baseline is a ratchet, keyed on identity](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0012-a-baseline-is-a-ratchet.md)
|
|
934
|
+
- [ADR-0013 — When the scanner is unsure, it hands back prose](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0013-the-scanner-hands-back-prose.md)
|
|
935
|
+
- [ADR-0014 — A relation is spelled in both directions](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0014-a-relation-is-spelled-both-ways.md)
|
|
936
|
+
- [ADR-0015 — Feedback goes where the tools already look](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0015-feedback-goes-where-the-tools-already-look.md)
|
|
937
|
+
- [ADR-0016 — A query needs a sentence before it is a rule](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0016-a-query-needs-a-sentence.md)
|
|
938
|
+
- [ADR-0017 — A predicate must finish](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0017-a-predicate-must-finish.md)
|
|
939
|
+
- [ADR-0018 — The configuration file is the root](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0018-the-configuration-file-is-the-root.md)
|
|
940
|
+
- [ADR-0019 — The sweep runs in shards](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0019-the-sweep-runs-in-shards.md)
|
|
941
|
+
- [ADR-0020 — A diff names only what it can tell apart](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0020-a-diff-names-what-it-can-tell-apart.md)
|
|
942
|
+
- [ADR-0021 — Releases are published by CI, with provenance](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0021-releases-are-published-by-ci.md)
|
|
943
|
+
- [ADR-0022 — Globs are the family's path dialect](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0022-globs-are-the-family-path-dialect.md)
|
|
944
|
+
- [ADR-0023 — The scanner is the family's](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0023-the-scanner-is-the-familys.md)
|
|
863
945
|
|
|
864
946
|
**Zero runtime dependencies.** Node 22+, native ESM, TypeScript strict with
|
|
865
947
|
`noUncheckedIndexedAccess` and `exactOptionalPropertyTypes`. The Markdown
|
|
866
948
|
scanner, the front-matter reader, the glob matcher, the query parser and the
|
|
867
|
-
regular-expression matcher behind `~=` are all written here
|
|
868
|
-
|
|
869
|
-
|
|
949
|
+
regular-expression matcher behind `~=` are all written here or in
|
|
950
|
+
[spec-core](https://github.com/DescentVTT/spec-core), the library the spec-*
|
|
951
|
+
tools share, whose code is copied into `src/vendor/` and checked by hash rather
|
|
952
|
+
than installed. So the whole package is auditable in an afternoon — and nothing
|
|
953
|
+
in the pipeline can take longer than its input.
|
|
870
954
|
|
|
871
|
-
**Verified, not just covered.**
|
|
955
|
+
**Verified, not just covered.** 1,053 tests; 96.8% statement and 98.2% line
|
|
872
956
|
coverage. Coverage says a line ran, so the suite is also held to a mutation
|
|
873
|
-
score -
|
|
957
|
+
score - 76.73% over 8,400 mutants at 0.9.0, measured incrementally in CI -
|
|
958
|
+
because a vocabulary entry or a boundary
|
|
874
959
|
condition can be weakened by an ordinary-looking refactor without a single test
|
|
875
|
-
going red. [ADR-0007](docs/adr/0007-mutation-testing.md) is straight about what
|
|
960
|
+
going red. [ADR-0007](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0007-mutation-testing.md) is straight about what
|
|
876
961
|
that number is and is not.
|
|
877
962
|
|
|
878
963
|
**Fast enough to run on save.** A synthetic corpus of 2,000 documents — 8.9 MB,
|
package/dist/cli.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export interface CliOptions {
|
|
|
54
54
|
readonly ratchet: boolean;
|
|
55
55
|
/** Skip the repository configuration file entirely. */
|
|
56
56
|
readonly noConfig: boolean;
|
|
57
|
-
readonly format: 'human' | 'json' | 'sarif' | 'markdown';
|
|
57
|
+
readonly format: 'human' | 'json' | 'sarif' | 'markdown' | 'gitlab';
|
|
58
58
|
readonly graphFormat: GraphFormat;
|
|
59
59
|
readonly severities: Partial<Record<AnyRuleId, Severity>>;
|
|
60
60
|
readonly color: boolean | null;
|
|
@@ -77,7 +77,7 @@ export interface CliIO {
|
|
|
77
77
|
readonly env?: Readonly<Record<string, string | undefined>> | undefined;
|
|
78
78
|
readonly isTTY?: boolean | undefined;
|
|
79
79
|
}
|
|
80
|
-
export declare const HELP = "spec-graph - turn Markdown specifications into a verifiable graph\n\nUSAGE\n spec-graph [check] [patterns...] [options]\n spec-graph query <selector|project:rule> [patterns...] [options]\n spec-graph graph [patterns...] [--graph-format dot|mermaid|json]\n spec-graph rules [rule-id] [--explain]\n spec-graph diff <before.json> <after.json> [--format human|json|markdown]\n\nCOMMANDS\n check Validate the specification graph. The default.\n query Run a selector - or a registered project rule, by its id - and\n print the matching paths.\n graph Export the graph for Graphviz, Mermaid, or another tool.\n rules List the diagnostics that will run, built in and project. Name one\n to see only that one; --explain adds its selector and the ADR that\n decided it.\n diff Compare two graph exports: documents added, removed, moved or\n accepted; relations added or removed; and obligations resolved or\n reopened where they can be told apart. Make both exports with the\n same spec-graph. Exits 0 whether anything changed or not.\n\nOPTIONS\n --root <dir> Directory the patterns resolve against. Without it,\n .spec-graph.json is looked for in the working\n directory and then upward as far as the repository,\n and the directory holding it becomes the root - so a\n run from a subdirectory reports what a run from the\n top reports. Paths typed on the command line stay\n relative to where they were typed.\n --ignore <glob> Skip paths. Repeatable.\n --ignore-ref <glob> Do not report these reference targets when they fail\n to resolve, for repositories where [[...]] tags a\n concept rather than naming a file. Repeatable.\n Suppresses findings only, never edges.\n --family <name> Families a bare identifier in prose may name. When\n given, everything else stays prose. Repeatable.\n --ignore-family <name> Families that are never citations - RFC when the repo\n cites RFC 2119 and keeps its own RFCs. Repeatable.\n --history <glob> Files that log what was decided rather than deciding\n it - journals, changelogs, minutes. Their links are\n still checked; their obligations are not. Repeatable.\n --baseline <file> Accept the findings recorded in this file and report\n only what is new since. The file has to be there: a\n path that reads nothing accepts nothing and says so\n nowhere. A \"baseline\" in the configuration may name a\n file not recorded yet; a path typed here may not.\n --record-baseline <f> Write today's findings to this file as accepted debt,\n and exit 0 without judging them.\n --ratchet Also fail when a baseline entry no longer occurs, so\n a paid-off exemption cannot outlive the defect.\n --no-config Ignore .spec-graph.json and the package.json key.\n --format <fmt> human, json, sarif or markdown. sarif is the\n interchange format GitHub code scanning and editors\n already read, for check; markdown
|
|
80
|
+
export declare const HELP = "spec-graph - turn Markdown specifications into a verifiable graph\n\nUSAGE\n spec-graph [check] [patterns...] [options]\n spec-graph query <selector|project:rule> [patterns...] [options]\n spec-graph graph [patterns...] [--graph-format dot|mermaid|json]\n spec-graph rules [rule-id] [--explain]\n spec-graph diff <before.json> <after.json> [--format human|json|markdown]\n\nCOMMANDS\n check Validate the specification graph. The default.\n query Run a selector - or a registered project rule, by its id - and\n print the matching paths.\n graph Export the graph for Graphviz, Mermaid, or another tool.\n rules List the diagnostics that will run, built in and project. Name one\n to see only that one; --explain adds its selector and the ADR that\n decided it.\n diff Compare two graph exports: documents added, removed, moved or\n accepted; relations added or removed; and obligations resolved or\n reopened where they can be told apart. Make both exports with the\n same spec-graph. Exits 0 whether anything changed or not.\n\nOPTIONS\n --root <dir> Directory the patterns resolve against. Without it,\n .spec-graph.json is looked for in the working\n directory and then upward as far as the repository,\n and the directory holding it becomes the root - so a\n run from a subdirectory reports what a run from the\n top reports. Paths typed on the command line stay\n relative to where they were typed.\n --ignore <glob> Skip paths. Repeatable.\n --ignore-ref <glob> Do not report these reference targets when they fail\n to resolve, for repositories where [[...]] tags a\n concept rather than naming a file. Repeatable.\n Suppresses findings only, never edges.\n --family <name> Families a bare identifier in prose may name. When\n given, everything else stays prose. Repeatable.\n --ignore-family <name> Families that are never citations - RFC when the repo\n cites RFC 2119 and keeps its own RFCs. Repeatable.\n --history <glob> Files that log what was decided rather than deciding\n it - journals, changelogs, minutes. Their links are\n still checked; their obligations are not. Repeatable.\n --baseline <file> Accept the findings recorded in this file and report\n only what is new since. The file has to be there: a\n path that reads nothing accepts nothing and says so\n nowhere. A \"baseline\" in the configuration may name a\n file not recorded yet; a path typed here may not.\n --record-baseline <f> Write today's findings to this file as accepted debt,\n and exit 0 without judging them.\n --ratchet Also fail when a baseline entry no longer occurs, so\n a paid-off exemption cannot outlive the defect.\n --no-config Ignore .spec-graph.json and the package.json key.\n --format <fmt> human, json, sarif, gitlab or markdown. sarif is the\n interchange format GitHub code scanning and editors\n already read, and gitlab the Code Quality report a\n GitLab merge request reads, both for check; markdown\n is a table for a pull-request comment or\n $GITHUB_STEP_SUMMARY, for check or diff\n (default: human)\n --graph-format <fmt> dot, mermaid or json (default: dot)\n --documents-only Leave items out of the exported graph\n --rule <id>=<severity> Override one rule: error, warn, info or off. A project\n rule is named in full: project:no-draft-dependency.\n Repeatable.\n --max <n> Show at most n findings (0 = no limit)\n --max-warnings <n> Fail when warnings exceed n (default: no limit)\n --strict Raise every warning to an error. An explicit --rule\n still wins, so --strict --rule x=warn exempts x.\n --color / --no-color Force colour on or off\n --ascii Use ASCII glyphs only\n --verbose Include parse problems, per-file detail, and every\n reference --ignore-ref or --ignore-family silenced\n -h, --help Show this help\n -v, --version Show the version\n\nSELECTORS\n A selector is a path through the graph.\n\n item[openness=open] -delegates-to-> document[phase=retired]\n document[phase=active] -assumes-> document[phase=retired]\n document =supersedes=> document\n *[path^=docs/adr] -contains-> item[state=accepted-debt]\n\n Node types: document, item, * (any)\n Attributes: id, kind, title, path, file, line, phase, status, receptivity,\n alias, document, state (or disposition), openness, section,\n text, body, evidence, conflicted, fm.<front-matter-key>\n Operators: = != ^= $= *= ~= and [attr] for \"is present\"\n ~= is a regular expression, matched by an automaton that cannot\n backtrack: linear in the subject, whatever the pattern. It reads\n the usual syntax minus backreferences and lookaround, which are\n not regular - both are refused when the selector is read, with\n the character pointed at\n Relations: -kind-> <-kind- =kind=> <=kind= (= forms are transitive)\n\nEXIT CODES\n 0 clean\n 1 findings at error severity (or over --max-warnings)\n 2 the tool could not run\n\nCONFIGURATION\n Anything repeated on every run belongs in the repository rather than in the\n command. spec-graph reads the first of these that exists:\n\n .spec-graph.json, spec-graph.config.json, or a \"spec-graph\" key in\n package.json\n\n { \"patterns\": [\"docs/**/*.md\"],\n \"ignoreReferences\": [\"trap *\"],\n \"ignoreFamilies\": [\"RFC\"],\n \"historyPatterns\": [\"**/JOURNAL_*.md\", \"archive/**\"],\n \"baseline\": \".spec-graph-baseline.json\",\n \"severities\": { \"self-reference\": \"off\" },\n \"strict\": true }\n\n A flag always wins over the file, and list flags add to it rather than\n replacing it.\n\n A problem in that file stops the run with exit 2, named on stderr: invalid\n JSON, an unknown key, a rule that does not compile. A configuration that did\n not load checks a different repository than the one configured, and would\n report that one as consistent. --no-config runs on defaults instead.\n\nPROJECT RULES\n A convention spec-graph never anticipated is a selector plus a sentence, and\n belongs in the same file. Every rule declared here runs beside the built-ins\n and is reported, baselined, escalated by --strict and silenced by --rule in\n exactly the same way.\n\n { \"rules\": {\n \"no-draft-dependency\": {\n \"query\": \"document[phase=active] -depends-on-> document[phase=draft]\",\n \"message\": \"{0} depends on {1}, which is still a draft\",\n \"hint\": \"wait for {1} to be accepted, or drop it from {0.path}\",\n \"severity\": \"error\" } } }\n\n query One selector, or a list of them read as a union.\n message The headline. {0} is the first node on the path, {1} the next;\n {1.phase} and {0.fm.owner} read any selector attribute.\n hint The next action. Optional, and templated the same way.\n severity error, warn, info or off. Defaults to warn - a rule a team has\n just written has not yet earned the right to fail their build.\n\n The id is the name with project: in front, which is why it can never collide\n with a built-in. A selector that does not parse, or a {2} the query can never\n reach, is reported when the file is read rather than found missing later.\n\n To see what one matches without copying its selector out of the file:\n\n spec-graph query project:no-draft-dependency --verbose\n\nADOPTING THIS ON AN OLD REPOSITORY\n Record what is already wrong, then report only what happens next:\n\n spec-graph check --record-baseline .spec-graph-baseline.json\n git add .spec-graph-baseline.json\n\n The file is keyed by specification and citation, not by line number, so it\n survives edits, moves and renames. Findings that stop occurring are reported\n so the file can be tightened; nothing new gets in.\n\nEXAMPLES\n spec-graph \"docs/**/*.md\"\n spec-graph check --rule self-reference=off --format json\n spec-graph query 'item[openness=open] -delegates-to-> document[phase=retired]'\n spec-graph check --ignore-ref \"trap *\" # [[trap 55]] tags a concept, not a file\n spec-graph graph --documents-only --graph-format mermaid > graph.mmd\n spec-graph check --history \"**/JOURNAL_*.md\" # a log is not a specification\n spec-graph check --baseline .spec-graph-baseline.json\n spec-graph rules --explain # including this repository's own\n spec-graph rules ghost-handover --explain # and why it exists\n spec-graph check --rule project:no-draft-dependency=off\n spec-graph query project:no-draft-dependency # what does that rule match?\n spec-graph check --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n spec-graph check --format gitlab > gl-code-quality-report.json\n spec-graph diff base.json head.json --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n";
|
|
81
81
|
/** Parses argv into options. Throws {@link UsageError} on anything malformed. */
|
|
82
82
|
export declare function parseArgs(argv: readonly string[], cwd: string): CliOptions;
|
|
83
83
|
/** Runs the CLI and returns the process exit code. Never throws. */
|
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import { discoverConfig, loadConfig } from './config.js';
|
|
|
14
14
|
import { DiffInputError, diffExports, formatDiffJson, formatDiffMarkdown, formatDiffText, parseGraphExport, } from './diff.js';
|
|
15
15
|
import { isGlob, underRoot } from './glob.js';
|
|
16
16
|
import { analyse, DEFAULT_PATTERNS, withDiagnostics } from './runner.js';
|
|
17
|
-
import { formatGraph, formatJson, formatMarkdown, formatReport, formatSarif, shouldUseAscii, shouldUseColor, } from './report.js';
|
|
17
|
+
import { formatGitlab, formatGraph, formatJson, formatMarkdown, formatReport, formatSarif, shouldUseAscii, shouldUseColor, } from './report.js';
|
|
18
18
|
import { DEFAULT_SEVERITIES, resolveStrict, RULE_DECISIONS, RULE_DESCRIPTIONS, RULE_IDS, RULE_QUERIES, } from './rules.js';
|
|
19
19
|
import { isAbsolutePath, toPosix } from './paths.js';
|
|
20
20
|
import { formatRef } from './source.js';
|
|
@@ -81,11 +81,13 @@ OPTIONS
|
|
|
81
81
|
--ratchet Also fail when a baseline entry no longer occurs, so
|
|
82
82
|
a paid-off exemption cannot outlive the defect.
|
|
83
83
|
--no-config Ignore .spec-graph.json and the package.json key.
|
|
84
|
-
--format <fmt> human, json, sarif or markdown. sarif is the
|
|
84
|
+
--format <fmt> human, json, sarif, gitlab or markdown. sarif is the
|
|
85
85
|
interchange format GitHub code scanning and editors
|
|
86
|
-
already read,
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
already read, and gitlab the Code Quality report a
|
|
87
|
+
GitLab merge request reads, both for check; markdown
|
|
88
|
+
is a table for a pull-request comment or
|
|
89
|
+
$GITHUB_STEP_SUMMARY, for check or diff
|
|
90
|
+
(default: human)
|
|
89
91
|
--graph-format <fmt> dot, mermaid or json (default: dot)
|
|
90
92
|
--documents-only Leave items out of the exported graph
|
|
91
93
|
--rule <id>=<severity> Override one rule: error, warn, info or off. A project
|
|
@@ -201,6 +203,7 @@ EXAMPLES
|
|
|
201
203
|
spec-graph check --rule project:no-draft-dependency=off
|
|
202
204
|
spec-graph query project:no-draft-dependency # what does that rule match?
|
|
203
205
|
spec-graph check --format markdown >> "$GITHUB_STEP_SUMMARY"
|
|
206
|
+
spec-graph check --format gitlab > gl-code-quality-report.json
|
|
204
207
|
spec-graph diff base.json head.json --format markdown >> "$GITHUB_STEP_SUMMARY"
|
|
205
208
|
`;
|
|
206
209
|
const SEVERITIES = ['error', 'warn', 'info', 'off'];
|
|
@@ -330,8 +333,8 @@ export function parseArgs(argv, cwd) {
|
|
|
330
333
|
break;
|
|
331
334
|
case '--format': {
|
|
332
335
|
const value = next(arg, i);
|
|
333
|
-
if (value !== 'human' && value !== 'json' && value !== 'sarif' && value !== 'markdown') {
|
|
334
|
-
throw new UsageError(`--format must be human, json, sarif or
|
|
336
|
+
if (value !== 'human' && value !== 'json' && value !== 'sarif' && value !== 'markdown' && value !== 'gitlab') {
|
|
337
|
+
throw new UsageError(`--format must be human, json, sarif, markdown or gitlab, got "${value}"`);
|
|
335
338
|
}
|
|
336
339
|
format = value;
|
|
337
340
|
i += 1;
|
|
@@ -381,12 +384,13 @@ export function parseArgs(argv, cwd) {
|
|
|
381
384
|
throw new UsageError(`unknown option "${arg}"\n run "spec-graph --help" to see the available options`);
|
|
382
385
|
}
|
|
383
386
|
}
|
|
384
|
-
// SARIF
|
|
385
|
-
// back to JSON would hand a pipeline something
|
|
386
|
-
// message about a schema rather than about the
|
|
387
|
-
// is for a pull request, which reads a check
|
|
388
|
-
|
|
389
|
-
|
|
387
|
+
// SARIF and GitLab's Code Quality are reports about findings, and only
|
|
388
|
+
// `check` produces those. Falling back to JSON would hand a pipeline something
|
|
389
|
+
// its uploader rejects with a message about a schema rather than about the
|
|
390
|
+
// command that was run. Markdown is for a pull request, which reads a check
|
|
391
|
+
// or a diff.
|
|
392
|
+
if ((format === 'sarif' || format === 'gitlab') && command !== 'check' && !help && !version) {
|
|
393
|
+
throw new UsageError(`--format ${format} reports findings, so it belongs to check, not to ${command}`);
|
|
390
394
|
}
|
|
391
395
|
if (format === 'markdown' && command !== 'check' && command !== 'diff' && !help && !version) {
|
|
392
396
|
throw new UsageError(`--format markdown is a report for a pull request, so it belongs to check or diff, not to ${command}`);
|
|
@@ -702,11 +706,13 @@ export async function main(io = {}) {
|
|
|
702
706
|
const reporterOptions = { verbose: options.verbose, max: options.max, escalated, ...baselineNote };
|
|
703
707
|
out(options.format === 'sarif'
|
|
704
708
|
? formatSarif(reported, reported.graph, { version: await readVersion(), escalated, projectRules })
|
|
705
|
-
: options.format === '
|
|
706
|
-
?
|
|
707
|
-
: options.format === '
|
|
708
|
-
?
|
|
709
|
-
:
|
|
709
|
+
: options.format === 'gitlab'
|
|
710
|
+
? formatGitlab(reported, { escalated })
|
|
711
|
+
: options.format === 'json'
|
|
712
|
+
? formatJson(reported, { escalated, ...baselineNote })
|
|
713
|
+
: options.format === 'markdown'
|
|
714
|
+
? formatMarkdown(reported, reporterOptions)
|
|
715
|
+
: `${formatReport(reported, { color, ascii, ...reporterOptions })}\n`);
|
|
710
716
|
if (!reported.ok || looseBaseline)
|
|
711
717
|
return EXIT_FAILED;
|
|
712
718
|
if (options.maxWarnings >= 0 && reported.summary.warnings > options.maxWarnings)
|
|
@@ -738,7 +744,20 @@ function below(root, from) {
|
|
|
738
744
|
function anchor(prefix, value) {
|
|
739
745
|
if (prefix === '' || isAbsolutePath(value))
|
|
740
746
|
return value;
|
|
741
|
-
|
|
747
|
+
const negated = value.startsWith('!');
|
|
748
|
+
const directories = prefix.split('/');
|
|
749
|
+
let rest = toPosix(negated ? value.slice(1) : value);
|
|
750
|
+
// `../docs` typed one directory down is the root's `docs`. That is arithmetic
|
|
751
|
+
// on two relative paths, and this is the one place both are known; left in
|
|
752
|
+
// the pattern, it reads as a glob climbing out of the root, which is refused.
|
|
753
|
+
// A bare `..` typed in `docs/deep` leaves `docs/`: the directory's contents,
|
|
754
|
+
// which is what it named.
|
|
755
|
+
while (directories.length > 0 && (rest === '..' || rest.startsWith('../'))) {
|
|
756
|
+
directories.pop();
|
|
757
|
+
rest = rest.slice(3);
|
|
758
|
+
}
|
|
759
|
+
const joined = [...directories, rest].join('/');
|
|
760
|
+
return negated ? `!${joined}` : joined;
|
|
742
761
|
}
|
|
743
762
|
/** The same, for an ignore, where a bare name is a directory at any depth. */
|
|
744
763
|
function anchorPath(prefix, pattern) {
|