@descent-vtt/spec-graph 0.19.0 → 0.20.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 +68 -0
- package/README.md +28 -8
- package/dist/cli.js +34 -9
- package/dist/cli.js.map +1 -1
- package/dist/extract.js +36 -3
- package/dist/extract.js.map +1 -1
- package/dist/paths.d.ts +12 -0
- package/dist/paths.js +14 -0
- package/dist/paths.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,74 @@ the family's
|
|
|
11
11
|
[versioning policy](https://github.com/DescentVTT/spec-core/blob/main/docs/adr/0009-versions-before-1-0.md)
|
|
12
12
|
records.
|
|
13
13
|
|
|
14
|
+
## 0.20.0
|
|
15
|
+
|
|
16
|
+
A negation an aside sets apart from its phrase cancels it, and a
|
|
17
|
+
pointer such as `see` governs the reference after it, so neither is
|
|
18
|
+
read as the relation it denies or passes over. On Linux a `\` in an
|
|
19
|
+
export path `diff` is given is a separator, and a run does not start in
|
|
20
|
+
a directory whose name holds one. A relative `cwd` given to the library
|
|
21
|
+
is read once.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **A negation an aside sets apart from the phrase cancels it.**
|
|
26
|
+
`Not, as it happens, blocked by [ADR-0002](0002.md)` and
|
|
27
|
+
`Never, ever, blocked by` were `blocked-by`, the relation each denies:
|
|
28
|
+
the comma after the negation ended its clause. An aside closed by a comma
|
|
29
|
+
of its own leaves the clause open now, and each is a citation, and no
|
|
30
|
+
phrase before such a negation governs the reference either, so
|
|
31
|
+
`Not, as decided in [ADR-0003](0003.md), blocked by [ADR-0002](0002.md)`
|
|
32
|
+
reads no premise from the second. The miss it trades is a negation that
|
|
33
|
+
ends a clause of its own before an aside, `If not, then, blocked by`,
|
|
34
|
+
which is a citation too
|
|
35
|
+
([ADR-0006](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0006-false-positives-cost-more.md)).
|
|
36
|
+
A `ghost-handover`, `stale-premise` or `live-supersession` such a
|
|
37
|
+
sentence denies is no longer reported, and an open question it hands to
|
|
38
|
+
a live document is an `orphaned-obligation`, so a check can pass that
|
|
39
|
+
failed, and fail that passed. Upgrading: run `check`; where a hand-off
|
|
40
|
+
is written `Owner: nobody, for now, deferred to`, write it without the
|
|
41
|
+
aside.
|
|
42
|
+
- **A pointer governs the reference after it.**
|
|
43
|
+
`Per tick, see [ADR-0002](0002.md)` read the cited document as a
|
|
44
|
+
premise, and `Blocked by the cache, see [ADR-0002](0002.md)` as what
|
|
45
|
+
blocks: `see` between a phrase
|
|
46
|
+
and the reference was passed over. It governs the reference as a
|
|
47
|
+
citation now, as it does with no phrase before it; `the must-see list`
|
|
48
|
+
and `seen in` point at nothing
|
|
49
|
+
([ADR-0006](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0006-false-positives-cost-more.md)).
|
|
50
|
+
A finding read from such a phrase is no longer reported. Upgrading: run
|
|
51
|
+
`check`; where the phrase does name the reference, name it before the
|
|
52
|
+
pointer, `Blocked by [ADR-0002](0002.md), see its table`.
|
|
53
|
+
- **A `\` in an export path is a separator.** `diff` read its two paths as
|
|
54
|
+
typed, so on Linux `diff 'sub\base.json' 'sub\head.json'` compared files
|
|
55
|
+
of those names, or found none, and `..\sub\base.json` typed one directory
|
|
56
|
+
down found none, where a baseline typed either way is `sub/base.json`.
|
|
57
|
+
Each path is read as a root and a baseline are now, from where it was
|
|
58
|
+
typed. Windows reads as before
|
|
59
|
+
([ADR-0010](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0010-configuration-belongs-to-the-repository.md)).
|
|
60
|
+
Upgrading: on Linux, rename an export whose file name holds a `\`; a
|
|
61
|
+
diff that read it reads the path the `\` spells now.
|
|
62
|
+
- **A run does not start in a directory whose name holds a `\`.** Where the
|
|
63
|
+
host separates names with `/`, as Linux does, `check`, `query`, `graph`
|
|
64
|
+
and `rules` in `/work/a\b` read `/work/a/b`, the path the `\` spells: its
|
|
65
|
+
documents, its configuration and its rules, so a check could pass over
|
|
66
|
+
the tree it was typed in, and a `--record-baseline` was written there.
|
|
67
|
+
Each stops now with exit `2` and one line, before it reads or writes
|
|
68
|
+
anything; `diff`, `--help` and `--version` run there as before
|
|
69
|
+
([ADR-0010](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0010-configuration-belongs-to-the-repository.md)).
|
|
70
|
+
Upgrading: run from a path whose names hold no `\`, or give `--root` one,
|
|
71
|
+
such as a link to the directory.
|
|
72
|
+
- **A relative `cwd` is read once.** The library's `main({ cwd: 'docs' })`
|
|
73
|
+
with no `--root` started the run in `docs/docs`, the directory joined to
|
|
74
|
+
itself: it checked `docs/docs/*.md` for `*.md` and recorded a baseline
|
|
75
|
+
there. It starts in `docs` now, and two values of a baseline are
|
|
76
|
+
compared from there. The `spec-graph` command gives an absolute
|
|
77
|
+
directory and is as it was
|
|
78
|
+
([ADR-0010](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0010-configuration-belongs-to-the-repository.md)).
|
|
79
|
+
Upgrading: nothing to do; a caller that gave a relative `cwd` gets the
|
|
80
|
+
directory it named.
|
|
81
|
+
|
|
14
82
|
## 0.19.0
|
|
15
83
|
|
|
16
84
|
A comma ends the clause a negation before a phrase is in, so
|
package/README.md
CHANGED
|
@@ -475,7 +475,15 @@ it, in the same statement: a sentence, a paragraph, a list item or a table cell.
|
|
|
475
475
|
`Not in this release: deferred to` each state the relation, and are read as
|
|
476
476
|
they state it. So do `if not, blocked by` and `both or neither, because`: a
|
|
477
477
|
comma ends the clause the negation is in, and counts toward no phrase's 40
|
|
478
|
-
characters.
|
|
478
|
+
characters. An aside closed by a comma of its own leaves the clause open,
|
|
479
|
+
so `not, as it happens, blocked by` and `never, ever, blocked by` are
|
|
480
|
+
citations, and so is `if not, then, blocked by`, which is missed. No
|
|
481
|
+
phrase before such a negation governs the reference either:
|
|
482
|
+
`not, as decided in [ADR-0003](0003.md), blocked by [ADR-0002](0002.md)`
|
|
483
|
+
reads the first as a premise and the second as a citation.
|
|
484
|
+
- A pointer between the two, `see`, governs the reference as a citation:
|
|
485
|
+
`per tick, see [ADR-0002](0002.md)` and `blocked by the cache, see` read no
|
|
486
|
+
relation, and `the must-see list` and `seen in` point at nothing.
|
|
479
487
|
- A sentence written in Chinese ends at `。`, `?`, `!` or `;`, with a space
|
|
480
488
|
after it or none, so `Deferred to later。另見 ADR-0002` hands nothing to
|
|
481
489
|
ADR-0002. A stop only ends a statement sooner: it reads less, never more.
|
|
@@ -894,9 +902,17 @@ reports, byte for byte, because every path here is relative to the root.
|
|
|
894
902
|
- A path typed on the command line stays relative to where you typed it, brace
|
|
895
903
|
alternative by brace alternative: `{/docs,x}` typed in `sub` is `/docs` or
|
|
896
904
|
`sub/x`.
|
|
897
|
-
- A `\` in a root
|
|
898
|
-
so `--baseline sub\held.json` typed at the
|
|
899
|
-
typed in `docs` are one file, `sub/held.json`,
|
|
905
|
+
- A `\` in a root, a baseline or an export `diff` reads is a separator on
|
|
906
|
+
every host, as in a pattern, so `--baseline sub\held.json` typed at the
|
|
907
|
+
root and `..\sub\held.json` typed in `docs` are one file, `sub/held.json`,
|
|
908
|
+
on Linux as on Windows.
|
|
909
|
+
- So a run does not start in a directory whose name holds a `\`, where the
|
|
910
|
+
host separates names with `/`, as Linux does: read as a separator, the
|
|
911
|
+
`\` would name another directory, and the run would check it and record a
|
|
912
|
+
baseline into it. The run stops with
|
|
913
|
+
exit `2` and one line before it reads anything; run it from a path whose
|
|
914
|
+
names hold none, or give `--root` one, such as a link to the directory.
|
|
915
|
+
`diff`, `--help` and `--version` run there.
|
|
900
916
|
|
|
901
917
|
See [ADR-0018](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0018-the-configuration-file-is-the-root.md).
|
|
902
918
|
|
|
@@ -1339,7 +1355,7 @@ steps:
|
|
|
1339
1355
|
- run: npm ci
|
|
1340
1356
|
- run: npx --no-install @descent-vtt/spec-graph --format sarif > spec-graph.sarif
|
|
1341
1357
|
continue-on-error: true
|
|
1342
|
-
- uses: github/codeql-action/upload-sarif@
|
|
1358
|
+
- uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
|
1343
1359
|
with:
|
|
1344
1360
|
sarif_file: spec-graph.sarif
|
|
1345
1361
|
```
|
|
@@ -1483,6 +1499,9 @@ It names only what it can tell apart:
|
|
|
1483
1499
|
resolved or reopened.
|
|
1484
1500
|
- Findings are left to `--baseline`, which already reports what is new.
|
|
1485
1501
|
|
|
1502
|
+
Each export is read from where its path was typed, with a `\` a separator,
|
|
1503
|
+
as a baseline is.
|
|
1504
|
+
|
|
1486
1505
|
The exit code is `0` whether anything changed or not: a diff describes, and
|
|
1487
1506
|
`check` gates. See
|
|
1488
1507
|
[ADR-0020](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0020-a-diff-names-what-it-can-tell-apart.md).
|
|
@@ -1524,7 +1543,8 @@ The command line is `main`, which takes the line and the directory it was
|
|
|
1524
1543
|
typed in: `await main({ argv: ['check', '--root', 'docs'], cwd })` reads
|
|
1525
1544
|
`docs`, its configuration and any baseline beside it from `cwd`, and walks
|
|
1526
1545
|
up from `cwd` for a configuration where no `--root` is given. Left out,
|
|
1527
|
-
`cwd` is the process's working directory, as for the `spec-graph` command
|
|
1546
|
+
`cwd` is the process's working directory, as for the `spec-graph` command;
|
|
1547
|
+
a relative `cwd` is read from that directory.
|
|
1528
1548
|
|
|
1529
1549
|
## Design
|
|
1530
1550
|
|
|
@@ -1563,9 +1583,9 @@ tools share, whose code is copied into `src/vendor/` and checked by hash rather
|
|
|
1563
1583
|
than installed. So the whole package is auditable in an afternoon — and nothing
|
|
1564
1584
|
in the pipeline can take longer than its input.
|
|
1565
1585
|
|
|
1566
|
-
**Verified, not just covered.** 2,
|
|
1586
|
+
**Verified, not just covered.** 2,381 tests; 99.4% statement and 99.8% line
|
|
1567
1587
|
coverage. Coverage says a line ran, so the suite is also held to a mutation
|
|
1568
|
-
score - 96.
|
|
1588
|
+
score - 96.59% over 9,149 mutants, measured by CI's full sweep, and a build
|
|
1569
1589
|
fails under 93 - because a vocabulary entry or a boundary
|
|
1570
1590
|
condition can be weakened by an ordinary-looking refactor without a single test
|
|
1571
1591
|
going red. [ADR-0007](https://github.com/DescentVTT/spec-graph/blob/main/docs/adr/0007-mutation-testing.md) is straight about what
|
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* itself could not run. A usage mistake never masquerades as a passing build,
|
|
11
11
|
* and an error nothing here expected never as findings.
|
|
12
12
|
*/
|
|
13
|
-
import { resolve } from 'node:path';
|
|
13
|
+
import { resolve, sep } from 'node:path';
|
|
14
14
|
import { applyBaseline, EMPTY_BASELINE, formatBaseline, parseBaseline, } from './baseline.js';
|
|
15
15
|
import { discoverConfig, loadConfig } from './config.js';
|
|
16
16
|
import { DiffInputError, diffExports, formatDiffJson, formatDiffMarkdown, formatDiffText, parseGraphExport, } from './diff.js';
|
|
@@ -19,7 +19,7 @@ import { isFamilyName } from './identity.js';
|
|
|
19
19
|
import { analyse, DEFAULT_PATTERNS, withDiagnostics } from './runner.js';
|
|
20
20
|
import { formatGithub, formatGitlab, formatGraph, formatJson, formatMarkdown, formatReport, formatSarif, shouldUseAscii, shouldUseColor, } from './report.js';
|
|
21
21
|
import { DEFAULT_SEVERITIES, resolveStrict, RULE_DECISIONS, decisionUrl, RULE_DESCRIPTIONS, RULE_IDS, RULE_QUERIES, } from './rules.js';
|
|
22
|
-
import { foldDriveLetter, isAbsolutePath, joinPosix, toPosix } from './paths.js';
|
|
22
|
+
import { foldDriveLetter, holdsBackslashInName, isAbsolutePath, joinPosix, toPosix } from './paths.js';
|
|
23
23
|
import { formatRef } from './source.js';
|
|
24
24
|
import { execute, parseQuery, QueryError, renderMatch } from './select.js';
|
|
25
25
|
import { isProjectRule } from './types.js';
|
|
@@ -500,6 +500,11 @@ export function parseArgs(argv, cwd) {
|
|
|
500
500
|
const once = (flag, typed, read, key = flag, takes = 'takes one value') => {
|
|
501
501
|
valued.push({ flag, key, typed, read, takes });
|
|
502
502
|
};
|
|
503
|
+
// A baseline typed here is read from the root where `--root` names one,
|
|
504
|
+
// and from where it was typed otherwise, which is the root's default. That
|
|
505
|
+
// directory is read once: joined to itself, a relative `cwd` such as
|
|
506
|
+
// `docs` read `docs/docs` (ADR-0010, 2026-10-24).
|
|
507
|
+
const fromRoot = (value) => foldDriveLetter(resolve(cwd, rootExplicit ? toPosix(root) : '', toPosix(value)));
|
|
503
508
|
for (let i = 0; i < args.length; i += 1) {
|
|
504
509
|
const arg = args[i];
|
|
505
510
|
if (arg === '--') {
|
|
@@ -582,18 +587,16 @@ export function parseArgs(argv, cwd) {
|
|
|
582
587
|
historyPatterns.push(next(arg, i));
|
|
583
588
|
i += 1;
|
|
584
589
|
break;
|
|
585
|
-
// A baseline typed here is read from the root where `--root` names one,
|
|
586
|
-
// and from where it was typed otherwise, which is the root's default.
|
|
587
590
|
case '--baseline': {
|
|
588
591
|
const value = filled(arg, i, 'a file');
|
|
589
|
-
once(arg, value, () =>
|
|
592
|
+
once(arg, value, () => fromRoot(value));
|
|
590
593
|
baseline = value;
|
|
591
594
|
i += 1;
|
|
592
595
|
break;
|
|
593
596
|
}
|
|
594
597
|
case '--record-baseline': {
|
|
595
598
|
const value = filled(arg, i, 'a file');
|
|
596
|
-
once(arg, value, () =>
|
|
599
|
+
once(arg, value, () => fromRoot(value));
|
|
597
600
|
recordBaseline = value;
|
|
598
601
|
i += 1;
|
|
599
602
|
break;
|
|
@@ -906,8 +909,24 @@ async function run(io, err) {
|
|
|
906
909
|
// the library names with `io.cwd`, and where two values of `--root` were
|
|
907
910
|
// compared: left relative, the disk read it from Node's own directory, so
|
|
908
911
|
// `--root docs` checked, and recorded a baseline into, another tree than
|
|
909
|
-
// the caller's. A `\` is a separator, as the walk has
|
|
910
|
-
|
|
912
|
+
// the caller's. A `\` typed in the root is a separator, as the walk has
|
|
913
|
+
// always read it. The directory the line was typed in is not typed, and is
|
|
914
|
+
// read once, as the host gives it: joined to itself, a relative `cwd` such
|
|
915
|
+
// as `docs` started the run in `docs/docs`.
|
|
916
|
+
const start = options.rootExplicit ? resolve(cwd, toPosix(options.root)) : resolve(cwd);
|
|
917
|
+
// On a host whose separator is `/`, a `\` in where the run starts is a
|
|
918
|
+
// character of a name, from the directory the line was typed in, and the
|
|
919
|
+
// walk, the configuration and a baseline read every `\` as a separator: in
|
|
920
|
+
// `/t/a\b` they read `/t/a/b`, another directory, and recorded a baseline
|
|
921
|
+
// into it. No path handed on tells the one `\` from the other, so the run
|
|
922
|
+
// stops before it reads or writes anything. A diff reads its two files
|
|
923
|
+
// where they are, from that directory as the host gives it, and is not
|
|
924
|
+
// stopped (ADR-0010, 2026-10-24).
|
|
925
|
+
if (holdsBackslashInName(start, sep)) {
|
|
926
|
+
const named = options.rootExplicit ? `--root "${options.root}" is ${start}, which has` : `${start} has`;
|
|
927
|
+
err(`spec-graph: ${named} a "\\" in a name, and spec-graph reads each "\\" as a separator, so it would read ${toPosix(start)} instead: run it from a path with none, or give --root one\n`);
|
|
928
|
+
return EXIT_ERROR;
|
|
929
|
+
}
|
|
911
930
|
// A root that was named is a directory, for every command that reads one.
|
|
912
931
|
// `rules --root` with a path that is not there listed the built-in rules and
|
|
913
932
|
// exited 0, as if the repository declared none, and the others said only
|
|
@@ -1456,9 +1475,15 @@ async function runDiff(paths, cwd, format, out, err) {
|
|
|
1456
1475
|
for (const path of paths) {
|
|
1457
1476
|
let raw;
|
|
1458
1477
|
try {
|
|
1478
|
+
// A `\` is a separator, as it is in a root and a baseline: kept as a
|
|
1479
|
+
// character, `sub\a.json` typed at the top read a file of that name on
|
|
1480
|
+
// Linux, where the same baseline is `sub/a.json`, and `..\sub\a.json`
|
|
1481
|
+
// typed below it read nothing. The directory it was typed in is read
|
|
1482
|
+
// as the host gives it, so a diff reads its files there, `\` or none.
|
|
1483
|
+
//
|
|
1459
1484
|
// Without the encoding this is a Buffer, which JSON.parse decodes as
|
|
1460
1485
|
// UTF-8 all the same - so emptying the literal parses the same export.
|
|
1461
|
-
raw = await readFile(resolve(cwd, path), 'utf8');
|
|
1486
|
+
raw = await readFile(resolve(cwd, toPosix(path)), 'utf8');
|
|
1462
1487
|
}
|
|
1463
1488
|
catch (error) {
|
|
1464
1489
|
err(`spec-graph: cannot read ${path}: ${error.message}\n`);
|