@descent-vtt/spec-graph 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +84 -0
- package/README.md +25 -9
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +60 -12
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +3 -3
- package/dist/config.js +4 -4
- package/dist/config.js.map +1 -1
- package/dist/glob.d.ts +7 -1
- package/dist/glob.js +10 -2
- package/dist/glob.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/paths.d.ts +10 -0
- package/dist/paths.js +13 -0
- package/dist/paths.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,90 @@
|
|
|
3
3
|
Notable changes, newest first. Versions follow [semver](https://semver.org):
|
|
4
4
|
a patch fixes behaviour without asking anything of a repository that upgrades.
|
|
5
5
|
|
|
6
|
+
## 0.7.0
|
|
7
|
+
|
|
8
|
+
Three ways a run handed back something other than what it said it was: a
|
|
9
|
+
configuration that did not load reporting a clean graph, a `--baseline` that
|
|
10
|
+
read nothing accepting nothing, and `--verbose` breaking the document it was
|
|
11
|
+
printed above. The first two were found on an 885-document repository, and the
|
|
12
|
+
third while fixing them.
|
|
13
|
+
|
|
14
|
+
A minor rather than a patch. Two of the three turn a build that was passing
|
|
15
|
+
into one that fails, which is the whole point of them and exactly what the rule
|
|
16
|
+
at the top of this file says a patch may not do.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
**A configuration that did not load stops the run, with exit `2`.** Invalid
|
|
21
|
+
JSON, an unknown key, a value of the wrong type, a project rule that does not
|
|
22
|
+
compile: each was printed to stderr, and the run then went on with defaults -
|
|
23
|
+
reporting `the specification graph is consistent`, exiting `0`, and writing
|
|
24
|
+
`"ok": true` into JSON, SARIF and Markdown. Under `--strict` as well. A
|
|
25
|
+
`{1.phse}` for `{1.phase}` in one rule's message was a green build over exactly
|
|
26
|
+
the files that rule fails.
|
|
27
|
+
|
|
28
|
+
It was the one place the exit codes disagreed with themselves, too:
|
|
29
|
+
`--rule not-a-rule=off` on the command line has always exited `2`, and the same
|
|
30
|
+
misspelling inside `"severities"` printed a line and passed. `--no-config`
|
|
31
|
+
checks on defaults, and the exit code then says which run it was. The old
|
|
32
|
+
behaviour was the decision in ADR-0010, and that ADR now carries why it moved.
|
|
33
|
+
|
|
34
|
+
**A `--baseline` that cannot be read stops the run.** Read as an empty baseline,
|
|
35
|
+
a typo in the path reports every accepted finding as new, passes `--ratchet`
|
|
36
|
+
with nothing left to be stale about, and looks like a regression nobody
|
|
37
|
+
introduced. A `"baseline"` named in configuration is unchanged and still
|
|
38
|
+
optional, because a repository declares the path before the first run records
|
|
39
|
+
the file - `--verbose` now says when it read nothing. Unreadable, as opposed to
|
|
40
|
+
absent, is an error wherever the path was written. A baseline that cannot be
|
|
41
|
+
*parsed* is still reported and ignored: it only ever suppresses, so a row that
|
|
42
|
+
is dropped is a finding reported. See
|
|
43
|
+
[ADR-0012](docs/adr/0012-a-baseline-is-a-ratchet.md).
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
**`--verbose` no longer breaks the format it was combined with.** The line
|
|
48
|
+
naming which configuration file was read, and the one naming a project rule's
|
|
49
|
+
selector under `query`, went to stdout above the report. With `--format json`
|
|
50
|
+
that made the output unparseable; with `--format sarif` it made a file the
|
|
51
|
+
code-scanning uploader rejects over a schema rather than over the command that
|
|
52
|
+
was run. Both now go to stderr, with everything else a run says about itself.
|
|
53
|
+
[ADR-0015](docs/adr/0015-feedback-goes-where-the-tools-already-look.md) already
|
|
54
|
+
had the rule - rows "cannot go on stdout beside a JSON document without
|
|
55
|
+
breaking the parse" - and these two lines were the places that did not follow
|
|
56
|
+
it.
|
|
57
|
+
|
|
58
|
+
**An absolute baseline path is no longer resolved under the root.**
|
|
59
|
+
`--baseline /tmp/b.json` read `<root>/tmp/b.json` and `--record-baseline`
|
|
60
|
+
wrote there: the recording landed inside the corpus, or failed naming a
|
|
61
|
+
directory nobody had typed, and the read found nothing and said nothing. Both
|
|
62
|
+
platforms' spellings are absolute on either platform, since one repository is
|
|
63
|
+
read on a Windows checkout and in Linux CI from the same file.
|
|
64
|
+
|
|
65
|
+
- **A `package.json` configuration problem names `package.json`.** It reported
|
|
66
|
+
`"spec-graph" must be an object` with no file in it, and a problem that names
|
|
67
|
+
no file sends the reader to a `.spec-graph.json` that is not there.
|
|
68
|
+
|
|
69
|
+
### Verified
|
|
70
|
+
|
|
71
|
+
| | |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `npm run lint` | pass |
|
|
74
|
+
| `npm test` | 935 passing, 23 files |
|
|
75
|
+
| `npm run selfcheck` | pass, over 23 documents and 184 relations |
|
|
76
|
+
| focused sweep, every changed region | 100% |
|
|
77
|
+
| `src/cli.ts` alone, before and after | 80.43% over 797 mutants, then 81.11% over 826 |
|
|
78
|
+
| `break` | unchanged at 70 |
|
|
79
|
+
|
|
80
|
+
Each behaviour was also run against the built binary rather than only through
|
|
81
|
+
`main()`, including the absolute paths, which is where a Windows drive letter
|
|
82
|
+
either survives `toPosix` or does not.
|
|
83
|
+
|
|
84
|
+
Moving two lines from stdout to stderr cost two mutants that a JSON parse used
|
|
85
|
+
to kill for free, and both were replaced with an assertion: a flag that is only
|
|
86
|
+
ever asserted when it is passed stops meaning anything. Both `cli.ts` figures
|
|
87
|
+
above are single-file runs on one machine with `--coverageAnalysis all`, which
|
|
88
|
+
is the comparison ADR-0007 asks for and not the hosted sweep that governs.
|
|
89
|
+
|
|
6
90
|
## 0.6.0
|
|
7
91
|
|
|
8
92
|
A diff of two graph exports that names only the changes it can tell apart,
|
package/README.md
CHANGED
|
@@ -561,8 +561,15 @@ spec-graph check --baseline .spec-graph-baseline.json --ratchet
|
|
|
561
561
|
|
|
562
562
|
Now an undeclared finding fails **and** a declared one that no longer occurs
|
|
563
563
|
fails, each named, with `--record-baseline` as the fix — and the diff of that
|
|
564
|
-
file is the record of what was paid off.
|
|
565
|
-
|
|
564
|
+
file is the record of what was paid off.
|
|
565
|
+
|
|
566
|
+
**A `--baseline` you typed has to be there.** A path that reads nothing accepts
|
|
567
|
+
nothing, reports every accepted finding as new, and passes `--ratchet` with
|
|
568
|
+
nothing left to be stale about, so a typo in it looks exactly like a regression
|
|
569
|
+
nobody introduced: it exits `2` instead. A `"baseline"` in the configuration is
|
|
570
|
+
the other case and stays optional, because a repository declares the path before
|
|
571
|
+
the first run records the file — `--verbose` says when it read nothing. Either
|
|
572
|
+
may be absolute. See [ADR-0012](docs/adr/0012-a-baseline-is-a-ratchet.md).
|
|
566
573
|
|
|
567
574
|
## In CI
|
|
568
575
|
|
|
@@ -633,11 +640,19 @@ a path typed on the command line stays relative to where you typed it. See
|
|
|
633
640
|
A flag always wins over the file, and list flags **add** to it rather than
|
|
634
641
|
replacing it — a `--ignore-ref` on the command line is one more exclusion, not a
|
|
635
642
|
decision to discard what the repository already declared. `--verbose` prints
|
|
636
|
-
which file was read
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
643
|
+
which file was read, on stderr with everything else a run says about itself, so
|
|
644
|
+
it can be combined with any `--format` and leave the document on stdout intact.
|
|
645
|
+
`--no-config` skips the mechanism entirely.
|
|
646
|
+
|
|
647
|
+
A broken config stops the run with exit `2`, each problem named on stderr —
|
|
648
|
+
invalid JSON, a value of the wrong type, an unknown key, a rule that does not
|
|
649
|
+
compile. A configuration that did not load checks a different repository than
|
|
650
|
+
the one configured, and would report *that* one as consistent: a `{1.phse}` for
|
|
651
|
+
`{1.phase}` in one rule's message is a green build over the files the correctly
|
|
652
|
+
spelled rule fails. An unknown key counts, for the same reason it is reported at
|
|
653
|
+
all — a silently ignored `ignoreReference` is a configuration that looks applied
|
|
654
|
+
and is not. `--no-config` checks on defaults instead, and the exit code then
|
|
655
|
+
says which run it was.
|
|
641
656
|
|
|
642
657
|
### Family rules
|
|
643
658
|
|
|
@@ -711,8 +726,9 @@ spec-graph diff <before> <after> Compare two JSON graph exports.
|
|
|
711
726
|
```
|
|
712
727
|
|
|
713
728
|
Exit codes: `0` clean, `1` findings, `2` the tool could not run. A mistyped flag
|
|
714
|
-
never masquerades as a passing build, and a pattern
|
|
715
|
-
|
|
729
|
+
never masquerades as a passing build, and neither does a pattern that matches
|
|
730
|
+
nothing, a configuration file that did not load, or a `--baseline` that is not
|
|
731
|
+
there.
|
|
716
732
|
|
|
717
733
|
## Continuous integration
|
|
718
734
|
|
package/dist/cli.d.ts
CHANGED
|
@@ -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.
|
|
80
|
+
export declare const HELP = "spec-graph - turn Markdown specifications into a verifiable graph\n\nUSAGE\n spec-graph [check] [patterns...] [options]\n spec-graph query <selector|project:rule> [patterns...] [options]\n spec-graph graph [patterns...] [--graph-format dot|mermaid|json]\n spec-graph rules [rule-id] [--explain]\n spec-graph diff <before.json> <after.json> [--format human|json|markdown]\n\nCOMMANDS\n check Validate the specification graph. The default.\n query Run a selector - or a registered project rule, by its id - and\n print the matching paths.\n graph Export the graph for Graphviz, Mermaid, or another tool.\n rules List the diagnostics that will run, built in and project. Name one\n to see only that one; --explain adds its selector and the ADR that\n decided it.\n diff Compare two graph exports: documents added, removed, moved or\n accepted; relations added or removed; and obligations resolved or\n reopened where they can be told apart. Make both exports with the\n same spec-graph. Exits 0 whether anything changed or not.\n\nOPTIONS\n --root <dir> Directory the patterns resolve against. Without it,\n .spec-graph.json is looked for in the working\n directory and then upward as far as the repository,\n and the directory holding it becomes the root - so a\n run from a subdirectory reports what a run from the\n top reports. Paths typed on the command line stay\n relative to where they were typed.\n --ignore <glob> Skip paths. Repeatable.\n --ignore-ref <glob> Do not report these reference targets when they fail\n to resolve, for repositories where [[...]] tags a\n concept rather than naming a file. Repeatable.\n Suppresses findings only, never edges.\n --family <name> Families a bare identifier in prose may name. When\n given, everything else stays prose. Repeatable.\n --ignore-family <name> Families that are never citations - RFC when the repo\n cites RFC 2119 and keeps its own RFCs. Repeatable.\n --history <glob> Files that log what was decided rather than deciding\n it - journals, changelogs, minutes. Their links are\n still checked; their obligations are not. Repeatable.\n --baseline <file> Accept the findings recorded in this file and report\n only what is new since. The file has to be there: a\n path that reads nothing accepts nothing and says so\n nowhere. A \"baseline\" in the configuration may name a\n file not recorded yet; a path typed here may not.\n --record-baseline <f> Write today's findings to this file as accepted debt,\n and exit 0 without judging them.\n --ratchet Also fail when a baseline entry no longer occurs, so\n a paid-off exemption cannot outlive the defect.\n --no-config Ignore .spec-graph.json and the package.json key.\n --format <fmt> human, json, sarif or markdown. sarif is the\n interchange format GitHub code scanning and editors\n already read, for check; markdown is a table for a\n pull-request comment or $GITHUB_STEP_SUMMARY, for check\n or diff (default: human)\n --graph-format <fmt> dot, mermaid or json (default: dot)\n --documents-only Leave items out of the exported graph\n --rule <id>=<severity> Override one rule: error, warn, info or off. A project\n rule is named in full: project:no-draft-dependency.\n Repeatable.\n --max <n> Show at most n findings (0 = no limit)\n --max-warnings <n> Fail when warnings exceed n (default: no limit)\n --strict Raise every warning to an error. An explicit --rule\n still wins, so --strict --rule x=warn exempts x.\n --color / --no-color Force colour on or off\n --ascii Use ASCII glyphs only\n --verbose Include parse problems, per-file detail, and every\n reference --ignore-ref or --ignore-family silenced\n -h, --help Show this help\n -v, --version Show the version\n\nSELECTORS\n A selector is a path through the graph.\n\n item[openness=open] -delegates-to-> document[phase=retired]\n document[phase=active] -assumes-> document[phase=retired]\n document =supersedes=> document\n *[path^=docs/adr] -contains-> item[state=accepted-debt]\n\n Node types: document, item, * (any)\n Attributes: id, kind, title, path, file, line, phase, status, receptivity,\n alias, document, state (or disposition), openness, section,\n text, body, evidence, conflicted, fm.<front-matter-key>\n Operators: = != ^= $= *= ~= and [attr] for \"is present\"\n ~= is a regular expression, matched by an automaton that cannot\n backtrack: linear in the subject, whatever the pattern. It reads\n the usual syntax minus backreferences and lookaround, which are\n not regular - both are refused when the selector is read, with\n the character pointed at\n Relations: -kind-> <-kind- =kind=> <=kind= (= forms are transitive)\n\nEXIT CODES\n 0 clean\n 1 findings at error severity (or over --max-warnings)\n 2 the tool could not run\n\nCONFIGURATION\n Anything repeated on every run belongs in the repository rather than in the\n command. spec-graph reads the first of these that exists:\n\n .spec-graph.json, spec-graph.config.json, or a \"spec-graph\" key in\n package.json\n\n { \"patterns\": [\"docs/**/*.md\"],\n \"ignoreReferences\": [\"trap *\"],\n \"ignoreFamilies\": [\"RFC\"],\n \"historyPatterns\": [\"**/JOURNAL_*.md\", \"archive/**\"],\n \"baseline\": \".spec-graph-baseline.json\",\n \"severities\": { \"self-reference\": \"off\" },\n \"strict\": true }\n\n A flag always wins over the file, and list flags add to it rather than\n replacing it.\n\n A problem in that file stops the run with exit 2, named on stderr: invalid\n JSON, an unknown key, a rule that does not compile. A configuration that did\n not load checks a different repository than the one configured, and would\n report that one as consistent. --no-config runs on defaults instead.\n\nPROJECT RULES\n A convention spec-graph never anticipated is a selector plus a sentence, and\n belongs in the same file. Every rule declared here runs beside the built-ins\n and is reported, baselined, escalated by --strict and silenced by --rule in\n exactly the same way.\n\n { \"rules\": {\n \"no-draft-dependency\": {\n \"query\": \"document[phase=active] -depends-on-> document[phase=draft]\",\n \"message\": \"{0} depends on {1}, which is still a draft\",\n \"hint\": \"wait for {1} to be accepted, or drop it from {0.path}\",\n \"severity\": \"error\" } } }\n\n query One selector, or a list of them read as a union.\n message The headline. {0} is the first node on the path, {1} the next;\n {1.phase} and {0.fm.owner} read any selector attribute.\n hint The next action. Optional, and templated the same way.\n severity error, warn, info or off. Defaults to warn - a rule a team has\n just written has not yet earned the right to fail their build.\n\n The id is the name with project: in front, which is why it can never collide\n with a built-in. A selector that does not parse, or a {2} the query can never\n reach, is reported when the file is read rather than found missing later.\n\n To see what one matches without copying its selector out of the file:\n\n spec-graph query project:no-draft-dependency --verbose\n\nADOPTING THIS ON AN OLD REPOSITORY\n Record what is already wrong, then report only what happens next:\n\n spec-graph check --record-baseline .spec-graph-baseline.json\n git add .spec-graph-baseline.json\n\n The file is keyed by specification and citation, not by line number, so it\n survives edits, moves and renames. Findings that stop occurring are reported\n so the file can be tightened; nothing new gets in.\n\nEXAMPLES\n spec-graph \"docs/**/*.md\"\n spec-graph check --rule self-reference=off --format json\n spec-graph query 'item[openness=open] -delegates-to-> document[phase=retired]'\n spec-graph check --ignore-ref \"trap *\" # [[trap 55]] tags a concept, not a file\n spec-graph graph --documents-only --graph-format mermaid > graph.mmd\n spec-graph check --history \"**/JOURNAL_*.md\" # a log is not a specification\n spec-graph check --baseline .spec-graph-baseline.json\n spec-graph rules --explain # including this repository's own\n spec-graph rules ghost-handover --explain # and why it exists\n spec-graph check --rule project:no-draft-dependency=off\n spec-graph query project:no-draft-dependency # what does that rule match?\n spec-graph check --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n spec-graph diff base.json head.json --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n";
|
|
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
|
@@ -16,7 +16,7 @@ import { isGlob, underRoot } from './glob.js';
|
|
|
16
16
|
import { analyse, DEFAULT_PATTERNS, withDiagnostics } from './runner.js';
|
|
17
17
|
import { 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
|
-
import { toPosix } from './paths.js';
|
|
19
|
+
import { isAbsolutePath, toPosix } from './paths.js';
|
|
20
20
|
import { formatRef } from './source.js';
|
|
21
21
|
import { execute, parseQuery, QueryError, renderMatch } from './select.js';
|
|
22
22
|
import { isProjectRule } from './types.js';
|
|
@@ -72,7 +72,10 @@ OPTIONS
|
|
|
72
72
|
it - journals, changelogs, minutes. Their links are
|
|
73
73
|
still checked; their obligations are not. Repeatable.
|
|
74
74
|
--baseline <file> Accept the findings recorded in this file and report
|
|
75
|
-
only what is new since.
|
|
75
|
+
only what is new since. The file has to be there: a
|
|
76
|
+
path that reads nothing accepts nothing and says so
|
|
77
|
+
nowhere. A "baseline" in the configuration may name a
|
|
78
|
+
file not recorded yet; a path typed here may not.
|
|
76
79
|
--record-baseline <f> Write today's findings to this file as accepted debt,
|
|
77
80
|
and exit 0 without judging them.
|
|
78
81
|
--ratchet Also fail when a baseline entry no longer occurs, so
|
|
@@ -142,6 +145,11 @@ CONFIGURATION
|
|
|
142
145
|
A flag always wins over the file, and list flags add to it rather than
|
|
143
146
|
replacing it.
|
|
144
147
|
|
|
148
|
+
A problem in that file stops the run with exit 2, named on stderr: invalid
|
|
149
|
+
JSON, an unknown key, a rule that does not compile. A configuration that did
|
|
150
|
+
not load checks a different repository than the one configured, and would
|
|
151
|
+
report that one as consistent. --no-config runs on defaults instead.
|
|
152
|
+
|
|
145
153
|
PROJECT RULES
|
|
146
154
|
A convention spec-graph never anticipated is a selector plus a sentence, and
|
|
147
155
|
belongs in the same file. Every rule declared here runs beside the built-ins
|
|
@@ -424,9 +432,11 @@ function count(n, word, many) {
|
|
|
424
432
|
/**
|
|
425
433
|
* Reads a baseline file.
|
|
426
434
|
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
* baseline
|
|
435
|
+
* Absent and unreadable are different answers, and the caller needs both. A
|
|
436
|
+
* repository that has not recorded a baseline yet declares the path before the
|
|
437
|
+
* file exists, and an empty baseline accepts nothing, which is what that run
|
|
438
|
+
* should report (ADR-0012). Anything else - a directory, a permission, a path
|
|
439
|
+
* that went somewhere unintended - is the tool failing to do what it was told.
|
|
430
440
|
*/
|
|
431
441
|
async function readBaseline(path, source) {
|
|
432
442
|
let raw;
|
|
@@ -434,10 +444,15 @@ async function readBaseline(path, source) {
|
|
|
434
444
|
const { readFile } = await import('node:fs/promises');
|
|
435
445
|
raw = await readFile(path, 'utf8');
|
|
436
446
|
}
|
|
437
|
-
catch {
|
|
438
|
-
|
|
447
|
+
catch (error) {
|
|
448
|
+
const failure = error;
|
|
449
|
+
return {
|
|
450
|
+
baseline: EMPTY_BASELINE,
|
|
451
|
+
problems: [],
|
|
452
|
+
unread: { missing: failure.code === 'ENOENT', message: failure.message },
|
|
453
|
+
};
|
|
439
454
|
}
|
|
440
|
-
return parseBaseline(raw, source);
|
|
455
|
+
return { ...parseBaseline(raw, source), unread: null };
|
|
441
456
|
}
|
|
442
457
|
function parseCount(flag, value) {
|
|
443
458
|
const parsed = Number.parseInt(value, 10);
|
|
@@ -495,6 +510,17 @@ export async function main(io = {}) {
|
|
|
495
510
|
: discoverConfig(options.root);
|
|
496
511
|
for (const problem of loaded.problems)
|
|
497
512
|
err(`spec-graph: ${problem}\n`);
|
|
513
|
+
// A configuration that did not load means a run against a different
|
|
514
|
+
// repository than the one configured - other patterns, other rules, other
|
|
515
|
+
// severities - and the verdict printed is about that one. Reporting the
|
|
516
|
+
// problem and carrying on gave a green build with `ok: true` from a file with
|
|
517
|
+
// a typo in it, which is the one answer a checker must never reach by
|
|
518
|
+
// accident, and the same mistake in a `--rule` has always exited 2. The
|
|
519
|
+
// findings are one flag away, and the flag is named. Amends ADR-0010.
|
|
520
|
+
if (loaded.problems.length > 0) {
|
|
521
|
+
err(`spec-graph: ${count(loaded.problems.length, 'problem')} in the configuration, so nothing was checked\n fix it, or run again with --no-config to check on defaults\n`);
|
|
522
|
+
return EXIT_ERROR;
|
|
523
|
+
}
|
|
498
524
|
const file = loaded.config;
|
|
499
525
|
const projectRules = file.rules ?? [];
|
|
500
526
|
const root = loaded.root;
|
|
@@ -543,9 +569,14 @@ export async function main(io = {}) {
|
|
|
543
569
|
};
|
|
544
570
|
// Named the way the reader would have to type it, because a discovered
|
|
545
571
|
// configuration is often not the one in front of them.
|
|
572
|
+
//
|
|
573
|
+
// On stderr, with everything else this run says about itself. stdout is the
|
|
574
|
+
// report, and in three of the four formats it is a document: a line above it
|
|
575
|
+
// made `--verbose --format json` unparseable and handed `--format sarif` to a
|
|
576
|
+
// code-scanning uploader that rejects it over a schema.
|
|
546
577
|
if (options.verbose && loaded.source !== null) {
|
|
547
578
|
const depth = here === '' ? 0 : here.split('/').length;
|
|
548
|
-
|
|
579
|
+
err(`configuration: ${'../'.repeat(depth)}${loaded.source}\n`);
|
|
549
580
|
}
|
|
550
581
|
let result;
|
|
551
582
|
try {
|
|
@@ -580,9 +611,11 @@ export async function main(io = {}) {
|
|
|
580
611
|
return EXIT_ERROR;
|
|
581
612
|
}
|
|
582
613
|
queries = rule.queries;
|
|
614
|
+
// Beside the report rather than inside it, for the reason the
|
|
615
|
+
// configuration line above is: `--format json` is a document.
|
|
583
616
|
if (options.verbose)
|
|
584
617
|
for (const source of rule.sources)
|
|
585
|
-
|
|
618
|
+
err(`${rule.id}: ${source}\n`);
|
|
586
619
|
}
|
|
587
620
|
else {
|
|
588
621
|
try {
|
|
@@ -624,6 +657,22 @@ export async function main(io = {}) {
|
|
|
624
657
|
let note;
|
|
625
658
|
if (source !== null) {
|
|
626
659
|
const held = await readBaseline(underRoot(root, source), source);
|
|
660
|
+
// A path typed on the command line says the file is there for this run.
|
|
661
|
+
// Read as an empty baseline instead, a typo in it reports every accepted
|
|
662
|
+
// finding as new, and passes --ratchet with nothing left to be stale.
|
|
663
|
+
// A path in the configuration is the case ADR-0012 argued for, and only
|
|
664
|
+
// where the file is absent: unreadable is unreadable wherever the path
|
|
665
|
+
// was written.
|
|
666
|
+
if (held.unread !== null && (options.baseline !== null || !held.unread.missing)) {
|
|
667
|
+
err(`spec-graph: cannot read the baseline ${source}: ${held.unread.message}\n`);
|
|
668
|
+
return EXIT_ERROR;
|
|
669
|
+
}
|
|
670
|
+
// Quiet by default, because a configured path legitimately precedes the
|
|
671
|
+
// first --record-baseline. Under --verbose, where parse problems already
|
|
672
|
+
// go, a path that reads nothing says so rather than accepting nothing.
|
|
673
|
+
if (held.unread !== null && options.verbose) {
|
|
674
|
+
err(`spec-graph: baseline ${source} is not there, so nothing is accepted\n`);
|
|
675
|
+
}
|
|
627
676
|
for (const problem of held.problems)
|
|
628
677
|
err(`spec-graph: ${problem}\n`);
|
|
629
678
|
const outcome = applyBaseline(result.graph, result.diagnostics, held.baseline);
|
|
@@ -687,11 +736,10 @@ function below(root, from) {
|
|
|
687
736
|
* the root cannot change what it means.
|
|
688
737
|
*/
|
|
689
738
|
function anchor(prefix, value) {
|
|
690
|
-
if (prefix === '' ||
|
|
739
|
+
if (prefix === '' || isAbsolutePath(value))
|
|
691
740
|
return value;
|
|
692
741
|
return value.startsWith('!') ? `!${prefix}/${value.slice(1)}` : `${prefix}/${value}`;
|
|
693
742
|
}
|
|
694
|
-
const ABSOLUTE = /^(?:[/\\]|[A-Za-z]:)/;
|
|
695
743
|
/** The same, for an ignore, where a bare name is a directory at any depth. */
|
|
696
744
|
function anchorPath(prefix, pattern) {
|
|
697
745
|
return isGlob(pattern) || pattern.includes('/') ? anchor(prefix, pattern) : pattern;
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The command line.\n *\n * Five verbs, because there are five questions worth asking of a specification\n * graph: is it consistent (`check`), what does it contain (`query`), what does\n * it look like (`graph`), what will you check for me (`rules`), and what did a\n * change do to it (`diff`).\n *\n * Exit codes are the contract with CI: `0` clean, `1` findings, `2` the tool\n * itself could not run. A usage mistake never masquerades as a passing build.\n */\n\nimport {\n applyBaseline,\n EMPTY_BASELINE,\n formatBaseline,\n parseBaseline,\n type Baseline,\n type StaleEntry,\n} from './baseline.js';\nimport { discoverConfig, loadConfig, type SpecGraphConfig } from './config.js';\nimport {\n DiffInputError,\n diffExports,\n formatDiffJson,\n formatDiffMarkdown,\n formatDiffText,\n parseGraphExport,\n type GraphExport,\n} from './diff.js';\nimport { isGlob, underRoot } from './glob.js';\nimport { analyse, DEFAULT_PATTERNS, withDiagnostics, type AnalyseOptions, type AnalysisResult } from './runner.js';\nimport {\n formatGraph,\n formatJson,\n formatMarkdown,\n formatReport,\n formatSarif,\n shouldUseAscii,\n shouldUseColor,\n type GraphFormat,\n} from './report.js';\nimport type { ProjectRule } from './project-rules.js';\nimport {\n DEFAULT_SEVERITIES,\n resolveStrict,\n RULE_DECISIONS,\n RULE_DESCRIPTIONS,\n RULE_IDS,\n RULE_QUERIES,\n} from './rules.js';\nimport { toPosix } from './paths.js';\nimport { formatRef } from './source.js';\nimport { execute, parseQuery, QueryError, renderMatch, type Match, type QuerySpec } from './select.js';\nimport { isProjectRule, type AnyRuleId, type RuleId, type Severity, type SpecNode } from './types.js';\n\nexport const EXIT_OK = 0;\nexport const EXIT_FAILED = 1;\nexport const EXIT_ERROR = 2;\n\nexport class UsageError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UsageError';\n }\n}\n\nexport type Command = 'check' | 'query' | 'graph' | 'rules' | 'diff';\n\nexport interface CliOptions {\n readonly command: Command;\n readonly patterns: readonly string[];\n readonly root: string;\n /**\n * Whether `--root` was given.\n *\n * When it was, the caller has named the root and configuration is read from\n * exactly there. When it was not, the root is discovered - which is a\n * different question from what the root currently is. See ADR-0018.\n */\n readonly rootExplicit: boolean;\n readonly ignore: readonly string[];\n /** Reference targets to leave unreported when they do not resolve. */\n readonly ignoreReferences: readonly string[];\n /** Families a bare identifier may name. Empty means any family in the corpus. */\n readonly families: readonly string[];\n /** Families that are never citations. */\n readonly ignoreFamilies: readonly string[];\n /** Files that log what was decided rather than deciding it. */\n readonly historyPatterns: readonly string[];\n /** Accepted-debt file to read. `null` means none was asked for. */\n readonly baseline: string | null;\n /** Where to write the current findings as accepted debt. */\n readonly recordBaseline: string | null;\n /**\n * Fail when the baseline allows something that no longer happens.\n *\n * The other side of the ratchet, and off by default for the reason ADR-0012\n * gives: failing a build because somebody fixed something is a strange way to\n * encourage them. A team that has decided its debt only goes one way asks for\n * it explicitly, and then a dead exemption cannot outlive the defect.\n */\n readonly ratchet: boolean;\n /** Skip the repository configuration file entirely. */\n readonly noConfig: boolean;\n readonly format: 'human' | 'json' | 'sarif' | 'markdown';\n readonly graphFormat: GraphFormat;\n readonly severities: Partial<Record<AnyRuleId, Severity>>;\n readonly color: boolean | null;\n readonly ascii: boolean | null;\n readonly verbose: boolean;\n readonly documentsOnly: boolean;\n readonly max: number;\n readonly maxWarnings: number;\n /** Raise every warning to an error. Explicit `--rule` overrides still win. */\n readonly strict: boolean;\n readonly selector: string | null;\n readonly help: boolean;\n readonly version: boolean;\n}\n\nexport interface CliIO {\n readonly argv?: readonly string[] | undefined;\n readonly cwd?: string | undefined;\n readonly stdout?: ((text: string) => void) | undefined;\n readonly stderr?: ((text: string) => void) | undefined;\n readonly env?: Readonly<Record<string, string | undefined>> | undefined;\n readonly isTTY?: boolean | undefined;\n}\n\nexport 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. Missing file = accept nothing.\n --record-baseline <f> Write today's findings to this file as accepted debt,\n and exit 0 without judging them.\n --ratchet Also fail when a baseline entry no longer occurs, so\n a paid-off exemption cannot outlive the defect.\n --no-config Ignore .spec-graph.json and the package.json key.\n --format <fmt> human, json, sarif or markdown. sarif is the\n interchange format GitHub code scanning and editors\n already read, for check; markdown is a table for a\n pull-request comment or $GITHUB_STEP_SUMMARY, for check\n or diff (default: human)\n --graph-format <fmt> dot, mermaid or json (default: dot)\n --documents-only Leave items out of the exported graph\n --rule <id>=<severity> Override one rule: error, warn, info or off. A project\n rule is named in full: project:no-draft-dependency.\n Repeatable.\n --max <n> Show at most n findings (0 = no limit)\n --max-warnings <n> Fail when warnings exceed n (default: no limit)\n --strict Raise every warning to an error. An explicit --rule\n still wins, so --strict --rule x=warn exempts x.\n --color / --no-color Force colour on or off\n --ascii Use ASCII glyphs only\n --verbose Include parse problems, per-file detail, and every\n reference --ignore-ref or --ignore-family silenced\n -h, --help Show this help\n -v, --version Show the version\n\nSELECTORS\n A selector is a path through the graph.\n\n item[openness=open] -delegates-to-> document[phase=retired]\n document[phase=active] -assumes-> document[phase=retired]\n document =supersedes=> document\n *[path^=docs/adr] -contains-> item[state=accepted-debt]\n\n Node types: document, item, * (any)\n Attributes: id, kind, title, path, file, line, phase, status, receptivity,\n alias, document, state (or disposition), openness, section,\n text, body, evidence, conflicted, fm.<front-matter-key>\n Operators: = != ^= $= *= ~= and [attr] for \"is present\"\n ~= is a regular expression, matched by an automaton that cannot\n backtrack: linear in the subject, whatever the pattern. It reads\n the usual syntax minus backreferences and lookaround, which are\n not regular - both are refused when the selector is read, with\n the character pointed at\n Relations: -kind-> <-kind- =kind=> <=kind= (= forms are transitive)\n\nEXIT CODES\n 0 clean\n 1 findings at error severity (or over --max-warnings)\n 2 the tool could not run\n\nCONFIGURATION\n Anything repeated on every run belongs in the repository rather than in the\n command. spec-graph reads the first of these that exists:\n\n .spec-graph.json, spec-graph.config.json, or a \"spec-graph\" key in\n package.json\n\n { \"patterns\": [\"docs/**/*.md\"],\n \"ignoreReferences\": [\"trap *\"],\n \"ignoreFamilies\": [\"RFC\"],\n \"historyPatterns\": [\"**/JOURNAL_*.md\", \"archive/**\"],\n \"baseline\": \".spec-graph-baseline.json\",\n \"severities\": { \"self-reference\": \"off\" },\n \"strict\": true }\n\n A flag always wins over the file, and list flags add to it rather than\n replacing it.\n\nPROJECT RULES\n A convention spec-graph never anticipated is a selector plus a sentence, and\n belongs in the same file. Every rule declared here runs beside the built-ins\n and is reported, baselined, escalated by --strict and silenced by --rule in\n exactly the same way.\n\n { \"rules\": {\n \"no-draft-dependency\": {\n \"query\": \"document[phase=active] -depends-on-> document[phase=draft]\",\n \"message\": \"{0} depends on {1}, which is still a draft\",\n \"hint\": \"wait for {1} to be accepted, or drop it from {0.path}\",\n \"severity\": \"error\" } } }\n\n query One selector, or a list of them read as a union.\n message The headline. {0} is the first node on the path, {1} the next;\n {1.phase} and {0.fm.owner} read any selector attribute.\n hint The next action. Optional, and templated the same way.\n severity error, warn, info or off. Defaults to warn - a rule a team has\n just written has not yet earned the right to fail their build.\n\n The id is the name with project: in front, which is why it can never collide\n with a built-in. A selector that does not parse, or a {2} the query can never\n reach, is reported when the file is read rather than found missing later.\n\n To see what one matches without copying its selector out of the file:\n\n spec-graph query project:no-draft-dependency --verbose\n\nADOPTING THIS ON AN OLD REPOSITORY\n Record what is already wrong, then report only what happens next:\n\n spec-graph check --record-baseline .spec-graph-baseline.json\n git add .spec-graph-baseline.json\n\n The file is keyed by specification and citation, not by line number, so it\n survives edits, moves and renames. Findings that stop occurring are reported\n so the file can be tightened; nothing new gets in.\n\nEXAMPLES\n spec-graph \"docs/**/*.md\"\n spec-graph check --rule self-reference=off --format json\n spec-graph query 'item[openness=open] -delegates-to-> document[phase=retired]'\n spec-graph check --ignore-ref \"trap *\" # [[trap 55]] tags a concept, not a file\n spec-graph graph --documents-only --graph-format mermaid > graph.mmd\n spec-graph check --history \"**/JOURNAL_*.md\" # a log is not a specification\n spec-graph check --baseline .spec-graph-baseline.json\n spec-graph rules --explain # including this repository's own\n spec-graph rules ghost-handover --explain # and why it exists\n spec-graph check --rule project:no-draft-dependency=off\n spec-graph query project:no-draft-dependency # what does that rule match?\n spec-graph check --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n spec-graph diff base.json head.json --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n`;\n\nconst SEVERITIES: readonly Severity[] = ['error', 'warn', 'info', 'off'];\n\n/** Parses argv into options. Throws {@link UsageError} on anything malformed. */\nexport function parseArgs(argv: readonly string[], cwd: string): CliOptions {\n const args = [...argv];\n let command: Command = 'check';\n\n if (args.length > 0 && !((args[0] as string).startsWith('-'))) {\n const first = args[0] as string;\n if (first === 'check' || first === 'query' || first === 'graph' || first === 'rules' || first === 'diff') {\n command = first;\n args.shift();\n }\n }\n\n const patterns: string[] = [];\n const ignore: string[] = [];\n const ignoreReferences: string[] = [];\n const families: string[] = [];\n const ignoreFamilies: string[] = [];\n const historyPatterns: string[] = [];\n let noConfig = false;\n const severities: Partial<Record<AnyRuleId, Severity>> = {};\n let root = cwd;\n let rootExplicit = false;\n let format: 'human' | 'json' | 'sarif' | 'markdown' = 'human';\n let graphFormat: GraphFormat = 'dot';\n let color: boolean | null = null;\n let ascii: boolean | null = null;\n let verbose = false;\n let documentsOnly = false;\n let max = 0;\n let maxWarnings = -1;\n let strict = false;\n let baseline: string | null = null;\n let ratchet = false;\n let recordBaseline: string | null = null;\n let selector: string | null = null;\n let help = false;\n let version = false;\n\n const next = (flag: string, index: number): string => {\n const value = args[index + 1];\n if (value === undefined) throw new UsageError(`${flag} needs a value`);\n return value;\n };\n\n for (let i = 0; i < args.length; i += 1) {\n const arg = args[i] as string;\n\n if (arg === '--') {\n patterns.push(...args.slice(i + 1));\n break;\n }\n\n if (!arg.startsWith('-')) {\n if (command === 'query' && selector === null) selector = arg;\n else patterns.push(arg);\n continue;\n }\n\n switch (arg) {\n case '-h':\n case '--help':\n help = true;\n break;\n case '-v':\n case '--version':\n version = true;\n break;\n case '--verbose':\n verbose = true;\n break;\n case '--color':\n color = true;\n break;\n case '--no-color':\n color = false;\n break;\n case '--ascii':\n ascii = true;\n break;\n case '--documents-only':\n documentsOnly = true;\n break;\n case '--strict':\n strict = true;\n break;\n case '--ratchet':\n ratchet = true;\n break;\n case '--explain':\n verbose = true;\n break;\n case '--root':\n root = next(arg, i);\n rootExplicit = true;\n i += 1;\n break;\n case '--ignore':\n ignore.push(next(arg, i));\n i += 1;\n break;\n case '--ignore-ref':\n ignoreReferences.push(next(arg, i));\n i += 1;\n break;\n case '--family':\n families.push(next(arg, i));\n i += 1;\n break;\n case '--ignore-family':\n ignoreFamilies.push(next(arg, i));\n i += 1;\n break;\n case '--history':\n historyPatterns.push(next(arg, i));\n i += 1;\n break;\n case '--baseline':\n baseline = next(arg, i);\n i += 1;\n break;\n case '--record-baseline':\n recordBaseline = next(arg, i);\n i += 1;\n break;\n case '--no-config':\n noConfig = true;\n break;\n case '--format': {\n const value = next(arg, i);\n if (value !== 'human' && value !== 'json' && value !== 'sarif' && value !== 'markdown') {\n throw new UsageError(`--format must be human, json, sarif or markdown, got \"${value}\"`);\n }\n format = value;\n i += 1;\n break;\n }\n case '--graph-format': {\n const value = next(arg, i);\n if (value !== 'dot' && value !== 'mermaid' && value !== 'json') {\n throw new UsageError(`--graph-format must be dot, mermaid or json, got \"${value}\"`);\n }\n graphFormat = value;\n i += 1;\n break;\n }\n case '--rule': {\n const value = next(arg, i);\n const equals = value.indexOf('=');\n if (equals === -1) throw new UsageError(`--rule expects <id>=<severity>, got \"${value}\"`);\n const id = value.slice(0, equals) as AnyRuleId;\n const level = value.slice(equals + 1) as Severity;\n // A project rule is taken on its namespace here and checked for real\n // once the configuration defining it has been read: arguments are\n // parsed before any file is opened, and a flag has to be usable on that\n // first pass whatever the repository turns out to declare.\n if (!RULE_IDS.includes(id as RuleId) && !isProjectRule(id)) {\n throw new UsageError(`unknown rule \"${id}\"\\n known rules: ${RULE_IDS.join(', ')}`);\n }\n if (!SEVERITIES.includes(level)) {\n throw new UsageError(`unknown severity \"${level}\", expected one of: ${SEVERITIES.join(', ')}`);\n }\n severities[id] = level;\n i += 1;\n break;\n }\n case '--max': {\n max = parseCount(arg, next(arg, i));\n i += 1;\n break;\n }\n case '--max-warnings': {\n maxWarnings = parseCount(arg, next(arg, i));\n i += 1;\n break;\n }\n default:\n throw new UsageError(`unknown option \"${arg}\"\\n run \"spec-graph --help\" to see the available options`);\n }\n }\n\n // SARIF is a report about findings, and only `check` produces those. Falling\n // back to JSON would hand a pipeline something its uploader rejects with a\n // message about a schema rather than about the command that was run. Markdown\n // is for a pull request, which reads a check or a diff.\n if (format === 'sarif' && command !== 'check' && !help && !version) {\n throw new UsageError(`--format sarif reports findings, so it belongs to check, not to ${command}`);\n }\n if (format === 'markdown' && command !== 'check' && command !== 'diff' && !help && !version) {\n throw new UsageError(`--format markdown is a report for a pull request, so it belongs to check or diff, not to ${command}`);\n }\n\n if (command === 'diff' && patterns.length !== 2 && !help && !version) {\n throw new UsageError(\n 'diff compares two graph exports, for example:\\n spec-graph diff base.json head.json\\n (make each with spec-graph graph --graph-format json)',\n );\n }\n\n if (command === 'query' && selector === null && !help && !version) {\n throw new UsageError('query needs a selector, for example:\\n spec-graph query \\'item[openness=open]\\'');\n }\n\n return {\n command,\n patterns,\n root,\n rootExplicit,\n ignore,\n ignoreReferences,\n families,\n ignoreFamilies,\n historyPatterns,\n baseline,\n ratchet,\n recordBaseline,\n noConfig,\n format,\n graphFormat,\n severities,\n color,\n ascii,\n verbose,\n documentsOnly,\n max,\n maxWarnings,\n strict,\n selector,\n help,\n version,\n };\n}\n\nfunction count(n: number, word: string, many?: string): string {\n return `${n} ${n === 1 ? word : (many ?? `${word}s`)}`;\n}\n\n/**\n * Reads a baseline file.\n *\n * A missing file is not an error: `--baseline` against a repository that has\n * not recorded one yet should report everything, which is exactly what an empty\n * baseline does.\n */\nasync function readBaseline(path: string, source: string): Promise<ReturnType<typeof parseBaseline>> {\n let raw: string;\n try {\n const { readFile } = await import('node:fs/promises');\n raw = await readFile(path, 'utf8');\n } catch {\n return { baseline: EMPTY_BASELINE, problems: [] };\n }\n return parseBaseline(raw, source);\n}\n\nfunction parseCount(flag: string, value: string): number {\n const parsed = Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed < 0 || String(parsed) !== value.trim()) {\n throw new UsageError(`${flag} expects a non-negative whole number, got \"${value}\"`);\n }\n return parsed;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Entry point */\n/* -------------------------------------------------------------------------- */\n\n/** Runs the CLI and returns the process exit code. Never throws. */\nexport async function main(io: CliIO = {}): Promise<number> {\n const argv = io.argv ?? process.argv.slice(2);\n const cwd = io.cwd ?? process.cwd();\n const out = io.stdout ?? ((text) => process.stdout.write(text));\n const err = io.stderr ?? ((text) => process.stderr.write(text));\n const env = io.env ?? process.env;\n\n let options: CliOptions;\n try {\n options = parseArgs(argv, cwd);\n } catch (error) {\n if (error instanceof UsageError) {\n err(`spec-graph: ${error.message}\\n`);\n return EXIT_ERROR;\n }\n throw error;\n }\n\n if (options.help) {\n out(HELP);\n return EXIT_OK;\n }\n if (options.version) {\n out(`${await readVersion()}\\n`);\n return EXIT_OK;\n }\n\n // A diff reads two files and nothing else: no configuration and no corpus, so\n // it can compare exports from a checkout it is not running in.\n if (options.command === 'diff') return runDiff(options.patterns as readonly [string, string], cwd, options.format, out, err);\n\n const color = options.color ?? shouldUseColor({ isTTY: io.isTTY, env });\n const ascii = options.ascii ?? shouldUseAscii({ env });\n\n // Configuration is what is true of the repository; a flag is somebody\n // overriding it for one run. So a flag always wins, and lists add rather than\n // replace - a `--ignore-ref` on the command line is one more exclusion, not a\n // decision to throw away the ones the repository already declared.\n //\n // Read before `rules` prints anything, so that command lists the conventions\n // this repository will actually check rather than the ones spec-graph ships.\n const loaded = options.noConfig\n ? { config: {} as SpecGraphConfig, source: null, problems: [], root: options.root }\n : options.rootExplicit\n ? { ...loadConfig(options.root), root: options.root }\n : discoverConfig(options.root);\n for (const problem of loaded.problems) err(`spec-graph: ${problem}\\n`);\n const file = loaded.config;\n const projectRules = file.rules ?? [];\n const root = loaded.root;\n // How far the root moved up. Everything a repository declares about itself is\n // relative to the root; everything typed on the command line is relative to\n // where it was typed, and this is what keeps those two readings apart.\n const here = below(root, options.root);\n\n // A `--rule` naming a project rule that does not exist is a flag that\n // silently does nothing, and nothing in the output would distinguish that\n // from a rule that ran and found none.\n for (const id of Object.keys(options.severities)) {\n if (!isProjectRule(id as AnyRuleId) || projectRules.some((rule) => rule.id === id)) continue;\n err(`spec-graph: unknown rule \"${id}\"\\n ${knownProjectRules(projectRules)}\\n`);\n return EXIT_ERROR;\n }\n\n if (options.command === 'rules') {\n // The one positional argument `rules` can take is a rule id: this command\n // reads no files, so a pattern here would be a word with nowhere to go.\n const wanted = options.patterns[0];\n if (wanted !== undefined && !RULE_IDS.includes(wanted as RuleId) && !projectRules.some((rule) => rule.id === wanted)) {\n err(\n `spec-graph: unknown rule \"${wanted}\"\\n known rules: ${RULE_IDS.join(', ')}\\n ${knownProjectRules(projectRules)}\\n`,\n );\n return EXIT_ERROR;\n }\n out(renderRules(options.verbose, projectRules, wanted ?? null, loaded.source));\n return EXIT_OK;\n }\n\n const patterns =\n options.patterns.length > 0 ? options.patterns.map((pattern) => anchor(here, pattern)) : (file.patterns ?? DEFAULT_PATTERNS);\n const severityOverrides = { ...(file.severities ?? {}), ...options.severities };\n const { severities, escalated } = resolveStrict(\n severityOverrides,\n options.strict || (file.strict ?? false),\n projectRules,\n );\n\n const analyseOptions: AnalyseOptions = {\n root,\n patterns,\n // A bare name prunes a directory of that name at any depth, the way a\n // .gitignore line does, so it means the same thing wherever it was typed.\n // A path or a glob is matched against the repository-relative path, and\n // leaving that one alone would make it silently match nothing.\n ignore: [...(file.ignore ?? []), ...options.ignore.map((pattern) => anchorPath(here, pattern))],\n ignoreReferences: [...(file.ignoreReferences ?? []), ...options.ignoreReferences],\n families: [...(file.families ?? []), ...options.families],\n ignoreFamilies: [...(file.ignoreFamilies ?? []), ...options.ignoreFamilies],\n historyPatterns: [...(file.historyPatterns ?? []), ...options.historyPatterns.map((pattern) => anchorPath(here, pattern))],\n severities,\n projectRules,\n ...(file.maxRelated !== undefined ? { maxRelated: file.maxRelated } : {}),\n };\n\n // Named the way the reader would have to type it, because a discovered\n // configuration is often not the one in front of them.\n if (options.verbose && loaded.source !== null) {\n const depth = here === '' ? 0 : here.split('/').length;\n out(`configuration: ${'../'.repeat(depth)}${loaded.source}\\n`);\n }\n\n let result;\n try {\n result = await analyse(analyseOptions);\n } catch (error) {\n err(`spec-graph: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n\n if (result.files.length === 0) {\n err(\n `spec-graph: no specifications matched ${\n patterns.map((p) => `\"${p}\"`).join(', ')\n }\\n looked under ${root}\\n`,\n );\n return EXIT_ERROR;\n }\n\n switch (options.command) {\n case 'graph':\n out(\n formatGraph(result.graph, options.graphFormat, {\n documentsOnly: options.documentsOnly,\n generator: { name: 'spec-graph', version: await readVersion() },\n }),\n );\n return EXIT_OK;\n\n case 'query': {\n const selector = options.selector as string;\n let queries: readonly QuerySpec[];\n\n // A registered rule is asked for by name, because by the time this runs\n // it is already compiled and the alternative is copying its selector out\n // of the configuration file by hand. The namespace cannot collide with\n // the grammar: no selector begins with a word and a colon. See ADR-0016.\n if (isProjectRule(selector as AnyRuleId)) {\n const rule = projectRules.find((candidate) => candidate.id === selector);\n if (rule === undefined) {\n err(`spec-graph: unknown rule \"${selector}\"\\n ${knownProjectRules(projectRules)}\\n`);\n return EXIT_ERROR;\n }\n queries = rule.queries;\n if (options.verbose) for (const source of rule.sources) out(`${rule.id}: ${source}\\n`);\n } else {\n try {\n queries = [parseQuery(selector)];\n } catch (error) {\n if (error instanceof QueryError) {\n err(renderQueryError(selector, error));\n return EXIT_ERROR;\n }\n throw error;\n }\n }\n\n const matches = union(result.graph, queries);\n out(renderMatches(matches, options.format === 'json' ? 'json' : 'human', result.graph.nodes.size));\n return matches.length > 0 ? EXIT_OK : EXIT_FAILED;\n }\n\n default: {\n // Recording is not checking. It writes down what is wrong today so that\n // tomorrow can be compared against it, and says nothing about whether\n // today is acceptable - so it reports what it wrote and exits clean.\n if (options.recordBaseline !== null) {\n const text = formatBaseline(result.graph, result.diagnostics);\n try {\n const { writeFile } = await import('node:fs/promises');\n await writeFile(underRoot(root, anchor(here, options.recordBaseline)), text, 'utf8');\n } catch (error) {\n err(`spec-graph: cannot write ${options.recordBaseline}: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n const entries = (JSON.parse(text) as Baseline).findings.length;\n out(\n `recorded ${count(result.diagnostics.length, 'finding')} as ${count(entries, 'entry', 'entries')} in ${options.recordBaseline}\\n`,\n );\n return EXIT_OK;\n }\n\n const source = options.baseline === null ? (file.baseline ?? null) : anchor(here, options.baseline);\n const ratchet = options.ratchet || file.ratchet === true;\n let reported = result;\n let note:\n | {\n source: string;\n suppressed: number;\n stale: number;\n ratchet: boolean;\n entries: readonly StaleEntry[];\n }\n | undefined;\n if (source !== null) {\n const held = await readBaseline(underRoot(root, source), source);\n for (const problem of held.problems) err(`spec-graph: ${problem}\\n`);\n const outcome = applyBaseline(result.graph, result.diagnostics, held.baseline);\n reported = withDiagnostics(result, outcome.kept);\n note = {\n source,\n suppressed: outcome.suppressed,\n stale: outcome.stale.length,\n ratchet,\n entries: outcome.stale,\n };\n // Listed rather than counted when the run turns on them: a number is\n // enough to know the file has slack, and not enough to strike it. On\n // stdout only for a human - the structured formats carry the same rows\n // inside the document, where a stray line is the difference between\n // parsing and not.\n if (options.format === 'human' && (options.verbose || (ratchet && outcome.stale.length > 0))) {\n for (const entry of outcome.stale) {\n const subject = entry.subject === '' ? '' : ` \"${entry.subject}\"`;\n const why = entry.reason === 'gone' ? ` - ${entry.document} is not in this corpus` : '';\n out(` ${entry.reason}: ${entry.rule} ${entry.document}${subject}${why}\\n`);\n }\n }\n }\n const looseBaseline = note !== undefined && note.ratchet && note.stale > 0;\n\n const baselineNote = note === undefined ? {} : { baseline: note };\n const reporterOptions = { verbose: options.verbose, max: options.max, escalated, ...baselineNote };\n out(\n options.format === 'sarif'\n ? formatSarif(reported, reported.graph, { version: await readVersion(), escalated, projectRules })\n : options.format === 'json'\n ? formatJson(reported, { escalated, ...baselineNote })\n : options.format === 'markdown'\n ? formatMarkdown(reported, reporterOptions)\n : `${formatReport(reported, { color, ascii, ...reporterOptions })}\\n`,\n );\n if (!reported.ok || looseBaseline) return EXIT_FAILED;\n if (options.maxWarnings >= 0 && reported.summary.warnings > options.maxWarnings) return EXIT_FAILED;\n return EXIT_OK;\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Rendering */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Where the working directory sits below the root, or `''` when it is the root.\n *\n * Discovery only ever walks upward, so the root is always a prefix of the\n * directory the command was typed in, and this is a slice rather than path\n * arithmetic.\n */\nfunction below(root: string, from: string): string {\n const start = toPosix(from).replace(/[/]+$/, '');\n return start.startsWith(`${root}/`) ? start.slice(root.length + 1) : '';\n}\n\n/**\n * Re-anchors a path typed in `prefix` so it reads from the root.\n *\n * An absolute path is left alone: it was not relative to anywhere, so moving\n * the root cannot change what it means.\n */\nfunction anchor(prefix: string, value: string): string {\n if (prefix === '' || ABSOLUTE.test(value)) return value;\n return value.startsWith('!') ? `!${prefix}/${value.slice(1)}` : `${prefix}/${value}`;\n}\n\nconst ABSOLUTE = /^(?:[/\\\\]|[A-Za-z]:)/;\n\n/** The same, for an ignore, where a bare name is a directory at any depth. */\nfunction anchorPath(prefix: string, pattern: string): string {\n return isGlob(pattern) || pattern.includes('/') ? anchor(prefix, pattern) : pattern;\n}\n\nfunction knownProjectRules(projectRules: readonly ProjectRule[]): string {\n const known = projectRules.map((rule) => rule.id);\n return known.length > 0 ? `project rules here: ${known.join(', ')}` : 'this repository defines no project rules';\n}\n\n/**\n * Runs a list of selectors as one result set.\n *\n * A project rule is a union of its selectors, and a path that two of them both\n * find is one path - the same dedupe `projectFindings` does, so that asking\n * this command what a rule matches answers with the set `check` reports on.\n */\nfunction union(graph: AnalysisResult['graph'], queries: readonly QuerySpec[]): Match[] {\n if (queries.length === 1) return execute(graph, queries[0] as QuerySpec);\n const seen = new Set<string>();\n const matches: Match[] = [];\n for (const spec of queries) {\n for (const match of execute(graph, spec)) {\n const key = match.nodes.map((node) => node.id).join('>');\n if (seen.has(key)) continue;\n seen.add(key);\n matches.push(match);\n }\n }\n return matches;\n}\n\ninterface RuleRow {\n readonly id: string;\n readonly severity: Severity;\n readonly description: string;\n /** The selector, where the rule is one. */\n readonly query: string | undefined;\n /** Where the reasoning is: an ADR, or the file that declared the rule. */\n readonly decided: string;\n}\n\nfunction renderRules(\n explain: boolean,\n projectRules: readonly ProjectRule[] = [],\n only: string | null = null,\n configSource: string | null = null,\n): string {\n // A project rule is described by the selector it is, because that is what it\n // is - its message is a template, and a template is not a description.\n const rows: RuleRow[] = [\n ...RULE_IDS.map((id): RuleRow => ({\n id,\n severity: DEFAULT_SEVERITIES[id],\n description: RULE_DESCRIPTIONS[id],\n query: RULE_QUERIES[id],\n decided: RULE_DECISIONS[id],\n })),\n ...projectRules.map((rule): RuleRow => ({\n id: rule.id,\n severity: rule.severity,\n description: rule.sources[0] as string,\n query: rule.sources.slice(1).join(' | ') || undefined,\n decided: configSource === null ? `rules.${rule.name}` : `${configSource}: rules.${rule.name}`,\n })),\n ];\n\n const shown = only === null ? rows : rows.filter((row) => row.id === only);\n const width = Math.max(...shown.map((row) => row.id.length));\n const lines: string[] = [];\n for (const row of shown) {\n lines.push(`${row.id.padEnd(width)} ${row.severity.padEnd(5)} ${row.description}`);\n if (!explain) continue;\n if (row.query) lines.push(`${' '.repeat(width)} ${row.query}`);\n // Where the decision is written down, rather than a second copy of it. A\n // reader who has just been told their document is inconsistent is entitled\n // to know who decided that and why, and the ADR is the answer.\n lines.push(`${' '.repeat(width)} ${row.decided}`);\n }\n return `${lines.join('\\n')}\\n`;\n}\n\nfunction renderMatches(matches: readonly Match[], format: 'human' | 'json', total: number): string {\n if (format === 'json') {\n return `${JSON.stringify(\n {\n version: 1,\n count: matches.length,\n matches: matches.map((match) => ({\n nodes: match.nodes.map((node) => nodeSummary(node)),\n edges: match.edges.map((edge) => ({\n kind: edge.kind,\n from: edge.from,\n to: edge.to,\n file: edge.declaredAt.file,\n line: edge.declaredAt.span.start.line,\n })),\n })),\n },\n null,\n 2,\n )}\\n`;\n }\n\n if (matches.length === 0) return `no matches (searched ${total} nodes)\\n`;\n\n const lines: string[] = [];\n for (const match of matches) {\n const head = match.nodes[0] as SpecNode;\n const tail = match.nodes[match.nodes.length - 1] as SpecNode;\n lines.push(renderMatch(match));\n lines.push(` ${formatRef(head.at)}${match.nodes.length > 1 ? ` ${formatRef(tail.at)}` : ''}`);\n }\n lines.push(`${matches.length} ${matches.length === 1 ? 'match' : 'matches'}`);\n return `${lines.join('\\n')}\\n`;\n}\n\nfunction nodeSummary(node: SpecNode): Record<string, unknown> {\n return {\n id: node.id,\n kind: node.kind,\n title: node.title,\n file: node.at.file,\n line: node.at.span.start.line,\n ...(node.kind === 'document' ? { phase: node.phase } : { state: node.disposition, openness: node.openness }),\n };\n}\n\n/** Points at the exact character of a selector that failed to parse. */\nfunction renderQueryError(selector: string, error: QueryError): string {\n const caret = `${' '.repeat(Math.max(0, error.offset))}^`;\n return `spec-graph: ${error.message}\\n ${selector}\\n ${caret}\\n`;\n}\n\n/**\n * Compares two exports.\n *\n * Exit 0 whether anything changed or not: a diff describes, and `check` gates. A\n * diff that failed on a removed relation would fail every legitimate\n * supersession (ADR-0020).\n */\nasync function runDiff(\n paths: readonly [string, string],\n cwd: string,\n format: CliOptions['format'],\n out: (text: string) => void,\n err: (text: string) => void,\n): Promise<number> {\n const { readFile } = await import('node:fs/promises');\n const { resolve } = await import('node:path');\n const sides: GraphExport[] = [];\n for (const path of paths) {\n let raw: string;\n try {\n raw = await readFile(resolve(cwd, path), 'utf8');\n } catch (error) {\n err(`spec-graph: cannot read ${path}: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n try {\n sides.push(parseGraphExport(raw, path));\n } catch (error) {\n if (!(error instanceof DiffInputError)) throw error;\n err(`spec-graph: ${error.message}\\n`);\n return EXIT_ERROR;\n }\n }\n const [before, after] = sides as [GraphExport, GraphExport];\n const diff = diffExports(before, after);\n out(format === 'json' ? formatDiffJson(diff) : format === 'markdown' ? formatDiffMarkdown(diff) : formatDiffText(diff));\n return EXIT_OK;\n}\n\nasync function readVersion(): Promise<string> {\n try {\n const { readFile } = await import('node:fs/promises');\n const url = new URL('../package.json', import.meta.url);\n const parsed: unknown = JSON.parse(await readFile(url, 'utf8'));\n if (typeof parsed === 'object' && parsed !== null && 'version' in parsed) {\n return String((parsed as { version: unknown }).version);\n }\n } catch {\n // Fall through to the placeholder rather than failing `--version`.\n }\n return '0.0.0';\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The command line.\n *\n * Five verbs, because there are five questions worth asking of a specification\n * graph: is it consistent (`check`), what does it contain (`query`), what does\n * it look like (`graph`), what will you check for me (`rules`), and what did a\n * change do to it (`diff`).\n *\n * Exit codes are the contract with CI: `0` clean, `1` findings, `2` the tool\n * itself could not run. A usage mistake never masquerades as a passing build.\n */\n\nimport {\n applyBaseline,\n EMPTY_BASELINE,\n formatBaseline,\n parseBaseline,\n type Baseline,\n type StaleEntry,\n} from './baseline.js';\nimport { discoverConfig, loadConfig, type SpecGraphConfig } from './config.js';\nimport {\n DiffInputError,\n diffExports,\n formatDiffJson,\n formatDiffMarkdown,\n formatDiffText,\n parseGraphExport,\n type GraphExport,\n} from './diff.js';\nimport { isGlob, underRoot } from './glob.js';\nimport { analyse, DEFAULT_PATTERNS, withDiagnostics, type AnalyseOptions, type AnalysisResult } from './runner.js';\nimport {\n formatGraph,\n formatJson,\n formatMarkdown,\n formatReport,\n formatSarif,\n shouldUseAscii,\n shouldUseColor,\n type GraphFormat,\n} from './report.js';\nimport type { ProjectRule } from './project-rules.js';\nimport {\n DEFAULT_SEVERITIES,\n resolveStrict,\n RULE_DECISIONS,\n RULE_DESCRIPTIONS,\n RULE_IDS,\n RULE_QUERIES,\n} from './rules.js';\nimport { isAbsolutePath, toPosix } from './paths.js';\nimport { formatRef } from './source.js';\nimport { execute, parseQuery, QueryError, renderMatch, type Match, type QuerySpec } from './select.js';\nimport { isProjectRule, type AnyRuleId, type RuleId, type Severity, type SpecNode } from './types.js';\n\nexport const EXIT_OK = 0;\nexport const EXIT_FAILED = 1;\nexport const EXIT_ERROR = 2;\n\nexport class UsageError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UsageError';\n }\n}\n\nexport type Command = 'check' | 'query' | 'graph' | 'rules' | 'diff';\n\nexport interface CliOptions {\n readonly command: Command;\n readonly patterns: readonly string[];\n readonly root: string;\n /**\n * Whether `--root` was given.\n *\n * When it was, the caller has named the root and configuration is read from\n * exactly there. When it was not, the root is discovered - which is a\n * different question from what the root currently is. See ADR-0018.\n */\n readonly rootExplicit: boolean;\n readonly ignore: readonly string[];\n /** Reference targets to leave unreported when they do not resolve. */\n readonly ignoreReferences: readonly string[];\n /** Families a bare identifier may name. Empty means any family in the corpus. */\n readonly families: readonly string[];\n /** Families that are never citations. */\n readonly ignoreFamilies: readonly string[];\n /** Files that log what was decided rather than deciding it. */\n readonly historyPatterns: readonly string[];\n /** Accepted-debt file to read. `null` means none was asked for. */\n readonly baseline: string | null;\n /** Where to write the current findings as accepted debt. */\n readonly recordBaseline: string | null;\n /**\n * Fail when the baseline allows something that no longer happens.\n *\n * The other side of the ratchet, and off by default for the reason ADR-0012\n * gives: failing a build because somebody fixed something is a strange way to\n * encourage them. A team that has decided its debt only goes one way asks for\n * it explicitly, and then a dead exemption cannot outlive the defect.\n */\n readonly ratchet: boolean;\n /** Skip the repository configuration file entirely. */\n readonly noConfig: boolean;\n readonly format: 'human' | 'json' | 'sarif' | 'markdown';\n readonly graphFormat: GraphFormat;\n readonly severities: Partial<Record<AnyRuleId, Severity>>;\n readonly color: boolean | null;\n readonly ascii: boolean | null;\n readonly verbose: boolean;\n readonly documentsOnly: boolean;\n readonly max: number;\n readonly maxWarnings: number;\n /** Raise every warning to an error. Explicit `--rule` overrides still win. */\n readonly strict: boolean;\n readonly selector: string | null;\n readonly help: boolean;\n readonly version: boolean;\n}\n\nexport interface CliIO {\n readonly argv?: readonly string[] | undefined;\n readonly cwd?: string | undefined;\n readonly stdout?: ((text: string) => void) | undefined;\n readonly stderr?: ((text: string) => void) | undefined;\n readonly env?: Readonly<Record<string, string | undefined>> | undefined;\n readonly isTTY?: boolean | undefined;\n}\n\nexport const HELP = `spec-graph - turn Markdown specifications into a verifiable graph\n\nUSAGE\n spec-graph [check] [patterns...] [options]\n spec-graph query <selector|project:rule> [patterns...] [options]\n spec-graph graph [patterns...] [--graph-format dot|mermaid|json]\n spec-graph rules [rule-id] [--explain]\n spec-graph diff <before.json> <after.json> [--format human|json|markdown]\n\nCOMMANDS\n check Validate the specification graph. The default.\n query Run a selector - or a registered project rule, by its id - and\n print the matching paths.\n graph Export the graph for Graphviz, Mermaid, or another tool.\n rules List the diagnostics that will run, built in and project. Name one\n to see only that one; --explain adds its selector and the ADR that\n decided it.\n diff Compare two graph exports: documents added, removed, moved or\n accepted; relations added or removed; and obligations resolved or\n reopened where they can be told apart. Make both exports with the\n same spec-graph. Exits 0 whether anything changed or not.\n\nOPTIONS\n --root <dir> Directory the patterns resolve against. Without it,\n .spec-graph.json is looked for in the working\n directory and then upward as far as the repository,\n and the directory holding it becomes the root - so a\n run from a subdirectory reports what a run from the\n top reports. Paths typed on the command line stay\n relative to where they were typed.\n --ignore <glob> Skip paths. Repeatable.\n --ignore-ref <glob> Do not report these reference targets when they fail\n to resolve, for repositories where [[...]] tags a\n concept rather than naming a file. Repeatable.\n Suppresses findings only, never edges.\n --family <name> Families a bare identifier in prose may name. When\n given, everything else stays prose. Repeatable.\n --ignore-family <name> Families that are never citations - RFC when the repo\n cites RFC 2119 and keeps its own RFCs. Repeatable.\n --history <glob> Files that log what was decided rather than deciding\n it - journals, changelogs, minutes. Their links are\n still checked; their obligations are not. Repeatable.\n --baseline <file> Accept the findings recorded in this file and report\n only what is new since. The file has to be there: a\n path that reads nothing accepts nothing and says so\n nowhere. A \"baseline\" in the configuration may name a\n file not recorded yet; a path typed here may not.\n --record-baseline <f> Write today's findings to this file as accepted debt,\n and exit 0 without judging them.\n --ratchet Also fail when a baseline entry no longer occurs, so\n a paid-off exemption cannot outlive the defect.\n --no-config Ignore .spec-graph.json and the package.json key.\n --format <fmt> human, json, sarif or markdown. sarif is the\n interchange format GitHub code scanning and editors\n already read, for check; markdown is a table for a\n pull-request comment or $GITHUB_STEP_SUMMARY, for check\n or diff (default: human)\n --graph-format <fmt> dot, mermaid or json (default: dot)\n --documents-only Leave items out of the exported graph\n --rule <id>=<severity> Override one rule: error, warn, info or off. A project\n rule is named in full: project:no-draft-dependency.\n Repeatable.\n --max <n> Show at most n findings (0 = no limit)\n --max-warnings <n> Fail when warnings exceed n (default: no limit)\n --strict Raise every warning to an error. An explicit --rule\n still wins, so --strict --rule x=warn exempts x.\n --color / --no-color Force colour on or off\n --ascii Use ASCII glyphs only\n --verbose Include parse problems, per-file detail, and every\n reference --ignore-ref or --ignore-family silenced\n -h, --help Show this help\n -v, --version Show the version\n\nSELECTORS\n A selector is a path through the graph.\n\n item[openness=open] -delegates-to-> document[phase=retired]\n document[phase=active] -assumes-> document[phase=retired]\n document =supersedes=> document\n *[path^=docs/adr] -contains-> item[state=accepted-debt]\n\n Node types: document, item, * (any)\n Attributes: id, kind, title, path, file, line, phase, status, receptivity,\n alias, document, state (or disposition), openness, section,\n text, body, evidence, conflicted, fm.<front-matter-key>\n Operators: = != ^= $= *= ~= and [attr] for \"is present\"\n ~= is a regular expression, matched by an automaton that cannot\n backtrack: linear in the subject, whatever the pattern. It reads\n the usual syntax minus backreferences and lookaround, which are\n not regular - both are refused when the selector is read, with\n the character pointed at\n Relations: -kind-> <-kind- =kind=> <=kind= (= forms are transitive)\n\nEXIT CODES\n 0 clean\n 1 findings at error severity (or over --max-warnings)\n 2 the tool could not run\n\nCONFIGURATION\n Anything repeated on every run belongs in the repository rather than in the\n command. spec-graph reads the first of these that exists:\n\n .spec-graph.json, spec-graph.config.json, or a \"spec-graph\" key in\n package.json\n\n { \"patterns\": [\"docs/**/*.md\"],\n \"ignoreReferences\": [\"trap *\"],\n \"ignoreFamilies\": [\"RFC\"],\n \"historyPatterns\": [\"**/JOURNAL_*.md\", \"archive/**\"],\n \"baseline\": \".spec-graph-baseline.json\",\n \"severities\": { \"self-reference\": \"off\" },\n \"strict\": true }\n\n A flag always wins over the file, and list flags add to it rather than\n replacing it.\n\n A problem in that file stops the run with exit 2, named on stderr: invalid\n JSON, an unknown key, a rule that does not compile. A configuration that did\n not load checks a different repository than the one configured, and would\n report that one as consistent. --no-config runs on defaults instead.\n\nPROJECT RULES\n A convention spec-graph never anticipated is a selector plus a sentence, and\n belongs in the same file. Every rule declared here runs beside the built-ins\n and is reported, baselined, escalated by --strict and silenced by --rule in\n exactly the same way.\n\n { \"rules\": {\n \"no-draft-dependency\": {\n \"query\": \"document[phase=active] -depends-on-> document[phase=draft]\",\n \"message\": \"{0} depends on {1}, which is still a draft\",\n \"hint\": \"wait for {1} to be accepted, or drop it from {0.path}\",\n \"severity\": \"error\" } } }\n\n query One selector, or a list of them read as a union.\n message The headline. {0} is the first node on the path, {1} the next;\n {1.phase} and {0.fm.owner} read any selector attribute.\n hint The next action. Optional, and templated the same way.\n severity error, warn, info or off. Defaults to warn - a rule a team has\n just written has not yet earned the right to fail their build.\n\n The id is the name with project: in front, which is why it can never collide\n with a built-in. A selector that does not parse, or a {2} the query can never\n reach, is reported when the file is read rather than found missing later.\n\n To see what one matches without copying its selector out of the file:\n\n spec-graph query project:no-draft-dependency --verbose\n\nADOPTING THIS ON AN OLD REPOSITORY\n Record what is already wrong, then report only what happens next:\n\n spec-graph check --record-baseline .spec-graph-baseline.json\n git add .spec-graph-baseline.json\n\n The file is keyed by specification and citation, not by line number, so it\n survives edits, moves and renames. Findings that stop occurring are reported\n so the file can be tightened; nothing new gets in.\n\nEXAMPLES\n spec-graph \"docs/**/*.md\"\n spec-graph check --rule self-reference=off --format json\n spec-graph query 'item[openness=open] -delegates-to-> document[phase=retired]'\n spec-graph check --ignore-ref \"trap *\" # [[trap 55]] tags a concept, not a file\n spec-graph graph --documents-only --graph-format mermaid > graph.mmd\n spec-graph check --history \"**/JOURNAL_*.md\" # a log is not a specification\n spec-graph check --baseline .spec-graph-baseline.json\n spec-graph rules --explain # including this repository's own\n spec-graph rules ghost-handover --explain # and why it exists\n spec-graph check --rule project:no-draft-dependency=off\n spec-graph query project:no-draft-dependency # what does that rule match?\n spec-graph check --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n spec-graph diff base.json head.json --format markdown >> \"$GITHUB_STEP_SUMMARY\"\n`;\n\nconst SEVERITIES: readonly Severity[] = ['error', 'warn', 'info', 'off'];\n\n/** Parses argv into options. Throws {@link UsageError} on anything malformed. */\nexport function parseArgs(argv: readonly string[], cwd: string): CliOptions {\n const args = [...argv];\n let command: Command = 'check';\n\n if (args.length > 0 && !((args[0] as string).startsWith('-'))) {\n const first = args[0] as string;\n if (first === 'check' || first === 'query' || first === 'graph' || first === 'rules' || first === 'diff') {\n command = first;\n args.shift();\n }\n }\n\n const patterns: string[] = [];\n const ignore: string[] = [];\n const ignoreReferences: string[] = [];\n const families: string[] = [];\n const ignoreFamilies: string[] = [];\n const historyPatterns: string[] = [];\n let noConfig = false;\n const severities: Partial<Record<AnyRuleId, Severity>> = {};\n let root = cwd;\n let rootExplicit = false;\n let format: 'human' | 'json' | 'sarif' | 'markdown' = 'human';\n let graphFormat: GraphFormat = 'dot';\n let color: boolean | null = null;\n let ascii: boolean | null = null;\n let verbose = false;\n let documentsOnly = false;\n let max = 0;\n let maxWarnings = -1;\n let strict = false;\n let baseline: string | null = null;\n let ratchet = false;\n let recordBaseline: string | null = null;\n let selector: string | null = null;\n let help = false;\n let version = false;\n\n const next = (flag: string, index: number): string => {\n const value = args[index + 1];\n if (value === undefined) throw new UsageError(`${flag} needs a value`);\n return value;\n };\n\n for (let i = 0; i < args.length; i += 1) {\n const arg = args[i] as string;\n\n if (arg === '--') {\n patterns.push(...args.slice(i + 1));\n break;\n }\n\n if (!arg.startsWith('-')) {\n if (command === 'query' && selector === null) selector = arg;\n else patterns.push(arg);\n continue;\n }\n\n switch (arg) {\n case '-h':\n case '--help':\n help = true;\n break;\n case '-v':\n case '--version':\n version = true;\n break;\n case '--verbose':\n verbose = true;\n break;\n case '--color':\n color = true;\n break;\n case '--no-color':\n color = false;\n break;\n case '--ascii':\n ascii = true;\n break;\n case '--documents-only':\n documentsOnly = true;\n break;\n case '--strict':\n strict = true;\n break;\n case '--ratchet':\n ratchet = true;\n break;\n case '--explain':\n verbose = true;\n break;\n case '--root':\n root = next(arg, i);\n rootExplicit = true;\n i += 1;\n break;\n case '--ignore':\n ignore.push(next(arg, i));\n i += 1;\n break;\n case '--ignore-ref':\n ignoreReferences.push(next(arg, i));\n i += 1;\n break;\n case '--family':\n families.push(next(arg, i));\n i += 1;\n break;\n case '--ignore-family':\n ignoreFamilies.push(next(arg, i));\n i += 1;\n break;\n case '--history':\n historyPatterns.push(next(arg, i));\n i += 1;\n break;\n case '--baseline':\n baseline = next(arg, i);\n i += 1;\n break;\n case '--record-baseline':\n recordBaseline = next(arg, i);\n i += 1;\n break;\n case '--no-config':\n noConfig = true;\n break;\n case '--format': {\n const value = next(arg, i);\n if (value !== 'human' && value !== 'json' && value !== 'sarif' && value !== 'markdown') {\n throw new UsageError(`--format must be human, json, sarif or markdown, got \"${value}\"`);\n }\n format = value;\n i += 1;\n break;\n }\n case '--graph-format': {\n const value = next(arg, i);\n if (value !== 'dot' && value !== 'mermaid' && value !== 'json') {\n throw new UsageError(`--graph-format must be dot, mermaid or json, got \"${value}\"`);\n }\n graphFormat = value;\n i += 1;\n break;\n }\n case '--rule': {\n const value = next(arg, i);\n const equals = value.indexOf('=');\n if (equals === -1) throw new UsageError(`--rule expects <id>=<severity>, got \"${value}\"`);\n const id = value.slice(0, equals) as AnyRuleId;\n const level = value.slice(equals + 1) as Severity;\n // A project rule is taken on its namespace here and checked for real\n // once the configuration defining it has been read: arguments are\n // parsed before any file is opened, and a flag has to be usable on that\n // first pass whatever the repository turns out to declare.\n if (!RULE_IDS.includes(id as RuleId) && !isProjectRule(id)) {\n throw new UsageError(`unknown rule \"${id}\"\\n known rules: ${RULE_IDS.join(', ')}`);\n }\n if (!SEVERITIES.includes(level)) {\n throw new UsageError(`unknown severity \"${level}\", expected one of: ${SEVERITIES.join(', ')}`);\n }\n severities[id] = level;\n i += 1;\n break;\n }\n case '--max': {\n max = parseCount(arg, next(arg, i));\n i += 1;\n break;\n }\n case '--max-warnings': {\n maxWarnings = parseCount(arg, next(arg, i));\n i += 1;\n break;\n }\n default:\n throw new UsageError(`unknown option \"${arg}\"\\n run \"spec-graph --help\" to see the available options`);\n }\n }\n\n // SARIF is a report about findings, and only `check` produces those. Falling\n // back to JSON would hand a pipeline something its uploader rejects with a\n // message about a schema rather than about the command that was run. Markdown\n // is for a pull request, which reads a check or a diff.\n if (format === 'sarif' && command !== 'check' && !help && !version) {\n throw new UsageError(`--format sarif reports findings, so it belongs to check, not to ${command}`);\n }\n if (format === 'markdown' && command !== 'check' && command !== 'diff' && !help && !version) {\n throw new UsageError(`--format markdown is a report for a pull request, so it belongs to check or diff, not to ${command}`);\n }\n\n if (command === 'diff' && patterns.length !== 2 && !help && !version) {\n throw new UsageError(\n 'diff compares two graph exports, for example:\\n spec-graph diff base.json head.json\\n (make each with spec-graph graph --graph-format json)',\n );\n }\n\n if (command === 'query' && selector === null && !help && !version) {\n throw new UsageError('query needs a selector, for example:\\n spec-graph query \\'item[openness=open]\\'');\n }\n\n return {\n command,\n patterns,\n root,\n rootExplicit,\n ignore,\n ignoreReferences,\n families,\n ignoreFamilies,\n historyPatterns,\n baseline,\n ratchet,\n recordBaseline,\n noConfig,\n format,\n graphFormat,\n severities,\n color,\n ascii,\n verbose,\n documentsOnly,\n max,\n maxWarnings,\n strict,\n selector,\n help,\n version,\n };\n}\n\nfunction count(n: number, word: string, many?: string): string {\n return `${n} ${n === 1 ? word : (many ?? `${word}s`)}`;\n}\n\ninterface HeldBaseline {\n readonly baseline: Baseline;\n readonly problems: readonly string[];\n /** What stopped the file being read, when something did. */\n readonly unread: { readonly missing: boolean; readonly message: string } | null;\n}\n\n/**\n * Reads a baseline file.\n *\n * Absent and unreadable are different answers, and the caller needs both. A\n * repository that has not recorded a baseline yet declares the path before the\n * file exists, and an empty baseline accepts nothing, which is what that run\n * should report (ADR-0012). Anything else - a directory, a permission, a path\n * that went somewhere unintended - is the tool failing to do what it was told.\n */\nasync function readBaseline(path: string, source: string): Promise<HeldBaseline> {\n let raw: string;\n try {\n const { readFile } = await import('node:fs/promises');\n raw = await readFile(path, 'utf8');\n } catch (error) {\n const failure = error as Error & { code?: string };\n return {\n baseline: EMPTY_BASELINE,\n problems: [],\n unread: { missing: failure.code === 'ENOENT', message: failure.message },\n };\n }\n return { ...parseBaseline(raw, source), unread: null };\n}\n\nfunction parseCount(flag: string, value: string): number {\n const parsed = Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed < 0 || String(parsed) !== value.trim()) {\n throw new UsageError(`${flag} expects a non-negative whole number, got \"${value}\"`);\n }\n return parsed;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Entry point */\n/* -------------------------------------------------------------------------- */\n\n/** Runs the CLI and returns the process exit code. Never throws. */\nexport async function main(io: CliIO = {}): Promise<number> {\n const argv = io.argv ?? process.argv.slice(2);\n const cwd = io.cwd ?? process.cwd();\n const out = io.stdout ?? ((text) => process.stdout.write(text));\n const err = io.stderr ?? ((text) => process.stderr.write(text));\n const env = io.env ?? process.env;\n\n let options: CliOptions;\n try {\n options = parseArgs(argv, cwd);\n } catch (error) {\n if (error instanceof UsageError) {\n err(`spec-graph: ${error.message}\\n`);\n return EXIT_ERROR;\n }\n throw error;\n }\n\n if (options.help) {\n out(HELP);\n return EXIT_OK;\n }\n if (options.version) {\n out(`${await readVersion()}\\n`);\n return EXIT_OK;\n }\n\n // A diff reads two files and nothing else: no configuration and no corpus, so\n // it can compare exports from a checkout it is not running in.\n if (options.command === 'diff') return runDiff(options.patterns as readonly [string, string], cwd, options.format, out, err);\n\n const color = options.color ?? shouldUseColor({ isTTY: io.isTTY, env });\n const ascii = options.ascii ?? shouldUseAscii({ env });\n\n // Configuration is what is true of the repository; a flag is somebody\n // overriding it for one run. So a flag always wins, and lists add rather than\n // replace - a `--ignore-ref` on the command line is one more exclusion, not a\n // decision to throw away the ones the repository already declared.\n //\n // Read before `rules` prints anything, so that command lists the conventions\n // this repository will actually check rather than the ones spec-graph ships.\n const loaded = options.noConfig\n ? { config: {} as SpecGraphConfig, source: null, problems: [], root: options.root }\n : options.rootExplicit\n ? { ...loadConfig(options.root), root: options.root }\n : discoverConfig(options.root);\n for (const problem of loaded.problems) err(`spec-graph: ${problem}\\n`);\n // A configuration that did not load means a run against a different\n // repository than the one configured - other patterns, other rules, other\n // severities - and the verdict printed is about that one. Reporting the\n // problem and carrying on gave a green build with `ok: true` from a file with\n // a typo in it, which is the one answer a checker must never reach by\n // accident, and the same mistake in a `--rule` has always exited 2. The\n // findings are one flag away, and the flag is named. Amends ADR-0010.\n if (loaded.problems.length > 0) {\n err(\n `spec-graph: ${count(loaded.problems.length, 'problem')} in the configuration, so nothing was checked\\n fix it, or run again with --no-config to check on defaults\\n`,\n );\n return EXIT_ERROR;\n }\n const file = loaded.config;\n const projectRules = file.rules ?? [];\n const root = loaded.root;\n // How far the root moved up. Everything a repository declares about itself is\n // relative to the root; everything typed on the command line is relative to\n // where it was typed, and this is what keeps those two readings apart.\n const here = below(root, options.root);\n\n // A `--rule` naming a project rule that does not exist is a flag that\n // silently does nothing, and nothing in the output would distinguish that\n // from a rule that ran and found none.\n for (const id of Object.keys(options.severities)) {\n if (!isProjectRule(id as AnyRuleId) || projectRules.some((rule) => rule.id === id)) continue;\n err(`spec-graph: unknown rule \"${id}\"\\n ${knownProjectRules(projectRules)}\\n`);\n return EXIT_ERROR;\n }\n\n if (options.command === 'rules') {\n // The one positional argument `rules` can take is a rule id: this command\n // reads no files, so a pattern here would be a word with nowhere to go.\n const wanted = options.patterns[0];\n if (wanted !== undefined && !RULE_IDS.includes(wanted as RuleId) && !projectRules.some((rule) => rule.id === wanted)) {\n err(\n `spec-graph: unknown rule \"${wanted}\"\\n known rules: ${RULE_IDS.join(', ')}\\n ${knownProjectRules(projectRules)}\\n`,\n );\n return EXIT_ERROR;\n }\n out(renderRules(options.verbose, projectRules, wanted ?? null, loaded.source));\n return EXIT_OK;\n }\n\n const patterns =\n options.patterns.length > 0 ? options.patterns.map((pattern) => anchor(here, pattern)) : (file.patterns ?? DEFAULT_PATTERNS);\n const severityOverrides = { ...(file.severities ?? {}), ...options.severities };\n const { severities, escalated } = resolveStrict(\n severityOverrides,\n options.strict || (file.strict ?? false),\n projectRules,\n );\n\n const analyseOptions: AnalyseOptions = {\n root,\n patterns,\n // A bare name prunes a directory of that name at any depth, the way a\n // .gitignore line does, so it means the same thing wherever it was typed.\n // A path or a glob is matched against the repository-relative path, and\n // leaving that one alone would make it silently match nothing.\n ignore: [...(file.ignore ?? []), ...options.ignore.map((pattern) => anchorPath(here, pattern))],\n ignoreReferences: [...(file.ignoreReferences ?? []), ...options.ignoreReferences],\n families: [...(file.families ?? []), ...options.families],\n ignoreFamilies: [...(file.ignoreFamilies ?? []), ...options.ignoreFamilies],\n historyPatterns: [...(file.historyPatterns ?? []), ...options.historyPatterns.map((pattern) => anchorPath(here, pattern))],\n severities,\n projectRules,\n ...(file.maxRelated !== undefined ? { maxRelated: file.maxRelated } : {}),\n };\n\n // Named the way the reader would have to type it, because a discovered\n // configuration is often not the one in front of them.\n //\n // On stderr, with everything else this run says about itself. stdout is the\n // report, and in three of the four formats it is a document: a line above it\n // made `--verbose --format json` unparseable and handed `--format sarif` to a\n // code-scanning uploader that rejects it over a schema.\n if (options.verbose && loaded.source !== null) {\n const depth = here === '' ? 0 : here.split('/').length;\n err(`configuration: ${'../'.repeat(depth)}${loaded.source}\\n`);\n }\n\n let result;\n try {\n result = await analyse(analyseOptions);\n } catch (error) {\n err(`spec-graph: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n\n if (result.files.length === 0) {\n err(\n `spec-graph: no specifications matched ${\n patterns.map((p) => `\"${p}\"`).join(', ')\n }\\n looked under ${root}\\n`,\n );\n return EXIT_ERROR;\n }\n\n switch (options.command) {\n case 'graph':\n out(\n formatGraph(result.graph, options.graphFormat, {\n documentsOnly: options.documentsOnly,\n generator: { name: 'spec-graph', version: await readVersion() },\n }),\n );\n return EXIT_OK;\n\n case 'query': {\n const selector = options.selector as string;\n let queries: readonly QuerySpec[];\n\n // A registered rule is asked for by name, because by the time this runs\n // it is already compiled and the alternative is copying its selector out\n // of the configuration file by hand. The namespace cannot collide with\n // the grammar: no selector begins with a word and a colon. See ADR-0016.\n if (isProjectRule(selector as AnyRuleId)) {\n const rule = projectRules.find((candidate) => candidate.id === selector);\n if (rule === undefined) {\n err(`spec-graph: unknown rule \"${selector}\"\\n ${knownProjectRules(projectRules)}\\n`);\n return EXIT_ERROR;\n }\n queries = rule.queries;\n // Beside the report rather than inside it, for the reason the\n // configuration line above is: `--format json` is a document.\n if (options.verbose) for (const source of rule.sources) err(`${rule.id}: ${source}\\n`);\n } else {\n try {\n queries = [parseQuery(selector)];\n } catch (error) {\n if (error instanceof QueryError) {\n err(renderQueryError(selector, error));\n return EXIT_ERROR;\n }\n throw error;\n }\n }\n\n const matches = union(result.graph, queries);\n out(renderMatches(matches, options.format === 'json' ? 'json' : 'human', result.graph.nodes.size));\n return matches.length > 0 ? EXIT_OK : EXIT_FAILED;\n }\n\n default: {\n // Recording is not checking. It writes down what is wrong today so that\n // tomorrow can be compared against it, and says nothing about whether\n // today is acceptable - so it reports what it wrote and exits clean.\n if (options.recordBaseline !== null) {\n const text = formatBaseline(result.graph, result.diagnostics);\n try {\n const { writeFile } = await import('node:fs/promises');\n await writeFile(underRoot(root, anchor(here, options.recordBaseline)), text, 'utf8');\n } catch (error) {\n err(`spec-graph: cannot write ${options.recordBaseline}: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n const entries = (JSON.parse(text) as Baseline).findings.length;\n out(\n `recorded ${count(result.diagnostics.length, 'finding')} as ${count(entries, 'entry', 'entries')} in ${options.recordBaseline}\\n`,\n );\n return EXIT_OK;\n }\n\n const source = options.baseline === null ? (file.baseline ?? null) : anchor(here, options.baseline);\n const ratchet = options.ratchet || file.ratchet === true;\n let reported = result;\n let note:\n | {\n source: string;\n suppressed: number;\n stale: number;\n ratchet: boolean;\n entries: readonly StaleEntry[];\n }\n | undefined;\n if (source !== null) {\n const held = await readBaseline(underRoot(root, source), source);\n // A path typed on the command line says the file is there for this run.\n // Read as an empty baseline instead, a typo in it reports every accepted\n // finding as new, and passes --ratchet with nothing left to be stale.\n // A path in the configuration is the case ADR-0012 argued for, and only\n // where the file is absent: unreadable is unreadable wherever the path\n // was written.\n if (held.unread !== null && (options.baseline !== null || !held.unread.missing)) {\n err(`spec-graph: cannot read the baseline ${source}: ${held.unread.message}\\n`);\n return EXIT_ERROR;\n }\n // Quiet by default, because a configured path legitimately precedes the\n // first --record-baseline. Under --verbose, where parse problems already\n // go, a path that reads nothing says so rather than accepting nothing.\n if (held.unread !== null && options.verbose) {\n err(`spec-graph: baseline ${source} is not there, so nothing is accepted\\n`);\n }\n for (const problem of held.problems) err(`spec-graph: ${problem}\\n`);\n const outcome = applyBaseline(result.graph, result.diagnostics, held.baseline);\n reported = withDiagnostics(result, outcome.kept);\n note = {\n source,\n suppressed: outcome.suppressed,\n stale: outcome.stale.length,\n ratchet,\n entries: outcome.stale,\n };\n // Listed rather than counted when the run turns on them: a number is\n // enough to know the file has slack, and not enough to strike it. On\n // stdout only for a human - the structured formats carry the same rows\n // inside the document, where a stray line is the difference between\n // parsing and not.\n if (options.format === 'human' && (options.verbose || (ratchet && outcome.stale.length > 0))) {\n for (const entry of outcome.stale) {\n const subject = entry.subject === '' ? '' : ` \"${entry.subject}\"`;\n const why = entry.reason === 'gone' ? ` - ${entry.document} is not in this corpus` : '';\n out(` ${entry.reason}: ${entry.rule} ${entry.document}${subject}${why}\\n`);\n }\n }\n }\n const looseBaseline = note !== undefined && note.ratchet && note.stale > 0;\n\n const baselineNote = note === undefined ? {} : { baseline: note };\n const reporterOptions = { verbose: options.verbose, max: options.max, escalated, ...baselineNote };\n out(\n options.format === 'sarif'\n ? formatSarif(reported, reported.graph, { version: await readVersion(), escalated, projectRules })\n : options.format === 'json'\n ? formatJson(reported, { escalated, ...baselineNote })\n : options.format === 'markdown'\n ? formatMarkdown(reported, reporterOptions)\n : `${formatReport(reported, { color, ascii, ...reporterOptions })}\\n`,\n );\n if (!reported.ok || looseBaseline) return EXIT_FAILED;\n if (options.maxWarnings >= 0 && reported.summary.warnings > options.maxWarnings) return EXIT_FAILED;\n return EXIT_OK;\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Rendering */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Where the working directory sits below the root, or `''` when it is the root.\n *\n * Discovery only ever walks upward, so the root is always a prefix of the\n * directory the command was typed in, and this is a slice rather than path\n * arithmetic.\n */\nfunction below(root: string, from: string): string {\n const start = toPosix(from).replace(/[/]+$/, '');\n return start.startsWith(`${root}/`) ? start.slice(root.length + 1) : '';\n}\n\n/**\n * Re-anchors a path typed in `prefix` so it reads from the root.\n *\n * An absolute path is left alone: it was not relative to anywhere, so moving\n * the root cannot change what it means.\n */\nfunction anchor(prefix: string, value: string): string {\n if (prefix === '' || isAbsolutePath(value)) return value;\n return value.startsWith('!') ? `!${prefix}/${value.slice(1)}` : `${prefix}/${value}`;\n}\n\n/** The same, for an ignore, where a bare name is a directory at any depth. */\nfunction anchorPath(prefix: string, pattern: string): string {\n return isGlob(pattern) || pattern.includes('/') ? anchor(prefix, pattern) : pattern;\n}\n\nfunction knownProjectRules(projectRules: readonly ProjectRule[]): string {\n const known = projectRules.map((rule) => rule.id);\n return known.length > 0 ? `project rules here: ${known.join(', ')}` : 'this repository defines no project rules';\n}\n\n/**\n * Runs a list of selectors as one result set.\n *\n * A project rule is a union of its selectors, and a path that two of them both\n * find is one path - the same dedupe `projectFindings` does, so that asking\n * this command what a rule matches answers with the set `check` reports on.\n */\nfunction union(graph: AnalysisResult['graph'], queries: readonly QuerySpec[]): Match[] {\n if (queries.length === 1) return execute(graph, queries[0] as QuerySpec);\n const seen = new Set<string>();\n const matches: Match[] = [];\n for (const spec of queries) {\n for (const match of execute(graph, spec)) {\n const key = match.nodes.map((node) => node.id).join('>');\n if (seen.has(key)) continue;\n seen.add(key);\n matches.push(match);\n }\n }\n return matches;\n}\n\ninterface RuleRow {\n readonly id: string;\n readonly severity: Severity;\n readonly description: string;\n /** The selector, where the rule is one. */\n readonly query: string | undefined;\n /** Where the reasoning is: an ADR, or the file that declared the rule. */\n readonly decided: string;\n}\n\nfunction renderRules(\n explain: boolean,\n projectRules: readonly ProjectRule[] = [],\n only: string | null = null,\n configSource: string | null = null,\n): string {\n // A project rule is described by the selector it is, because that is what it\n // is - its message is a template, and a template is not a description.\n const rows: RuleRow[] = [\n ...RULE_IDS.map((id): RuleRow => ({\n id,\n severity: DEFAULT_SEVERITIES[id],\n description: RULE_DESCRIPTIONS[id],\n query: RULE_QUERIES[id],\n decided: RULE_DECISIONS[id],\n })),\n ...projectRules.map((rule): RuleRow => ({\n id: rule.id,\n severity: rule.severity,\n description: rule.sources[0] as string,\n query: rule.sources.slice(1).join(' | ') || undefined,\n decided: configSource === null ? `rules.${rule.name}` : `${configSource}: rules.${rule.name}`,\n })),\n ];\n\n const shown = only === null ? rows : rows.filter((row) => row.id === only);\n const width = Math.max(...shown.map((row) => row.id.length));\n const lines: string[] = [];\n for (const row of shown) {\n lines.push(`${row.id.padEnd(width)} ${row.severity.padEnd(5)} ${row.description}`);\n if (!explain) continue;\n if (row.query) lines.push(`${' '.repeat(width)} ${row.query}`);\n // Where the decision is written down, rather than a second copy of it. A\n // reader who has just been told their document is inconsistent is entitled\n // to know who decided that and why, and the ADR is the answer.\n lines.push(`${' '.repeat(width)} ${row.decided}`);\n }\n return `${lines.join('\\n')}\\n`;\n}\n\nfunction renderMatches(matches: readonly Match[], format: 'human' | 'json', total: number): string {\n if (format === 'json') {\n return `${JSON.stringify(\n {\n version: 1,\n count: matches.length,\n matches: matches.map((match) => ({\n nodes: match.nodes.map((node) => nodeSummary(node)),\n edges: match.edges.map((edge) => ({\n kind: edge.kind,\n from: edge.from,\n to: edge.to,\n file: edge.declaredAt.file,\n line: edge.declaredAt.span.start.line,\n })),\n })),\n },\n null,\n 2,\n )}\\n`;\n }\n\n if (matches.length === 0) return `no matches (searched ${total} nodes)\\n`;\n\n const lines: string[] = [];\n for (const match of matches) {\n const head = match.nodes[0] as SpecNode;\n const tail = match.nodes[match.nodes.length - 1] as SpecNode;\n lines.push(renderMatch(match));\n lines.push(` ${formatRef(head.at)}${match.nodes.length > 1 ? ` ${formatRef(tail.at)}` : ''}`);\n }\n lines.push(`${matches.length} ${matches.length === 1 ? 'match' : 'matches'}`);\n return `${lines.join('\\n')}\\n`;\n}\n\nfunction nodeSummary(node: SpecNode): Record<string, unknown> {\n return {\n id: node.id,\n kind: node.kind,\n title: node.title,\n file: node.at.file,\n line: node.at.span.start.line,\n ...(node.kind === 'document' ? { phase: node.phase } : { state: node.disposition, openness: node.openness }),\n };\n}\n\n/** Points at the exact character of a selector that failed to parse. */\nfunction renderQueryError(selector: string, error: QueryError): string {\n const caret = `${' '.repeat(Math.max(0, error.offset))}^`;\n return `spec-graph: ${error.message}\\n ${selector}\\n ${caret}\\n`;\n}\n\n/**\n * Compares two exports.\n *\n * Exit 0 whether anything changed or not: a diff describes, and `check` gates. A\n * diff that failed on a removed relation would fail every legitimate\n * supersession (ADR-0020).\n */\nasync function runDiff(\n paths: readonly [string, string],\n cwd: string,\n format: CliOptions['format'],\n out: (text: string) => void,\n err: (text: string) => void,\n): Promise<number> {\n const { readFile } = await import('node:fs/promises');\n const { resolve } = await import('node:path');\n const sides: GraphExport[] = [];\n for (const path of paths) {\n let raw: string;\n try {\n raw = await readFile(resolve(cwd, path), 'utf8');\n } catch (error) {\n err(`spec-graph: cannot read ${path}: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n try {\n sides.push(parseGraphExport(raw, path));\n } catch (error) {\n if (!(error instanceof DiffInputError)) throw error;\n err(`spec-graph: ${error.message}\\n`);\n return EXIT_ERROR;\n }\n }\n const [before, after] = sides as [GraphExport, GraphExport];\n const diff = diffExports(before, after);\n out(format === 'json' ? formatDiffJson(diff) : format === 'markdown' ? formatDiffMarkdown(diff) : formatDiffText(diff));\n return EXIT_OK;\n}\n\nasync function readVersion(): Promise<string> {\n try {\n const { readFile } = await import('node:fs/promises');\n const url = new URL('../package.json', import.meta.url);\n const parsed: unknown = JSON.parse(await readFile(url, 'utf8'));\n if (typeof parsed === 'object' && parsed !== null && 'version' in parsed) {\n return String((parsed as { version: unknown }).version);\n }\n } catch {\n // Fall through to the placeholder rather than failing `--version`.\n }\n return '0.0.0';\n}\n"]}
|
package/dist/config.d.ts
CHANGED
|
@@ -85,9 +85,9 @@ export interface DiscoveryIO {
|
|
|
85
85
|
/**
|
|
86
86
|
* Reads configuration from a directory.
|
|
87
87
|
*
|
|
88
|
-
* Never throws
|
|
89
|
-
*
|
|
90
|
-
*
|
|
88
|
+
* Never throws, and never decides. Every problem is collected and handed back
|
|
89
|
+
* whole, so the reader sees all of them at once rather than the first; what a
|
|
90
|
+
* problem costs is the caller's to say, and the CLI stops on one.
|
|
91
91
|
*/
|
|
92
92
|
export declare function loadConfig(root: string, read?: (path: string) => string): LoadedConfig;
|
|
93
93
|
/**
|
package/dist/config.js
CHANGED
|
@@ -32,9 +32,9 @@ const EMPTY = { config: {}, source: null, problems: [] };
|
|
|
32
32
|
/**
|
|
33
33
|
* Reads configuration from a directory.
|
|
34
34
|
*
|
|
35
|
-
* Never throws
|
|
36
|
-
*
|
|
37
|
-
*
|
|
35
|
+
* Never throws, and never decides. Every problem is collected and handed back
|
|
36
|
+
* whole, so the reader sees all of them at once rather than the first; what a
|
|
37
|
+
* problem costs is the caller's to say, and the CLI stops on one.
|
|
38
38
|
*/
|
|
39
39
|
export function loadConfig(root, read = defaultRead) {
|
|
40
40
|
for (const name of CONFIG_FILES) {
|
|
@@ -59,7 +59,7 @@ export function loadConfig(root, read = defaultRead) {
|
|
|
59
59
|
if (section === undefined)
|
|
60
60
|
return EMPTY;
|
|
61
61
|
if (!isRecord(section)) {
|
|
62
|
-
return { config: {}, source: `package.json`, problems: [`"${CONFIG_PACKAGE_KEY}" must be an object`] };
|
|
62
|
+
return { config: {}, source: `package.json`, problems: [`package.json: "${CONFIG_PACKAGE_KEY}" must be an object`] };
|
|
63
63
|
}
|
|
64
64
|
return readFields(section, `package.json#${CONFIG_PACKAGE_KEY}`);
|
|
65
65
|
}
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Repository configuration.\n *\n * Three ADRs left the same open question: `--ignore-ref` patterns, include\n * globs and rule severities all belong to a repository rather than to whoever\n * typed the command, and an npm script was doing that job by accumulating\n * flags. A CI invocation carrying eight `--ignore-ref` arguments is a\n * configuration file that has not admitted what it is.\n *\n * The format is JSON, parsed with `JSON.parse`. That is not a shortcut - it is\n * the reason this file exists at all. spec-graph has no runtime dependencies,\n * and a YAML or TOML reader would be the largest thing in the package, to read\n * a file with nine keys in it.\n *\n * Every key mirrors a flag, and a flag always wins. Configuration is where a\n * team writes down what is true of the repository; a flag is where somebody\n * overrides it for one run.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\n\nimport { dirnamePosix, toPosix } from './paths.js';\nimport { compileProjectRules, type ProjectRule } from './project-rules.js';\nimport { RULE_IDS } from './rules.js';\nimport { isProjectRule, type AnyRuleId, type RuleId, type Severity } from './types.js';\n\n/** File names looked for, in order. The first that exists is the one used. */\nexport const CONFIG_FILES: readonly string[] = Object.freeze([\n '.spec-graph.json',\n 'spec-graph.config.json',\n]);\n\n/** The `package.json` key read when no config file is present. */\nexport const CONFIG_PACKAGE_KEY = 'spec-graph';\n\nexport interface SpecGraphConfig {\n readonly patterns?: readonly string[] | undefined;\n readonly ignore?: readonly string[] | undefined;\n readonly ignoreReferences?: readonly string[] | undefined;\n /**\n * Families a bare identifier in prose may name.\n *\n * When set, `ADR-0099` is only ever read as a citation if `ADR` is listed.\n * Everything else in prose stays prose.\n */\n readonly families?: readonly string[] | undefined;\n /** Families that are never citations, whatever else the corpus contains. */\n readonly ignoreFamilies?: readonly string[] | undefined;\n /**\n * Files that are logs of what was decided rather than decisions themselves.\n *\n * Journals, changelogs, minutes. Their links are still checked; their\n * obligations and lifecycle are not. See ADR-0011.\n */\n readonly historyPatterns?: readonly string[] | undefined;\n /** Path to the accepted-debt baseline, relative to the root. See ADR-0012. */\n readonly baseline?: string | undefined;\n /** Fail when a baseline entry no longer occurs. See ADR-0012. */\n readonly ratchet?: boolean | undefined;\n readonly severities?: Partial<Record<AnyRuleId, Severity>> | undefined;\n readonly strict?: boolean | undefined;\n readonly maxRelated?: number | undefined;\n /**\n * Conventions this repository checks that spec-graph does not.\n *\n * A selector, a sentence and a severity. Compiled here rather than carried as\n * raw JSON, so a rule that cannot run is reported at load time next to every\n * other configuration problem - and not as a rule that quietly finds nothing.\n * See ADR-0016.\n */\n readonly rules?: readonly ProjectRule[] | undefined;\n}\n\nexport interface LoadedConfig {\n readonly config: SpecGraphConfig;\n /** Where it came from, for `--verbose` and for error messages. */\n readonly source: string | null;\n /** Keys that were present but unusable. Reported, never silently dropped. */\n readonly problems: readonly string[];\n}\n\nexport interface DiscoveredConfig extends LoadedConfig {\n /**\n * The directory the configuration was found in.\n *\n * This is the root of the run. Every path in spec-graph is relative to it -\n * node identities, baseline keys, SARIF locations - so the configuration file\n * naming the repository is the same statement as the repository having a\n * root. See ADR-0018.\n */\n readonly root: string;\n}\n\n/** Injected so discovery can be tested without a directory tree. */\nexport interface DiscoveryIO {\n readonly read?: ((path: string) => string) | undefined;\n readonly exists?: ((path: string) => boolean) | undefined;\n}\n\nconst EMPTY: LoadedConfig = { config: {}, source: null, problems: [] };\n\n/**\n * Reads configuration from a directory.\n *\n * Never throws. A malformed config is reported as a problem and the run\n * continues with defaults: a broken config file should not stop a team seeing\n * the findings it was going to show them anyway.\n */\nexport function loadConfig(root: string, read: (path: string) => string = defaultRead): LoadedConfig {\n for (const name of CONFIG_FILES) {\n const raw = tryRead(read, `${root}/${name}`);\n if (raw === null) continue;\n return parseConfig(raw, name);\n }\n\n const pkg = tryRead(read, `${root}/package.json`);\n if (pkg === null) return EMPTY;\n let parsed: unknown;\n try {\n parsed = JSON.parse(pkg);\n } catch {\n return EMPTY;\n }\n if (!isRecord(parsed)) return EMPTY;\n const section = parsed[CONFIG_PACKAGE_KEY];\n if (section === undefined) return EMPTY;\n if (!isRecord(section)) {\n return { config: {}, source: `package.json`, problems: [`\"${CONFIG_PACKAGE_KEY}\" must be an object`] };\n }\n return readFields(section, `package.json#${CONFIG_PACKAGE_KEY}`);\n}\n\n/**\n * Finds the configuration by walking up from a directory.\n *\n * A monorepo is invoked from inside a package - `packages/auth`, or\n * `docs/architecture` - and reading configuration from the working directory\n * meant that the same repository checked differently depending on where\n * somebody stood in it. ADR-0010 worried that discovery would make a run\n * depend on where it started. It does the opposite: because the directory\n * holding the configuration becomes the root, a run from anywhere inside the\n * repository produces byte-identical output to a run from the top.\n *\n * The walk stops at the repository, which is a directory holding `.git`.\n * Above that is somebody else's checkout or a home directory, and a run that\n * silently picked up a configuration file nobody in the repository can see\n * would be worse than no discovery at all. See ADR-0018.\n */\nexport function discoverConfig(from: string, io: DiscoveryIO = {}): DiscoveredConfig {\n const read = io.read ?? defaultRead;\n const exists = io.exists ?? existsSync;\n const start = toPosix(from).replace(/\\/+$/, '');\n\n let directory = start;\n for (;;) {\n const loaded = loadConfig(directory, read);\n if (loaded.source !== null) return { ...loaded, root: directory };\n if (exists(`${directory}/.git`)) break;\n const parent = dirnamePosix(directory);\n // Two guards for one job, and each makes the other unreachable: a path with\n // no separator left in it gives back `''`, and `''` gives back itself. Only\n // one of them can ever fire, which is why neither has a test - removing\n // either leaves the loop terminating on the other.\n if (parent === '' || parent === directory) break;\n directory = parent;\n }\n return { ...EMPTY, root: start };\n}\n\n/** Parses config text. Exported so the shape can be tested without a disk. */\nexport function parseConfig(raw: string, source: string): LoadedConfig {\n let parsed: unknown;\n try {\n parsed = JSON.parse(withoutBom(raw));\n } catch (error) {\n return { config: {}, source, problems: [`${source} is not valid JSON: ${(error as Error).message}`] };\n }\n if (!isRecord(parsed)) return { config: {}, source, problems: [`${source} must contain a JSON object`] };\n return readFields(parsed, source);\n}\n\nfunction readFields(raw: Record<string, unknown>, source: string): LoadedConfig {\n const problems: string[] = [];\n const config: {\n -readonly [K in keyof SpecGraphConfig]: SpecGraphConfig[K];\n } = {};\n\n const strings = (key: keyof SpecGraphConfig): readonly string[] | undefined => {\n const value = raw[key];\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {\n problems.push(`${source}: \"${key}\" must be an array of strings`);\n return undefined;\n }\n return value as string[];\n };\n\n config.patterns = strings('patterns');\n config.ignore = strings('ignore');\n config.ignoreReferences = strings('ignoreReferences');\n config.families = strings('families');\n config.ignoreFamilies = strings('ignoreFamilies');\n config.historyPatterns = strings('historyPatterns');\n\n if (raw['baseline'] !== undefined) {\n if (typeof raw['baseline'] !== 'string' || raw['baseline'].trim().length === 0) {\n problems.push(`${source}: \"baseline\" must be a path`);\n } else {\n config.baseline = raw['baseline'];\n }\n }\n\n if (raw['strict'] !== undefined) {\n if (typeof raw['strict'] !== 'boolean') problems.push(`${source}: \"strict\" must be true or false`);\n else config.strict = raw['strict'];\n }\n\n if (raw['ratchet'] !== undefined) {\n if (typeof raw['ratchet'] !== 'boolean') problems.push(`${source}: \"ratchet\" must be true or false`);\n else config.ratchet = raw['ratchet'];\n }\n\n if (raw['maxRelated'] !== undefined) {\n const value = raw['maxRelated'];\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {\n problems.push(`${source}: \"maxRelated\" must be a non-negative whole number`);\n } else {\n config.maxRelated = value;\n }\n }\n\n if (raw['severities'] !== undefined) {\n const value = raw['severities'];\n if (!isRecord(value)) {\n problems.push(`${source}: \"severities\" must be an object of rule to severity`);\n } else {\n const severities: Partial<Record<AnyRuleId, Severity>> = {};\n for (const [rule, level] of Object.entries(value)) {\n // A project rule is named here by its full id, `project:no-drafts`, the\n // same spelling `--rule` takes and a report prints. Whether one exists\n // is checked below, once both halves of the file have been read.\n if (!RULE_IDS.includes(rule as RuleId) && !isProjectRule(rule as AnyRuleId)) {\n problems.push(`${source}: unknown rule \"${rule}\" in \"severities\"`);\n continue;\n }\n if (level !== 'error' && level !== 'warn' && level !== 'info' && level !== 'off') {\n problems.push(`${source}: \"severities.${rule}\" must be error, warn, info or off`);\n continue;\n }\n severities[rule as AnyRuleId] = level;\n }\n config.severities = severities;\n }\n }\n\n const compiled = raw['rules'] === undefined ? null : compileProjectRules(raw['rules'], source);\n if (compiled !== null) {\n problems.push(...compiled.problems);\n config.rules = compiled.rules;\n }\n\n // A severity naming a project rule that nothing defines is a line that will\n // never do anything, and reading like it does is the one thing a\n // configuration file must not be allowed to do.\n const defined = new Set<string>((compiled?.rules ?? []).map((rule) => rule.id));\n for (const rule of Object.keys(config.severities ?? {})) {\n if (!isProjectRule(rule as AnyRuleId) || defined.has(rule)) continue;\n problems.push(`${source}: \"severities.${rule}\" names no rule in \"rules\"`);\n }\n\n for (const key of Object.keys(raw)) {\n if (!KNOWN_KEYS.has(key)) problems.push(`${source}: unknown key \"${key}\"`);\n }\n\n return { config, source, problems };\n}\n\nconst KNOWN_KEYS: ReadonlySet<string> = new Set([\n 'patterns',\n 'ignore',\n 'ignoreReferences',\n 'families',\n 'ignoreFamilies',\n 'historyPatterns',\n 'baseline',\n 'ratchet',\n 'rules',\n 'severities',\n 'strict',\n 'maxRelated',\n // Tolerated so an editor can be pointed at a schema without being told off.\n '$schema',\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction tryRead(read: (path: string) => string, path: string): string | null {\n try {\n return withoutBom(read(path));\n } catch {\n return null;\n }\n}\n\n/**\n * Drops a leading byte-order mark.\n *\n * `JSON.parse` rejects one, and several Windows editors write one by default -\n * a config that silently stops applying because of an invisible first character\n * is the worst kind of configuration bug.\n */\nfunction withoutBom(text: string): string {\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n}\n\nfunction defaultRead(path: string): string {\n return readFileSync(path, 'utf8');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Repository configuration.\n *\n * Three ADRs left the same open question: `--ignore-ref` patterns, include\n * globs and rule severities all belong to a repository rather than to whoever\n * typed the command, and an npm script was doing that job by accumulating\n * flags. A CI invocation carrying eight `--ignore-ref` arguments is a\n * configuration file that has not admitted what it is.\n *\n * The format is JSON, parsed with `JSON.parse`. That is not a shortcut - it is\n * the reason this file exists at all. spec-graph has no runtime dependencies,\n * and a YAML or TOML reader would be the largest thing in the package, to read\n * a file with nine keys in it.\n *\n * Every key mirrors a flag, and a flag always wins. Configuration is where a\n * team writes down what is true of the repository; a flag is where somebody\n * overrides it for one run.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\n\nimport { dirnamePosix, toPosix } from './paths.js';\nimport { compileProjectRules, type ProjectRule } from './project-rules.js';\nimport { RULE_IDS } from './rules.js';\nimport { isProjectRule, type AnyRuleId, type RuleId, type Severity } from './types.js';\n\n/** File names looked for, in order. The first that exists is the one used. */\nexport const CONFIG_FILES: readonly string[] = Object.freeze([\n '.spec-graph.json',\n 'spec-graph.config.json',\n]);\n\n/** The `package.json` key read when no config file is present. */\nexport const CONFIG_PACKAGE_KEY = 'spec-graph';\n\nexport interface SpecGraphConfig {\n readonly patterns?: readonly string[] | undefined;\n readonly ignore?: readonly string[] | undefined;\n readonly ignoreReferences?: readonly string[] | undefined;\n /**\n * Families a bare identifier in prose may name.\n *\n * When set, `ADR-0099` is only ever read as a citation if `ADR` is listed.\n * Everything else in prose stays prose.\n */\n readonly families?: readonly string[] | undefined;\n /** Families that are never citations, whatever else the corpus contains. */\n readonly ignoreFamilies?: readonly string[] | undefined;\n /**\n * Files that are logs of what was decided rather than decisions themselves.\n *\n * Journals, changelogs, minutes. Their links are still checked; their\n * obligations and lifecycle are not. See ADR-0011.\n */\n readonly historyPatterns?: readonly string[] | undefined;\n /** Path to the accepted-debt baseline, relative to the root. See ADR-0012. */\n readonly baseline?: string | undefined;\n /** Fail when a baseline entry no longer occurs. See ADR-0012. */\n readonly ratchet?: boolean | undefined;\n readonly severities?: Partial<Record<AnyRuleId, Severity>> | undefined;\n readonly strict?: boolean | undefined;\n readonly maxRelated?: number | undefined;\n /**\n * Conventions this repository checks that spec-graph does not.\n *\n * A selector, a sentence and a severity. Compiled here rather than carried as\n * raw JSON, so a rule that cannot run is reported at load time next to every\n * other configuration problem - and not as a rule that quietly finds nothing.\n * See ADR-0016.\n */\n readonly rules?: readonly ProjectRule[] | undefined;\n}\n\nexport interface LoadedConfig {\n readonly config: SpecGraphConfig;\n /** Where it came from, for `--verbose` and for error messages. */\n readonly source: string | null;\n /** Keys that were present but unusable. Reported, never silently dropped. */\n readonly problems: readonly string[];\n}\n\nexport interface DiscoveredConfig extends LoadedConfig {\n /**\n * The directory the configuration was found in.\n *\n * This is the root of the run. Every path in spec-graph is relative to it -\n * node identities, baseline keys, SARIF locations - so the configuration file\n * naming the repository is the same statement as the repository having a\n * root. See ADR-0018.\n */\n readonly root: string;\n}\n\n/** Injected so discovery can be tested without a directory tree. */\nexport interface DiscoveryIO {\n readonly read?: ((path: string) => string) | undefined;\n readonly exists?: ((path: string) => boolean) | undefined;\n}\n\nconst EMPTY: LoadedConfig = { config: {}, source: null, problems: [] };\n\n/**\n * Reads configuration from a directory.\n *\n * Never throws, and never decides. Every problem is collected and handed back\n * whole, so the reader sees all of them at once rather than the first; what a\n * problem costs is the caller's to say, and the CLI stops on one.\n */\nexport function loadConfig(root: string, read: (path: string) => string = defaultRead): LoadedConfig {\n for (const name of CONFIG_FILES) {\n const raw = tryRead(read, `${root}/${name}`);\n if (raw === null) continue;\n return parseConfig(raw, name);\n }\n\n const pkg = tryRead(read, `${root}/package.json`);\n if (pkg === null) return EMPTY;\n let parsed: unknown;\n try {\n parsed = JSON.parse(pkg);\n } catch {\n return EMPTY;\n }\n if (!isRecord(parsed)) return EMPTY;\n const section = parsed[CONFIG_PACKAGE_KEY];\n if (section === undefined) return EMPTY;\n if (!isRecord(section)) {\n return { config: {}, source: `package.json`, problems: [`package.json: \"${CONFIG_PACKAGE_KEY}\" must be an object`] };\n }\n return readFields(section, `package.json#${CONFIG_PACKAGE_KEY}`);\n}\n\n/**\n * Finds the configuration by walking up from a directory.\n *\n * A monorepo is invoked from inside a package - `packages/auth`, or\n * `docs/architecture` - and reading configuration from the working directory\n * meant that the same repository checked differently depending on where\n * somebody stood in it. ADR-0010 worried that discovery would make a run\n * depend on where it started. It does the opposite: because the directory\n * holding the configuration becomes the root, a run from anywhere inside the\n * repository produces byte-identical output to a run from the top.\n *\n * The walk stops at the repository, which is a directory holding `.git`.\n * Above that is somebody else's checkout or a home directory, and a run that\n * silently picked up a configuration file nobody in the repository can see\n * would be worse than no discovery at all. See ADR-0018.\n */\nexport function discoverConfig(from: string, io: DiscoveryIO = {}): DiscoveredConfig {\n const read = io.read ?? defaultRead;\n const exists = io.exists ?? existsSync;\n const start = toPosix(from).replace(/\\/+$/, '');\n\n let directory = start;\n for (;;) {\n const loaded = loadConfig(directory, read);\n if (loaded.source !== null) return { ...loaded, root: directory };\n if (exists(`${directory}/.git`)) break;\n const parent = dirnamePosix(directory);\n // Two guards for one job, and each makes the other unreachable: a path with\n // no separator left in it gives back `''`, and `''` gives back itself. Only\n // one of them can ever fire, which is why neither has a test - removing\n // either leaves the loop terminating on the other.\n if (parent === '' || parent === directory) break;\n directory = parent;\n }\n return { ...EMPTY, root: start };\n}\n\n/** Parses config text. Exported so the shape can be tested without a disk. */\nexport function parseConfig(raw: string, source: string): LoadedConfig {\n let parsed: unknown;\n try {\n parsed = JSON.parse(withoutBom(raw));\n } catch (error) {\n return { config: {}, source, problems: [`${source} is not valid JSON: ${(error as Error).message}`] };\n }\n if (!isRecord(parsed)) return { config: {}, source, problems: [`${source} must contain a JSON object`] };\n return readFields(parsed, source);\n}\n\nfunction readFields(raw: Record<string, unknown>, source: string): LoadedConfig {\n const problems: string[] = [];\n const config: {\n -readonly [K in keyof SpecGraphConfig]: SpecGraphConfig[K];\n } = {};\n\n const strings = (key: keyof SpecGraphConfig): readonly string[] | undefined => {\n const value = raw[key];\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {\n problems.push(`${source}: \"${key}\" must be an array of strings`);\n return undefined;\n }\n return value as string[];\n };\n\n config.patterns = strings('patterns');\n config.ignore = strings('ignore');\n config.ignoreReferences = strings('ignoreReferences');\n config.families = strings('families');\n config.ignoreFamilies = strings('ignoreFamilies');\n config.historyPatterns = strings('historyPatterns');\n\n if (raw['baseline'] !== undefined) {\n if (typeof raw['baseline'] !== 'string' || raw['baseline'].trim().length === 0) {\n problems.push(`${source}: \"baseline\" must be a path`);\n } else {\n config.baseline = raw['baseline'];\n }\n }\n\n if (raw['strict'] !== undefined) {\n if (typeof raw['strict'] !== 'boolean') problems.push(`${source}: \"strict\" must be true or false`);\n else config.strict = raw['strict'];\n }\n\n if (raw['ratchet'] !== undefined) {\n if (typeof raw['ratchet'] !== 'boolean') problems.push(`${source}: \"ratchet\" must be true or false`);\n else config.ratchet = raw['ratchet'];\n }\n\n if (raw['maxRelated'] !== undefined) {\n const value = raw['maxRelated'];\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {\n problems.push(`${source}: \"maxRelated\" must be a non-negative whole number`);\n } else {\n config.maxRelated = value;\n }\n }\n\n if (raw['severities'] !== undefined) {\n const value = raw['severities'];\n if (!isRecord(value)) {\n problems.push(`${source}: \"severities\" must be an object of rule to severity`);\n } else {\n const severities: Partial<Record<AnyRuleId, Severity>> = {};\n for (const [rule, level] of Object.entries(value)) {\n // A project rule is named here by its full id, `project:no-drafts`, the\n // same spelling `--rule` takes and a report prints. Whether one exists\n // is checked below, once both halves of the file have been read.\n if (!RULE_IDS.includes(rule as RuleId) && !isProjectRule(rule as AnyRuleId)) {\n problems.push(`${source}: unknown rule \"${rule}\" in \"severities\"`);\n continue;\n }\n if (level !== 'error' && level !== 'warn' && level !== 'info' && level !== 'off') {\n problems.push(`${source}: \"severities.${rule}\" must be error, warn, info or off`);\n continue;\n }\n severities[rule as AnyRuleId] = level;\n }\n config.severities = severities;\n }\n }\n\n const compiled = raw['rules'] === undefined ? null : compileProjectRules(raw['rules'], source);\n if (compiled !== null) {\n problems.push(...compiled.problems);\n config.rules = compiled.rules;\n }\n\n // A severity naming a project rule that nothing defines is a line that will\n // never do anything, and reading like it does is the one thing a\n // configuration file must not be allowed to do.\n const defined = new Set<string>((compiled?.rules ?? []).map((rule) => rule.id));\n for (const rule of Object.keys(config.severities ?? {})) {\n if (!isProjectRule(rule as AnyRuleId) || defined.has(rule)) continue;\n problems.push(`${source}: \"severities.${rule}\" names no rule in \"rules\"`);\n }\n\n for (const key of Object.keys(raw)) {\n if (!KNOWN_KEYS.has(key)) problems.push(`${source}: unknown key \"${key}\"`);\n }\n\n return { config, source, problems };\n}\n\nconst KNOWN_KEYS: ReadonlySet<string> = new Set([\n 'patterns',\n 'ignore',\n 'ignoreReferences',\n 'families',\n 'ignoreFamilies',\n 'historyPatterns',\n 'baseline',\n 'ratchet',\n 'rules',\n 'severities',\n 'strict',\n 'maxRelated',\n // Tolerated so an editor can be pointed at a schema without being told off.\n '$schema',\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction tryRead(read: (path: string) => string, path: string): string | null {\n try {\n return withoutBom(read(path));\n } catch {\n return null;\n }\n}\n\n/**\n * Drops a leading byte-order mark.\n *\n * `JSON.parse` rejects one, and several Windows editors write one by default -\n * a config that silently stops applying because of an invisible first character\n * is the worst kind of configuration bug.\n */\nfunction withoutBom(text: string): string {\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n}\n\nfunction defaultRead(path: string): string {\n return readFileSync(path, 'utf8');\n}\n"]}
|
package/dist/glob.d.ts
CHANGED
|
@@ -103,5 +103,11 @@ export declare function globBase(pattern: string): string;
|
|
|
103
103
|
* `node_modules` should cost nothing to scan.
|
|
104
104
|
*/
|
|
105
105
|
export declare function walkFiles(options: WalkOptions): Promise<WalkedFile[]>;
|
|
106
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* Resolves a repository-relative path against the root, in POSIX form.
|
|
108
|
+
*
|
|
109
|
+
* An absolute path already names one place, and joining it under the root names
|
|
110
|
+
* another: `--baseline /tmp/b.json` read `<root>/tmp/b.json`, found nothing
|
|
111
|
+
* there, and accepted nothing without saying so.
|
|
112
|
+
*/
|
|
107
113
|
export declare function underRoot(root: string, relative: string): string;
|
package/dist/glob.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* POSIX paths so results do not depend on the host platform.
|
|
9
9
|
*/
|
|
10
10
|
import { readdir, stat } from 'node:fs/promises';
|
|
11
|
-
import { joinPosix, normalisePosix, toPosix } from './paths.js';
|
|
11
|
+
import { isAbsolutePath, joinPosix, normalisePosix, toPosix } from './paths.js';
|
|
12
12
|
import { compilePattern } from './regex.js';
|
|
13
13
|
/** Directories skipped unless a pattern explicitly names them. */
|
|
14
14
|
export const DEFAULT_IGNORED_DIRECTORIES = Object.freeze([
|
|
@@ -324,8 +324,16 @@ export async function walkFiles(options) {
|
|
|
324
324
|
await walk(base);
|
|
325
325
|
return [...out.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
326
326
|
}
|
|
327
|
-
/**
|
|
327
|
+
/**
|
|
328
|
+
* Resolves a repository-relative path against the root, in POSIX form.
|
|
329
|
+
*
|
|
330
|
+
* An absolute path already names one place, and joining it under the root names
|
|
331
|
+
* another: `--baseline /tmp/b.json` read `<root>/tmp/b.json`, found nothing
|
|
332
|
+
* there, and accepted nothing without saying so.
|
|
333
|
+
*/
|
|
328
334
|
export function underRoot(root, relative) {
|
|
335
|
+
if (isAbsolutePath(relative))
|
|
336
|
+
return toPosix(relative);
|
|
329
337
|
return joinPosix(toPosix(root), relative);
|
|
330
338
|
}
|
|
331
339
|
//# sourceMappingURL=glob.js.map
|
package/dist/glob.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pattern matching and directory walking.\n *\n * A dependency-free glob, because pulling in a matcher for `docs/**\\/*.md` would\n * be the single largest thing in the package. The supported syntax is the\n * portion people actually type - `**`, `*`, `?`, `{a,b}`, `[abc]`, and a leading\n * `!` for negation - and everything is matched against repository-relative\n * POSIX paths so results do not depend on the host platform.\n */\n\nimport { readdir, stat } from 'node:fs/promises';\n\nimport { joinPosix, normalisePosix, toPosix } from './paths.js';\nimport { compilePattern, type Matcher } from './regex.js';\n\n/** Directories skipped unless a pattern explicitly names them. */\nexport const DEFAULT_IGNORED_DIRECTORIES: readonly string[] = Object.freeze([\n '.git',\n '.hg',\n '.svn',\n '.cache',\n '.next',\n '.nuxt',\n '.turbo',\n '.venv',\n 'node_modules',\n 'bower_components',\n 'vendor',\n 'dist',\n 'build',\n 'out',\n 'target',\n 'coverage',\n '__pycache__',\n]);\n\n/** Files larger than this are skipped: a specification is not a binary. */\nexport const MAX_FILE_SIZE = 4 * 1024 * 1024;\n\nexport interface WalkedFile {\n /** Repository-relative POSIX path. */\n readonly path: string;\n /** Absolute host path, for reading. */\n readonly absolute: string;\n readonly size: number;\n}\n\nexport interface WalkOptions {\n readonly root: string;\n readonly patterns: readonly string[];\n readonly ignore?: readonly string[] | undefined;\n readonly maxFileSize?: number | undefined;\n /** Follow directory symlinks. Off by default: cycles are real. */\n readonly followSymlinks?: boolean | undefined;\n}\n\n/** True when the string contains glob syntax rather than being a literal path. */\nexport function isGlob(pattern: string): boolean {\n return /[*?[\\]{}]/.test(pattern);\n}\n\n/**\n * Compiles a glob to an anchored regular expression.\n *\n * `**` crosses directory separators; `*` and `?` do not. A trailing `/` or a\n * bare directory name matches everything beneath it, which is what people mean\n * when they write `--ignore drafts`.\n *\n * Nothing in the pipeline matches with this any more - see {@link compileGlob},\n * which gives the same answers without the backtracking. It stays for callers\n * who want a `RegExp`, and as the oracle the tests hold the automaton to.\n */\nexport function globToRegExp(pattern: string): RegExp {\n return new RegExp(`^${globSource(pattern)}$`, process.platform === 'win32' ? 'i' : '');\n}\n\nexport interface GlobOptions {\n /** Fold case. Defaults to the host's convention: on for Windows, off elsewhere. */\n readonly ignoreCase?: boolean | undefined;\n}\n\n/**\n * Compiles a glob to the automaton every matcher here uses.\n *\n * A glob has no nested quantifiers, and ADR-0017 took that to mean there was no\n * backtracking hazard in handing one to `RegExp`. Nobody had timed it. Each\n * `*` is a `[^/]*`, and against a subject that fails to match, a backtracking\n * engine tries every way of dividing it between them - the subject's length to\n * the power of the stars:\n *\n * ```text\n * RegExp here\n * **\\/*-*-*-*.md a 643-character hyphenated file name 2.3s 0.2ms\n * *-*-*-x a 10,000-character reference target 120s 0.9ms\n * ```\n *\n * The first is bounded by the filesystem, which caps a name at 255 bytes. The\n * second is not: `--ignore-ref` patterns are matched against targets read out\n * of documents, and a document is whatever somebody wrote.\n */\nexport function compileGlob(pattern: string, options: GlobOptions = {}): Matcher {\n const source = globSource(pattern);\n try {\n return compilePattern(`^${source}$`, { ignoreCase: options.ignoreCase ?? process.platform === 'win32' });\n } catch (error) {\n // The message describes an expression the user never wrote, so it is told\n // against the glob they did.\n throw new Error(`invalid glob \"${pattern}\": ${(error as Error).message}`);\n }\n}\n\n/** The expression a glob stands for, before anchors and flags. */\nfunction globSource(pattern: string): string {\n let source = '';\n let i = 0;\n const braces: number[] = [];\n\n while (i < pattern.length) {\n const ch = pattern[i] as string;\n\n if (ch === '*') {\n const doubled = pattern[i + 1] === '*';\n if (doubled) {\n const slashAfter = pattern[i + 2] === '/';\n // `a/**/b` must also match `a/b`, so the separator is folded in.\n source += slashAfter ? '(?:.*/)?' : '.*';\n i += slashAfter ? 3 : 2;\n } else {\n source += '[^/]*';\n i += 1;\n }\n continue;\n }\n\n if (ch === '?') {\n source += '[^/]';\n i += 1;\n continue;\n }\n\n if (ch === '[') {\n const close = pattern.indexOf(']', i + 1);\n if (close === -1) {\n source += '\\\\[';\n i += 1;\n continue;\n }\n const body = pattern.slice(i + 1, close);\n source += `[${body.startsWith('!') ? `^${escapeClass(body.slice(1))}` : escapeClass(body)}]`;\n i = close + 1;\n continue;\n }\n\n if (ch === '{') {\n braces.push(source.length);\n source += '(?:';\n i += 1;\n continue;\n }\n if (ch === '}' && braces.length > 0) {\n braces.pop();\n source += ')';\n i += 1;\n continue;\n }\n if (ch === ',' && braces.length > 0) {\n source += '|';\n i += 1;\n continue;\n }\n\n source += ch.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n i += 1;\n }\n\n // Otherwise reported as an unmatched parenthesis, in a glob that has none.\n if (braces.length > 0) throw new Error(`invalid glob \"${pattern}\": unclosed \"{\"`);\n return source;\n}\n\nfunction escapeClass(body: string): string {\n return body.replace(/[\\\\^\\]]/g, '\\\\$&');\n}\n\nexport interface GlobMatcher {\n (path: string): boolean;\n}\n\n/**\n * Builds a matcher from include patterns, honouring `!` negations.\n *\n * Later patterns win, so `docs/**\\/*.md` followed by `!docs/drafts/**` reads the\n * way a `.gitignore` does.\n */\nexport function createGlobMatcher(patterns: readonly string[]): GlobMatcher {\n const compiled = patterns.map((pattern) => {\n const negated = pattern.startsWith('!');\n const body = negated ? pattern.slice(1) : pattern;\n const normalised = normalisePosix(toPosix(body));\n // A bare directory means everything under it.\n const expanded = isGlob(normalised) ? normalised : `${normalised}/**`;\n return {\n negated,\n exact: isGlob(normalised) ? null : normalised.toLowerCase(),\n expression: compileGlob(expanded),\n direct: compileGlob(normalised),\n };\n });\n\n return (path: string): boolean => {\n let included = false;\n for (const entry of compiled) {\n const hit =\n entry.direct.test(path) ||\n entry.expression.test(path) ||\n (entry.exact !== null && entry.exact === path.toLowerCase());\n if (hit) included = !entry.negated;\n }\n return included;\n };\n}\n\n/**\n * Builds a predicate over reference *targets*, not paths.\n *\n * Separate from {@link createGlobMatcher} on purpose. That one is\n * path-oriented: it expands a bare name to `name/**` because a directory means\n * everything under it. A reference target has no such structure - `trap 55` is\n * a name, not a location - so a bare pattern must match it literally and\n * nothing else.\n *\n * Matching is case-insensitive on every platform. Path matching inherits the\n * host filesystem's case rules, which is right for paths and wrong here: a\n * repository's findings must not depend on which machine ran the check.\n *\n * Both sides are lower-cased, and that is the whole of the case rule. Folding\n * as well used to happen on Windows only, where it made U+00B5 (micro sign)\n * and U+03BC (mu) the same target - one machine's answer, which is what this\n * function exists not to give.\n */\nexport function createReferenceFilter(patterns: readonly string[]): (target: string) => boolean {\n if (patterns.length === 0) return () => false;\n const compiled = patterns\n .filter((pattern) => pattern.trim().length > 0)\n .map((pattern) => compileGlob(pattern.trim().toLowerCase(), { ignoreCase: false }));\n if (compiled.length === 0) return () => false;\n return (target: string): boolean => {\n const value = target.trim().toLowerCase();\n return compiled.some((expression) => expression.test(value));\n };\n}\n\n/** The literal directory prefix of a pattern, used to avoid walking the world. */\nexport function globBase(pattern: string): string {\n const normalised = normalisePosix(toPosix(pattern.startsWith('!') ? pattern.slice(1) : pattern));\n const segments = normalised.split('/');\n const literal: string[] = [];\n for (const segment of segments) {\n if (isGlob(segment)) break;\n literal.push(segment);\n }\n // The last literal segment may be the file itself rather than a directory.\n if (literal.length === segments.length && literal.length > 0) literal.pop();\n return literal.join('/');\n}\n\n/**\n * Walks the tree and returns every file matching the patterns.\n *\n * Directories are pruned as early as possible: a repository with a 2 GB\n * `node_modules` should cost nothing to scan.\n */\nexport async function walkFiles(options: WalkOptions): Promise<WalkedFile[]> {\n const root = toPosix(options.root).replace(/\\/+$/, '');\n const matcher = createGlobMatcher(options.patterns);\n const maxSize = options.maxFileSize ?? MAX_FILE_SIZE;\n\n // Ignores come in two shapes and both are documented. A bare name prunes any\n // directory called that, at any depth, the way a `.gitignore` line does.\n // Anything carrying a separator or glob syntax is matched against the\n // repository-relative path, which is what `--ignore \"docs/drafts/**\"` means -\n // and treating that as a directory name silently excluded nothing at all.\n const ignores = options.ignore ?? [];\n const ignoredNames = new Set([\n ...DEFAULT_IGNORED_DIRECTORIES,\n ...ignores.filter((pattern) => !isGlob(pattern) && !pattern.includes('/')),\n ]);\n const pathIgnores = ignores.filter((pattern) => isGlob(pattern) || pattern.includes('/'));\n const ignoreMatcher = pathIgnores.length > 0 ? createGlobMatcher(pathIgnores) : null;\n const excluded = (path: string): boolean => ignoreMatcher !== null && ignoreMatcher(path);\n\n // Only walk the directories the patterns can possibly reach.\n const bases = new Set<string>();\n for (const pattern of options.patterns) {\n if (pattern.startsWith('!')) continue;\n bases.add(globBase(pattern));\n }\n if (bases.size === 0) bases.add('');\n // Drop any base already contained in another: walking it again would only\n // repeat work. The empty base is the repository root, which contains\n // everything, so when it is present it is the only root worth walking.\n const roots = bases.has('')\n ? ['']\n : [...bases].filter((base) => ![...bases].some((other) => other !== base && base.startsWith(`${other}/`)));\n\n const out = new Map<string, WalkedFile>();\n const visited = new Set<string>();\n\n const walk = async (relative: string): Promise<void> => {\n const absolute = relative.length === 0 ? root : `${root}/${relative}`;\n if (visited.has(absolute)) return;\n visited.add(absolute);\n\n let entries;\n try {\n entries = await readdir(absolute, { withFileTypes: true });\n } catch {\n // An unreadable directory is not a reason to fail the whole run.\n return;\n }\n\n for (const entry of entries) {\n const child = relative.length === 0 ? entry.name : `${relative}/${entry.name}`;\n\n if (entry.isDirectory()) {\n // Pruning a whole subtree is an optimisation; a pattern that only\n // matches the files inside it is still honoured when they are filtered.\n if (ignoredNames.has(entry.name) || excluded(child)) continue;\n await walk(child);\n continue;\n }\n\n if (entry.isSymbolicLink()) {\n if (!options.followSymlinks) continue;\n try {\n const info = await stat(`${root}/${child}`);\n if (info.isDirectory()) {\n await walk(child);\n continue;\n }\n if (!matcher(child) || excluded(child) || info.size > maxSize) continue;\n out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });\n } catch {\n continue;\n }\n continue;\n }\n\n if (!entry.isFile()) continue;\n if (!matcher(child) || excluded(child)) continue;\n\n try {\n const info = await stat(`${root}/${child}`);\n if (info.size > maxSize) continue;\n out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });\n } catch {\n continue;\n }\n }\n };\n\n for (const base of roots) await walk(base);\n\n return [...out.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n}\n\n/** Resolves a repository-relative path against the root, in POSIX form. */\nexport function underRoot(root: string, relative: string): string {\n return joinPosix(toPosix(root), relative);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pattern matching and directory walking.\n *\n * A dependency-free glob, because pulling in a matcher for `docs/**\\/*.md` would\n * be the single largest thing in the package. The supported syntax is the\n * portion people actually type - `**`, `*`, `?`, `{a,b}`, `[abc]`, and a leading\n * `!` for negation - and everything is matched against repository-relative\n * POSIX paths so results do not depend on the host platform.\n */\n\nimport { readdir, stat } from 'node:fs/promises';\n\nimport { isAbsolutePath, joinPosix, normalisePosix, toPosix } from './paths.js';\nimport { compilePattern, type Matcher } from './regex.js';\n\n/** Directories skipped unless a pattern explicitly names them. */\nexport const DEFAULT_IGNORED_DIRECTORIES: readonly string[] = Object.freeze([\n '.git',\n '.hg',\n '.svn',\n '.cache',\n '.next',\n '.nuxt',\n '.turbo',\n '.venv',\n 'node_modules',\n 'bower_components',\n 'vendor',\n 'dist',\n 'build',\n 'out',\n 'target',\n 'coverage',\n '__pycache__',\n]);\n\n/** Files larger than this are skipped: a specification is not a binary. */\nexport const MAX_FILE_SIZE = 4 * 1024 * 1024;\n\nexport interface WalkedFile {\n /** Repository-relative POSIX path. */\n readonly path: string;\n /** Absolute host path, for reading. */\n readonly absolute: string;\n readonly size: number;\n}\n\nexport interface WalkOptions {\n readonly root: string;\n readonly patterns: readonly string[];\n readonly ignore?: readonly string[] | undefined;\n readonly maxFileSize?: number | undefined;\n /** Follow directory symlinks. Off by default: cycles are real. */\n readonly followSymlinks?: boolean | undefined;\n}\n\n/** True when the string contains glob syntax rather than being a literal path. */\nexport function isGlob(pattern: string): boolean {\n return /[*?[\\]{}]/.test(pattern);\n}\n\n/**\n * Compiles a glob to an anchored regular expression.\n *\n * `**` crosses directory separators; `*` and `?` do not. A trailing `/` or a\n * bare directory name matches everything beneath it, which is what people mean\n * when they write `--ignore drafts`.\n *\n * Nothing in the pipeline matches with this any more - see {@link compileGlob},\n * which gives the same answers without the backtracking. It stays for callers\n * who want a `RegExp`, and as the oracle the tests hold the automaton to.\n */\nexport function globToRegExp(pattern: string): RegExp {\n return new RegExp(`^${globSource(pattern)}$`, process.platform === 'win32' ? 'i' : '');\n}\n\nexport interface GlobOptions {\n /** Fold case. Defaults to the host's convention: on for Windows, off elsewhere. */\n readonly ignoreCase?: boolean | undefined;\n}\n\n/**\n * Compiles a glob to the automaton every matcher here uses.\n *\n * A glob has no nested quantifiers, and ADR-0017 took that to mean there was no\n * backtracking hazard in handing one to `RegExp`. Nobody had timed it. Each\n * `*` is a `[^/]*`, and against a subject that fails to match, a backtracking\n * engine tries every way of dividing it between them - the subject's length to\n * the power of the stars:\n *\n * ```text\n * RegExp here\n * **\\/*-*-*-*.md a 643-character hyphenated file name 2.3s 0.2ms\n * *-*-*-x a 10,000-character reference target 120s 0.9ms\n * ```\n *\n * The first is bounded by the filesystem, which caps a name at 255 bytes. The\n * second is not: `--ignore-ref` patterns are matched against targets read out\n * of documents, and a document is whatever somebody wrote.\n */\nexport function compileGlob(pattern: string, options: GlobOptions = {}): Matcher {\n const source = globSource(pattern);\n try {\n return compilePattern(`^${source}$`, { ignoreCase: options.ignoreCase ?? process.platform === 'win32' });\n } catch (error) {\n // The message describes an expression the user never wrote, so it is told\n // against the glob they did.\n throw new Error(`invalid glob \"${pattern}\": ${(error as Error).message}`);\n }\n}\n\n/** The expression a glob stands for, before anchors and flags. */\nfunction globSource(pattern: string): string {\n let source = '';\n let i = 0;\n const braces: number[] = [];\n\n while (i < pattern.length) {\n const ch = pattern[i] as string;\n\n if (ch === '*') {\n const doubled = pattern[i + 1] === '*';\n if (doubled) {\n const slashAfter = pattern[i + 2] === '/';\n // `a/**/b` must also match `a/b`, so the separator is folded in.\n source += slashAfter ? '(?:.*/)?' : '.*';\n i += slashAfter ? 3 : 2;\n } else {\n source += '[^/]*';\n i += 1;\n }\n continue;\n }\n\n if (ch === '?') {\n source += '[^/]';\n i += 1;\n continue;\n }\n\n if (ch === '[') {\n const close = pattern.indexOf(']', i + 1);\n if (close === -1) {\n source += '\\\\[';\n i += 1;\n continue;\n }\n const body = pattern.slice(i + 1, close);\n source += `[${body.startsWith('!') ? `^${escapeClass(body.slice(1))}` : escapeClass(body)}]`;\n i = close + 1;\n continue;\n }\n\n if (ch === '{') {\n braces.push(source.length);\n source += '(?:';\n i += 1;\n continue;\n }\n if (ch === '}' && braces.length > 0) {\n braces.pop();\n source += ')';\n i += 1;\n continue;\n }\n if (ch === ',' && braces.length > 0) {\n source += '|';\n i += 1;\n continue;\n }\n\n source += ch.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n i += 1;\n }\n\n // Otherwise reported as an unmatched parenthesis, in a glob that has none.\n if (braces.length > 0) throw new Error(`invalid glob \"${pattern}\": unclosed \"{\"`);\n return source;\n}\n\nfunction escapeClass(body: string): string {\n return body.replace(/[\\\\^\\]]/g, '\\\\$&');\n}\n\nexport interface GlobMatcher {\n (path: string): boolean;\n}\n\n/**\n * Builds a matcher from include patterns, honouring `!` negations.\n *\n * Later patterns win, so `docs/**\\/*.md` followed by `!docs/drafts/**` reads the\n * way a `.gitignore` does.\n */\nexport function createGlobMatcher(patterns: readonly string[]): GlobMatcher {\n const compiled = patterns.map((pattern) => {\n const negated = pattern.startsWith('!');\n const body = negated ? pattern.slice(1) : pattern;\n const normalised = normalisePosix(toPosix(body));\n // A bare directory means everything under it.\n const expanded = isGlob(normalised) ? normalised : `${normalised}/**`;\n return {\n negated,\n exact: isGlob(normalised) ? null : normalised.toLowerCase(),\n expression: compileGlob(expanded),\n direct: compileGlob(normalised),\n };\n });\n\n return (path: string): boolean => {\n let included = false;\n for (const entry of compiled) {\n const hit =\n entry.direct.test(path) ||\n entry.expression.test(path) ||\n (entry.exact !== null && entry.exact === path.toLowerCase());\n if (hit) included = !entry.negated;\n }\n return included;\n };\n}\n\n/**\n * Builds a predicate over reference *targets*, not paths.\n *\n * Separate from {@link createGlobMatcher} on purpose. That one is\n * path-oriented: it expands a bare name to `name/**` because a directory means\n * everything under it. A reference target has no such structure - `trap 55` is\n * a name, not a location - so a bare pattern must match it literally and\n * nothing else.\n *\n * Matching is case-insensitive on every platform. Path matching inherits the\n * host filesystem's case rules, which is right for paths and wrong here: a\n * repository's findings must not depend on which machine ran the check.\n *\n * Both sides are lower-cased, and that is the whole of the case rule. Folding\n * as well used to happen on Windows only, where it made U+00B5 (micro sign)\n * and U+03BC (mu) the same target - one machine's answer, which is what this\n * function exists not to give.\n */\nexport function createReferenceFilter(patterns: readonly string[]): (target: string) => boolean {\n if (patterns.length === 0) return () => false;\n const compiled = patterns\n .filter((pattern) => pattern.trim().length > 0)\n .map((pattern) => compileGlob(pattern.trim().toLowerCase(), { ignoreCase: false }));\n if (compiled.length === 0) return () => false;\n return (target: string): boolean => {\n const value = target.trim().toLowerCase();\n return compiled.some((expression) => expression.test(value));\n };\n}\n\n/** The literal directory prefix of a pattern, used to avoid walking the world. */\nexport function globBase(pattern: string): string {\n const normalised = normalisePosix(toPosix(pattern.startsWith('!') ? pattern.slice(1) : pattern));\n const segments = normalised.split('/');\n const literal: string[] = [];\n for (const segment of segments) {\n if (isGlob(segment)) break;\n literal.push(segment);\n }\n // The last literal segment may be the file itself rather than a directory.\n if (literal.length === segments.length && literal.length > 0) literal.pop();\n return literal.join('/');\n}\n\n/**\n * Walks the tree and returns every file matching the patterns.\n *\n * Directories are pruned as early as possible: a repository with a 2 GB\n * `node_modules` should cost nothing to scan.\n */\nexport async function walkFiles(options: WalkOptions): Promise<WalkedFile[]> {\n const root = toPosix(options.root).replace(/\\/+$/, '');\n const matcher = createGlobMatcher(options.patterns);\n const maxSize = options.maxFileSize ?? MAX_FILE_SIZE;\n\n // Ignores come in two shapes and both are documented. A bare name prunes any\n // directory called that, at any depth, the way a `.gitignore` line does.\n // Anything carrying a separator or glob syntax is matched against the\n // repository-relative path, which is what `--ignore \"docs/drafts/**\"` means -\n // and treating that as a directory name silently excluded nothing at all.\n const ignores = options.ignore ?? [];\n const ignoredNames = new Set([\n ...DEFAULT_IGNORED_DIRECTORIES,\n ...ignores.filter((pattern) => !isGlob(pattern) && !pattern.includes('/')),\n ]);\n const pathIgnores = ignores.filter((pattern) => isGlob(pattern) || pattern.includes('/'));\n const ignoreMatcher = pathIgnores.length > 0 ? createGlobMatcher(pathIgnores) : null;\n const excluded = (path: string): boolean => ignoreMatcher !== null && ignoreMatcher(path);\n\n // Only walk the directories the patterns can possibly reach.\n const bases = new Set<string>();\n for (const pattern of options.patterns) {\n if (pattern.startsWith('!')) continue;\n bases.add(globBase(pattern));\n }\n if (bases.size === 0) bases.add('');\n // Drop any base already contained in another: walking it again would only\n // repeat work. The empty base is the repository root, which contains\n // everything, so when it is present it is the only root worth walking.\n const roots = bases.has('')\n ? ['']\n : [...bases].filter((base) => ![...bases].some((other) => other !== base && base.startsWith(`${other}/`)));\n\n const out = new Map<string, WalkedFile>();\n const visited = new Set<string>();\n\n const walk = async (relative: string): Promise<void> => {\n const absolute = relative.length === 0 ? root : `${root}/${relative}`;\n if (visited.has(absolute)) return;\n visited.add(absolute);\n\n let entries;\n try {\n entries = await readdir(absolute, { withFileTypes: true });\n } catch {\n // An unreadable directory is not a reason to fail the whole run.\n return;\n }\n\n for (const entry of entries) {\n const child = relative.length === 0 ? entry.name : `${relative}/${entry.name}`;\n\n if (entry.isDirectory()) {\n // Pruning a whole subtree is an optimisation; a pattern that only\n // matches the files inside it is still honoured when they are filtered.\n if (ignoredNames.has(entry.name) || excluded(child)) continue;\n await walk(child);\n continue;\n }\n\n if (entry.isSymbolicLink()) {\n if (!options.followSymlinks) continue;\n try {\n const info = await stat(`${root}/${child}`);\n if (info.isDirectory()) {\n await walk(child);\n continue;\n }\n if (!matcher(child) || excluded(child) || info.size > maxSize) continue;\n out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });\n } catch {\n continue;\n }\n continue;\n }\n\n if (!entry.isFile()) continue;\n if (!matcher(child) || excluded(child)) continue;\n\n try {\n const info = await stat(`${root}/${child}`);\n if (info.size > maxSize) continue;\n out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });\n } catch {\n continue;\n }\n }\n };\n\n for (const base of roots) await walk(base);\n\n return [...out.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n}\n\n/**\n * Resolves a repository-relative path against the root, in POSIX form.\n *\n * An absolute path already names one place, and joining it under the root names\n * another: `--baseline /tmp/b.json` read `<root>/tmp/b.json`, found nothing\n * there, and accepted nothing without saying so.\n */\nexport function underRoot(root: string, relative: string): string {\n if (isAbsolutePath(relative)) return toPosix(relative);\n return joinPosix(toPosix(root), relative);\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -62,6 +62,6 @@ export { compileGlob, createGlobMatcher, createReferenceFilter, globBase, globTo
|
|
|
62
62
|
export type { GlobMatcher, GlobOptions, WalkedFile, WalkOptions } from './glob.js';
|
|
63
63
|
export { compareRefs, createLineIndex, formatRef, refOf, spanOf } from './source.js';
|
|
64
64
|
export type { LineIndex } from './source.js';
|
|
65
|
-
export { basenamePosix, dirnamePosix, extnamePosix, joinPosix, normalisePosix, resolveFrom, toPosix, } from './paths.js';
|
|
65
|
+
export { basenamePosix, dirnamePosix, extnamePosix, isAbsolutePath, joinPosix, normalisePosix, resolveFrom, toPosix, } from './paths.js';
|
|
66
66
|
export { isProjectRule, DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF, PROJECT_RULE_PREFIX } from './types.js';
|
|
67
67
|
export type { AnalysisSummary, AnyRuleId, DanglingReason, DanglingRef, Diagnostic, Disposition, DocumentNode, Edge, EdgeKind, EdgeOrigin, EdgeTraits, ItemNode, MisreadKey, NodeKind, Openness, ParseProblem, Phase, Position, Receptivity, ProjectRuleId, RelatedLocation, RuleId, Severity, SourceRef, Span, SpecNode, StateSignal, SuppressedRef, } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,6 @@ export { resolveItemState, KNOWN_STATE_WORDS } from './state.js';
|
|
|
42
42
|
export { familyFromPath, identify, isDocumentTarget, isExternal, looksLikePath, normaliseRef, parseBareRef, parsePrefixedRef, splitAnchor, ID_KEYS, } from './identity.js';
|
|
43
43
|
export { compileGlob, createGlobMatcher, createReferenceFilter, globBase, globToRegExp, isGlob, walkFiles, DEFAULT_IGNORED_DIRECTORIES, MAX_FILE_SIZE, } from './glob.js';
|
|
44
44
|
export { compareRefs, createLineIndex, formatRef, refOf, spanOf } from './source.js';
|
|
45
|
-
export { basenamePosix, dirnamePosix, extnamePosix, joinPosix, normalisePosix, resolveFrom, toPosix, } from './paths.js';
|
|
45
|
+
export { basenamePosix, dirnamePosix, extnamePosix, isAbsolutePath, joinPosix, normalisePosix, resolveFrom, toPosix, } from './paths.js';
|
|
46
46
|
export { isProjectRule, DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF, PROJECT_RULE_PREFIX } from './types.js';
|
|
47
47
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Programmatic API.\n *\n * The CLI is a shell over `analyse`; anything the CLI can do, a build script, a\n * custom reporter or an editor extension can do too.\n *\n * ```ts\n * import { analyse, formatReport } from '@descent-vtt/spec-graph';\n *\n * const result = await analyse({ root: process.cwd(), patterns: ['docs/**\\/*.md'] });\n * if (!result.ok) console.error(formatReport(result, { color: true }));\n * ```\n *\n * For a corpus that is already in memory - a monorepo tool, a language server,\n * a test - skip the filesystem entirely:\n *\n * ```ts\n * import { analyseSources, query } from '@descent-vtt/spec-graph';\n *\n * const { graph } = analyseSources([{ path: 'adr/0001.md', text: '...' }]);\n * const ghosts = query(graph, 'item[openness=open] -delegates-to-> document[phase=retired]');\n * ```\n */\n\nexport {\n applyBaseline,\n formatBaseline,\n fingerprintOf,\n parseBaseline,\n BASELINE_VERSION,\n EMPTY_BASELINE,\n} from './baseline.js';\nexport type { Baseline, BaselineEntry, BaselineOutcome, ParsedBaseline, StaleEntry } from './baseline.js';\n\nexport { discoverConfig, loadConfig, parseConfig, CONFIG_FILES, CONFIG_PACKAGE_KEY } from './config.js';\nexport type { DiscoveredConfig, DiscoveryIO, LoadedConfig, SpecGraphConfig } from './config.js';\n\nexport { main, parseArgs, HELP, UsageError, EXIT_OK, EXIT_FAILED, EXIT_ERROR } from './cli.js';\nexport type { CliIO, CliOptions, Command } from './cli.js';\n\nexport {\n analyse,\n analyseSources,\n createFamilyFilter,\n createHistoryMatcher,\n withDiagnostics,\n DEFAULT_CONCURRENCY,\n DEFAULT_PATTERNS,\n} from './runner.js';\nexport type { AnalyseOptions, AnalyseSourcesOptions, AnalysisResult, Source } from './runner.js';\n\nexport { buildGraph, LOAD_BEARING_EDGES, OBLIGATION_EDGES } from './graph.js';\nexport type { SpecGraph } from './graph.js';\n\nexport {\n attributesOf,\n execute,\n matches,\n parseQuery,\n pattern,\n query,\n renderMatch,\n QueryError,\n DEFAULT_MATCH_LIMIT,\n SELECTOR_KEYS,\n} from './select.js';\n\nexport { compilePattern, PatternError } from './regex.js';\nexport type { Matcher, PatternOptions } from './regex.js';\nexport type {\n ExecuteOptions,\n Match,\n NodeMatcher,\n Operator,\n Predicate,\n QuerySpec,\n StepSpec,\n} from './select.js';\n\nexport {\n resolveStrict,\n runRules,\n sortDiagnostics,\n DEFAULT_SEVERITIES,\n RULE_DECISIONS,\n RULE_DESCRIPTIONS,\n RULE_IDS,\n RULE_QUERIES,\n} from './rules.js';\nexport type { RuleOptions } from './rules.js';\n\nexport { compileProjectRules, renderTemplate } from './project-rules.js';\nexport type { CompiledProjectRules, ProjectRule } from './project-rules.js';\n\nexport {\n createPainter,\n formatGraph,\n formatJson,\n formatMarkdown,\n formatReport,\n formatSarif,\n shouldUseAscii,\n shouldUseColor,\n} from './report.js';\nexport type { ColorEnvironment, GraphExportOptions, GraphFormat, Painter, ReporterOptions } from './report.js';\n\nexport {\n extractDocument,\n extractSpecifications,\n classifyReference,\n inheritableFrontMatter,\n sectionPathAt,\n} from './extract.js';\nexport { findSpecificationRegions, readRegionStatus, regionAt } from './sections.js';\nexport type { RegionStatus, SpecificationRegion } from './sections.js';\nexport type { Classification, ExtractedDocument, ExtractInput, ReferenceCandidate } from './extract.js';\n\nexport { resolveCorpus, documentOf } from './resolve.js';\nexport type { ResolvedCorpus, ResolveOptions } from './resolve.js';\n\nexport { scanMarkdown, slugify } from './markdown.js';\nexport type {\n FrontMatter,\n Heading,\n HtmlComment,\n Link,\n LinkForm,\n ListItem,\n Range,\n ScannedDocument,\n ScannedLine,\n} from './markdown.js';\n\nexport { parseDirectives, attr, attrList, bindItemDirectives, directiveFor } from './directives.js';\nexport type { Attribute, Directive, DirectiveName } from './directives.js';\n\nexport { parseFrontMatter, toRecord, valuesOf } from './yaml.js';\nexport type { YamlEntry } from './yaml.js';\n\nexport {\n isRetired,\n isStatusHeading,\n normaliseStatus,\n phaseFromPath,\n phaseOf,\n receptivityOf,\n supersessionTargetsIn,\n STATUS_KEYS,\n} from './lifecycle.js';\n\nexport { resolveItemState, KNOWN_STATE_WORDS } from './state.js';\nexport type { ResolvedState, StateInput } from './state.js';\n\nexport {\n familyFromPath,\n identify,\n isDocumentTarget,\n isExternal,\n looksLikePath,\n normaliseRef,\n parseBareRef,\n parsePrefixedRef,\n splitAnchor,\n ID_KEYS,\n} from './identity.js';\nexport type { DocumentIdentity, IdentityInput } from './identity.js';\n\nexport {\n compileGlob,\n createGlobMatcher,\n createReferenceFilter,\n globBase,\n globToRegExp,\n isGlob,\n walkFiles,\n DEFAULT_IGNORED_DIRECTORIES,\n MAX_FILE_SIZE,\n} from './glob.js';\nexport type { GlobMatcher, GlobOptions, WalkedFile, WalkOptions } from './glob.js';\n\nexport { compareRefs, createLineIndex, formatRef, refOf, spanOf } from './source.js';\nexport type { LineIndex } from './source.js';\n\nexport {\n basenamePosix,\n dirnamePosix,\n extnamePosix,\n joinPosix,\n normalisePosix,\n resolveFrom,\n toPosix,\n} from './paths.js';\n\nexport { isProjectRule, DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF, PROJECT_RULE_PREFIX } from './types.js';\nexport type {\n AnalysisSummary,\n AnyRuleId,\n DanglingReason,\n DanglingRef,\n Diagnostic,\n Disposition,\n DocumentNode,\n Edge,\n EdgeKind,\n EdgeOrigin,\n EdgeTraits,\n ItemNode,\n MisreadKey,\n NodeKind,\n Openness,\n ParseProblem,\n Phase,\n Position,\n Receptivity,\n ProjectRuleId,\n RelatedLocation,\n RuleId,\n Severity,\n SourceRef,\n Span,\n SpecNode,\n StateSignal,\n SuppressedRef,\n} from './types.js';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Programmatic API.\n *\n * The CLI is a shell over `analyse`; anything the CLI can do, a build script, a\n * custom reporter or an editor extension can do too.\n *\n * ```ts\n * import { analyse, formatReport } from '@descent-vtt/spec-graph';\n *\n * const result = await analyse({ root: process.cwd(), patterns: ['docs/**\\/*.md'] });\n * if (!result.ok) console.error(formatReport(result, { color: true }));\n * ```\n *\n * For a corpus that is already in memory - a monorepo tool, a language server,\n * a test - skip the filesystem entirely:\n *\n * ```ts\n * import { analyseSources, query } from '@descent-vtt/spec-graph';\n *\n * const { graph } = analyseSources([{ path: 'adr/0001.md', text: '...' }]);\n * const ghosts = query(graph, 'item[openness=open] -delegates-to-> document[phase=retired]');\n * ```\n */\n\nexport {\n applyBaseline,\n formatBaseline,\n fingerprintOf,\n parseBaseline,\n BASELINE_VERSION,\n EMPTY_BASELINE,\n} from './baseline.js';\nexport type { Baseline, BaselineEntry, BaselineOutcome, ParsedBaseline, StaleEntry } from './baseline.js';\n\nexport { discoverConfig, loadConfig, parseConfig, CONFIG_FILES, CONFIG_PACKAGE_KEY } from './config.js';\nexport type { DiscoveredConfig, DiscoveryIO, LoadedConfig, SpecGraphConfig } from './config.js';\n\nexport { main, parseArgs, HELP, UsageError, EXIT_OK, EXIT_FAILED, EXIT_ERROR } from './cli.js';\nexport type { CliIO, CliOptions, Command } from './cli.js';\n\nexport {\n analyse,\n analyseSources,\n createFamilyFilter,\n createHistoryMatcher,\n withDiagnostics,\n DEFAULT_CONCURRENCY,\n DEFAULT_PATTERNS,\n} from './runner.js';\nexport type { AnalyseOptions, AnalyseSourcesOptions, AnalysisResult, Source } from './runner.js';\n\nexport { buildGraph, LOAD_BEARING_EDGES, OBLIGATION_EDGES } from './graph.js';\nexport type { SpecGraph } from './graph.js';\n\nexport {\n attributesOf,\n execute,\n matches,\n parseQuery,\n pattern,\n query,\n renderMatch,\n QueryError,\n DEFAULT_MATCH_LIMIT,\n SELECTOR_KEYS,\n} from './select.js';\n\nexport { compilePattern, PatternError } from './regex.js';\nexport type { Matcher, PatternOptions } from './regex.js';\nexport type {\n ExecuteOptions,\n Match,\n NodeMatcher,\n Operator,\n Predicate,\n QuerySpec,\n StepSpec,\n} from './select.js';\n\nexport {\n resolveStrict,\n runRules,\n sortDiagnostics,\n DEFAULT_SEVERITIES,\n RULE_DECISIONS,\n RULE_DESCRIPTIONS,\n RULE_IDS,\n RULE_QUERIES,\n} from './rules.js';\nexport type { RuleOptions } from './rules.js';\n\nexport { compileProjectRules, renderTemplate } from './project-rules.js';\nexport type { CompiledProjectRules, ProjectRule } from './project-rules.js';\n\nexport {\n createPainter,\n formatGraph,\n formatJson,\n formatMarkdown,\n formatReport,\n formatSarif,\n shouldUseAscii,\n shouldUseColor,\n} from './report.js';\nexport type { ColorEnvironment, GraphExportOptions, GraphFormat, Painter, ReporterOptions } from './report.js';\n\nexport {\n extractDocument,\n extractSpecifications,\n classifyReference,\n inheritableFrontMatter,\n sectionPathAt,\n} from './extract.js';\nexport { findSpecificationRegions, readRegionStatus, regionAt } from './sections.js';\nexport type { RegionStatus, SpecificationRegion } from './sections.js';\nexport type { Classification, ExtractedDocument, ExtractInput, ReferenceCandidate } from './extract.js';\n\nexport { resolveCorpus, documentOf } from './resolve.js';\nexport type { ResolvedCorpus, ResolveOptions } from './resolve.js';\n\nexport { scanMarkdown, slugify } from './markdown.js';\nexport type {\n FrontMatter,\n Heading,\n HtmlComment,\n Link,\n LinkForm,\n ListItem,\n Range,\n ScannedDocument,\n ScannedLine,\n} from './markdown.js';\n\nexport { parseDirectives, attr, attrList, bindItemDirectives, directiveFor } from './directives.js';\nexport type { Attribute, Directive, DirectiveName } from './directives.js';\n\nexport { parseFrontMatter, toRecord, valuesOf } from './yaml.js';\nexport type { YamlEntry } from './yaml.js';\n\nexport {\n isRetired,\n isStatusHeading,\n normaliseStatus,\n phaseFromPath,\n phaseOf,\n receptivityOf,\n supersessionTargetsIn,\n STATUS_KEYS,\n} from './lifecycle.js';\n\nexport { resolveItemState, KNOWN_STATE_WORDS } from './state.js';\nexport type { ResolvedState, StateInput } from './state.js';\n\nexport {\n familyFromPath,\n identify,\n isDocumentTarget,\n isExternal,\n looksLikePath,\n normaliseRef,\n parseBareRef,\n parsePrefixedRef,\n splitAnchor,\n ID_KEYS,\n} from './identity.js';\nexport type { DocumentIdentity, IdentityInput } from './identity.js';\n\nexport {\n compileGlob,\n createGlobMatcher,\n createReferenceFilter,\n globBase,\n globToRegExp,\n isGlob,\n walkFiles,\n DEFAULT_IGNORED_DIRECTORIES,\n MAX_FILE_SIZE,\n} from './glob.js';\nexport type { GlobMatcher, GlobOptions, WalkedFile, WalkOptions } from './glob.js';\n\nexport { compareRefs, createLineIndex, formatRef, refOf, spanOf } from './source.js';\nexport type { LineIndex } from './source.js';\n\nexport {\n basenamePosix,\n dirnamePosix,\n extnamePosix,\n isAbsolutePath,\n joinPosix,\n normalisePosix,\n resolveFrom,\n toPosix,\n} from './paths.js';\n\nexport { isProjectRule, DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF, PROJECT_RULE_PREFIX } from './types.js';\nexport type {\n AnalysisSummary,\n AnyRuleId,\n DanglingReason,\n DanglingRef,\n Diagnostic,\n Disposition,\n DocumentNode,\n Edge,\n EdgeKind,\n EdgeOrigin,\n EdgeTraits,\n ItemNode,\n MisreadKey,\n NodeKind,\n Openness,\n ParseProblem,\n Phase,\n Position,\n Receptivity,\n ProjectRuleId,\n RelatedLocation,\n RuleId,\n Severity,\n SourceRef,\n Span,\n SpecNode,\n StateSignal,\n SuppressedRef,\n} from './types.js';\n"]}
|
package/dist/paths.d.ts
CHANGED
|
@@ -8,6 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
/** Converts a host path to POSIX form. */
|
|
10
10
|
export declare function toPosix(value: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Whether a path names a place outright rather than a place relative to one.
|
|
13
|
+
*
|
|
14
|
+
* Both platforms' spellings, whichever this is running on: one repository is
|
|
15
|
+
* read on a Windows checkout and in Linux CI, so a path is absolute where it
|
|
16
|
+
* was written rather than where it is being read. `C:docs` counts, because it
|
|
17
|
+
* is relative to a drive rather than to a root, and prefixing it with a root
|
|
18
|
+
* produces a third place that is neither.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isAbsolutePath(value: string): boolean;
|
|
11
21
|
/** Removes `.` and `..` segments. Leading `..` are preserved. */
|
|
12
22
|
export declare function normalisePosix(value: string): string;
|
|
13
23
|
/** The directory containing `value`, or `''` at the root. */
|
package/dist/paths.js
CHANGED
|
@@ -10,6 +10,19 @@
|
|
|
10
10
|
export function toPosix(value) {
|
|
11
11
|
return value.replace(/\\/g, '/');
|
|
12
12
|
}
|
|
13
|
+
const ABSOLUTE = /^(?:[/\\]|[A-Za-z]:)/;
|
|
14
|
+
/**
|
|
15
|
+
* Whether a path names a place outright rather than a place relative to one.
|
|
16
|
+
*
|
|
17
|
+
* Both platforms' spellings, whichever this is running on: one repository is
|
|
18
|
+
* read on a Windows checkout and in Linux CI, so a path is absolute where it
|
|
19
|
+
* was written rather than where it is being read. `C:docs` counts, because it
|
|
20
|
+
* is relative to a drive rather than to a root, and prefixing it with a root
|
|
21
|
+
* produces a third place that is neither.
|
|
22
|
+
*/
|
|
23
|
+
export function isAbsolutePath(value) {
|
|
24
|
+
return ABSOLUTE.test(value);
|
|
25
|
+
}
|
|
13
26
|
/** Removes `.` and `..` segments. Leading `..` are preserved. */
|
|
14
27
|
export function normalisePosix(value) {
|
|
15
28
|
const absolute = value.startsWith('/');
|
package/dist/paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,0CAA0C;AAC1C,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACnC,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG;YAAE,SAAS;QAChD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACjC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,KAAK,IAAI;gBAAE,GAAG,CAAC,GAAG,EAAE,CAAC;iBAC1C,IAAI,CAAC,QAAQ;gBAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnC,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpB,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;AAC1C,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACnD,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,SAAS,CAAC,GAAG,QAA2B;IACtD,OAAO,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACpF,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,MAAc;IACtD,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;AAC/C,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;AACvD,CAAC","sourcesContent":["/**\n * POSIX path arithmetic, independent of the host platform.\n *\n * Every path inside spec-graph is a repository-relative POSIX path, on Windows\n * as much as on Linux. Node's `path` module would happily produce\n * `docs\\adr\\0007.md` on one machine and `docs/adr/0007.md` on another, and a\n * graph whose node identities depend on which laptop built it is not a graph.\n */\n\n/** Converts a host path to POSIX form. */\nexport function toPosix(value: string): string {\n return value.replace(/\\\\/g, '/');\n}\n\n/** Removes `.` and `..` segments. Leading `..` are preserved. */\nexport function normalisePosix(value: string): string {\n const absolute = value.startsWith('/');\n const out: string[] = [];\n for (const segment of value.split('/')) {\n if (segment === '' || segment === '.') continue;\n if (segment === '..') {\n const last = out[out.length - 1];\n if (out.length > 0 && last !== '..') out.pop();\n else if (!absolute) out.push('..');\n continue;\n }\n out.push(segment);\n }\n const joined = out.join('/');\n return absolute ? `/${joined}` : joined;\n}\n\n/** The directory containing `value`, or `''` at the root. */\nexport function dirnamePosix(value: string): string {\n const slash = value.lastIndexOf('/');\n return slash === -1 ? '' : value.slice(0, slash);\n}\n\n/** The final segment of `value`. */\nexport function basenamePosix(value: string): string {\n return value.slice(value.lastIndexOf('/') + 1);\n}\n\n/** Joins segments and normalises the result. */\nexport function joinPosix(...segments: readonly string[]): string {\n return normalisePosix(segments.filter((segment) => segment.length > 0).join('/'));\n}\n\n/** Resolves `target` against the directory holding `from`. */\nexport function resolveFrom(from: string, target: string): string {\n if (target.startsWith('/')) return normalisePosix(target);\n return joinPosix(dirnamePosix(from), target);\n}\n\n/** The extension including the dot, lower-cased, or `''`. */\nexport function extnamePosix(value: string): string {\n const base = basenamePosix(value);\n const dot = base.lastIndexOf('.');\n return dot <= 0 ? '' : base.slice(dot).toLowerCase();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,0CAA0C;AAC1C,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,QAAQ,GAAG,sBAAsB,CAAC;AAExC;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG;YAAE,SAAS;QAChD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACjC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,KAAK,IAAI;gBAAE,GAAG,CAAC,GAAG,EAAE,CAAC;iBAC1C,IAAI,CAAC,QAAQ;gBAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnC,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpB,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;AAC1C,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACnD,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,SAAS,CAAC,GAAG,QAA2B;IACtD,OAAO,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACpF,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,MAAc;IACtD,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;AAC/C,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;AACvD,CAAC","sourcesContent":["/**\n * POSIX path arithmetic, independent of the host platform.\n *\n * Every path inside spec-graph is a repository-relative POSIX path, on Windows\n * as much as on Linux. Node's `path` module would happily produce\n * `docs\\adr\\0007.md` on one machine and `docs/adr/0007.md` on another, and a\n * graph whose node identities depend on which laptop built it is not a graph.\n */\n\n/** Converts a host path to POSIX form. */\nexport function toPosix(value: string): string {\n return value.replace(/\\\\/g, '/');\n}\n\nconst ABSOLUTE = /^(?:[/\\\\]|[A-Za-z]:)/;\n\n/**\n * Whether a path names a place outright rather than a place relative to one.\n *\n * Both platforms' spellings, whichever this is running on: one repository is\n * read on a Windows checkout and in Linux CI, so a path is absolute where it\n * was written rather than where it is being read. `C:docs` counts, because it\n * is relative to a drive rather than to a root, and prefixing it with a root\n * produces a third place that is neither.\n */\nexport function isAbsolutePath(value: string): boolean {\n return ABSOLUTE.test(value);\n}\n\n/** Removes `.` and `..` segments. Leading `..` are preserved. */\nexport function normalisePosix(value: string): string {\n const absolute = value.startsWith('/');\n const out: string[] = [];\n for (const segment of value.split('/')) {\n if (segment === '' || segment === '.') continue;\n if (segment === '..') {\n const last = out[out.length - 1];\n if (out.length > 0 && last !== '..') out.pop();\n else if (!absolute) out.push('..');\n continue;\n }\n out.push(segment);\n }\n const joined = out.join('/');\n return absolute ? `/${joined}` : joined;\n}\n\n/** The directory containing `value`, or `''` at the root. */\nexport function dirnamePosix(value: string): string {\n const slash = value.lastIndexOf('/');\n return slash === -1 ? '' : value.slice(0, slash);\n}\n\n/** The final segment of `value`. */\nexport function basenamePosix(value: string): string {\n return value.slice(value.lastIndexOf('/') + 1);\n}\n\n/** Joins segments and normalises the result. */\nexport function joinPosix(...segments: readonly string[]): string {\n return normalisePosix(segments.filter((segment) => segment.length > 0).join('/'));\n}\n\n/** Resolves `target` against the directory holding `from`. */\nexport function resolveFrom(from: string, target: string): string {\n if (target.startsWith('/')) return normalisePosix(target);\n return joinPosix(dirnamePosix(from), target);\n}\n\n/** The extension including the dot, lower-cased, or `''`. */\nexport function extnamePosix(value: string): string {\n const base = basenamePosix(value);\n const dot = base.lastIndexOf('.');\n return dot <= 0 ? '' : base.slice(dot).toLowerCase();\n}\n"]}
|
package/package.json
CHANGED