@veda-hq/pariksha 0.5.8 → 0.5.9
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/README.md +187 -2
- package/dist/cli.js +109 -6
- package/dist/codec-registry.js +360 -0
- package/dist/fault-injection-registry.js +203 -0
- package/dist/git.js +40 -0
- package/dist/module-exports.js +169 -0
- package/dist/registry-ratchet.js +402 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
test-quality ratchets. Part of [Veda](https://github.com/veda-hq/veda).
|
|
5
5
|
|
|
6
6
|
```sh
|
|
7
|
-
npx @veda-hq/pariksha@0.5.
|
|
8
|
-
npx @veda-hq/pariksha@0.5.
|
|
7
|
+
npx @veda-hq/pariksha@0.5.9 <check> [flags]
|
|
8
|
+
npx @veda-hq/pariksha@0.5.9 --version
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Every check exits 0 when the repository passes, 1 when it refuses something
|
|
@@ -62,6 +62,114 @@ the repository's deny list.
|
|
|
62
62
|
- Binary files (any NUL byte) are skipped. Untracked files are not read.
|
|
63
63
|
Line 0 in a finding is the file's path.
|
|
64
64
|
|
|
65
|
+
## `codec-registry`
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
pariksha codec-registry --config <file> [--base <ref> | --no-base] [--root <dir>]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
A module that parses markdown with its own regexes passes every example its
|
|
72
|
+
author thought of, and misses the setext heading nobody did. So every module
|
|
73
|
+
that hand-parses a format must be held by a property suite to the format's
|
|
74
|
+
reference parser, and every exported writer/reader pair (`writeX`/`readX`,
|
|
75
|
+
`renderX`/`parseX`, `formatX`/`loadX`…) by a suite that feeds real writer
|
|
76
|
+
output through the real reader. Discovery reads tracked source under `roots`
|
|
77
|
+
as a TypeScript syntax tree (tests, declarations, fixtures and mocks are not
|
|
78
|
+
read):
|
|
79
|
+
|
|
80
|
+
- a hand parser is a regex literal that reads a format's syntax (an ATX
|
|
81
|
+
heading or a fence, a frontmatter delimiter, a tag, an import statement), a
|
|
82
|
+
string literal that delimits one by itself (`---`, a fence, `<script`), a
|
|
83
|
+
comma split in a module that says `csv`, or a tokenizer tracking both quote
|
|
84
|
+
kinds and the backslash in a module about shell commands: one id per module
|
|
85
|
+
and format, `path#format`, in the formats `oracles` names only;
|
|
86
|
+
- a pair is an exported producer and consumer of the same stem,
|
|
87
|
+
`path#writeRow->readRow` (an `export { … }` list counts; a class, a constant
|
|
88
|
+
or a type-only export does not), and `path#digest` for a module that calls
|
|
89
|
+
`createHash` beside a numeric `*SCHEMA*` export or a `'schema'` argument.
|
|
90
|
+
|
|
91
|
+
Each id is registered, exempt with a reason, or listed in the baseline, and
|
|
92
|
+
the baseline lists exactly what is still unregistered. The JSON `--config`
|
|
93
|
+
(relative to the root):
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"baseline": "scripts/codec-registry.baseline.json",
|
|
98
|
+
"roots": ["scripts", "src"],
|
|
99
|
+
"oracles": { "markdown": "mdast-util-from-markdown", "yaml": "js-yaml" },
|
|
100
|
+
"collected": ["^scripts/.+\\.test\\.ts$"],
|
|
101
|
+
"surfaces": ["scripts/**/*.{differential,roundtrip}.properties.test.ts"],
|
|
102
|
+
"library": "fast-check",
|
|
103
|
+
"markers": { "seed": "seed: SEED", "lineEndings": "LINE_ENDINGS", "golden": "assertGolden" },
|
|
104
|
+
"handParsers": [{ "source": "scripts/doc.ts", "formats": ["markdown"], "suite": "scripts/doc.differential.properties.test.ts", "outOfModel": "lists and block quotes", "motivatedBy": "M1234567890" }],
|
|
105
|
+
"pairs": [{ "id": "scripts/rows.ts#writeRow->readRow", "kind": "round-trip", "suite": "scripts/rows.roundtrip.properties.test.ts" }],
|
|
106
|
+
"exempt": [{ "id": "scripts/main.ts#csv", "reason": "a comma list in an env var, not CSV" }],
|
|
107
|
+
"minNoteLength": 21,
|
|
108
|
+
"examples": { "catalogue": { "path": "checks.json", "slice": "C-20" }, "fixtures": { "EDGE-12": { "example": "V3586514538", "test": "scripts/edge-12.examples.test.ts" } } },
|
|
109
|
+
"findingIds": { "motivatedBy": "^M\\d{10}$", "example": "^[MV]\\d{10}$" }
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- A hand-parser suite sits where a `collected` pattern matches it and on a
|
|
114
|
+
`surfaces` glob, imports its module by a relative specifier that resolves
|
|
115
|
+
to it, imports `library` (default `fast-check`), names each format's oracle
|
|
116
|
+
module, holds the `seed` marker, and holds the `lineEndings` marker unless
|
|
117
|
+
`lfOnly` says why the suite runs LF only. `outOfModel` (what the generators
|
|
118
|
+
leave out) is required.
|
|
119
|
+
- A pair's `kind` follows from its id and is refused otherwise: a
|
|
120
|
+
`producer->consumer` pair is `round-trip`, a `#digest` is `golden`. A
|
|
121
|
+
`round-trip` suite does what a hand-parser suite does less the oracle and
|
|
122
|
+
line endings; a `golden` one needs only its module and the `golden` marker.
|
|
123
|
+
- A suite's imports are read from its syntax tree: import declarations,
|
|
124
|
+
`export … from` and `import('…')` of a literal. A specifier in a comment
|
|
125
|
+
or another string, or a type-only import, is no import.
|
|
126
|
+
- `minNoteLength` is the fewest characters (trimmed) an exemption `reason`,
|
|
127
|
+
an `outOfModel` or an `lfOnly` may have: at 21, as here, `"reason": "not
|
|
128
|
+
csv"` is refused and the reason above passes. At 1 only a blank is refused.
|
|
129
|
+
- `examples` holds a fixture per check: its suite exists, is collected and
|
|
130
|
+
names the finding id; with a `catalogue`, the fixtures cover exactly that
|
|
131
|
+
JSON's checks for its `slice` (`{ "checks": [{ "id", "slice", "example" }]
|
|
132
|
+
}`, each example opening with `<finding id>:`), and `"catalogue": null`
|
|
133
|
+
checks the fixtures alone.
|
|
134
|
+
- `findingIds.motivatedBy` holds every `motivatedBy`, and `findingIds.example`
|
|
135
|
+
every example fixture's id, to its own pattern.
|
|
136
|
+
- `surfaces`, `examples`, `findingIds` and each pattern in it are required, so
|
|
137
|
+
a forgotten key never drops a check silently. `null` opts a check out, and
|
|
138
|
+
the run prints it on its own line: `not checked: surfaces, examples`.
|
|
139
|
+
- Messages name the kind (`hand parser scripts/doc.ts: …`, `pair
|
|
140
|
+
scripts/rows.ts#writeRow->readRow: …`), never a repository's check ids.
|
|
141
|
+
|
|
142
|
+
Refused (exit 1): a discovered id that is unaccounted; a registered or exempt
|
|
143
|
+
id discovery does not find; a suite that is missing, uncollected, off the
|
|
144
|
+
surface or missing any part above; a baseline entry that is registered,
|
|
145
|
+
exempt, gone or in the wrong list (each list is held to what discovery finds
|
|
146
|
+
of its kind); and a baseline entry the merge base of HEAD and the base did
|
|
147
|
+
not have, in any list, unless its file only moved (git's rename detection
|
|
148
|
+
over the working tree; the file is the id before its last `#`, so a path may
|
|
149
|
+
hold one). Without `--base`, the base is the first of `origin/main`,
|
|
150
|
+
`origin/develop`, `origin/master` and `main` that exists.
|
|
151
|
+
|
|
152
|
+
Growth is read against the merge base, so growth committed on the branch is
|
|
153
|
+
refused like growth in the working tree. The merge base's baseline is read
|
|
154
|
+
against today's lists: a list it lacks is empty (an entry in it today is
|
|
155
|
+
growth) and a list no longer named is ignored. Where the merge base has no
|
|
156
|
+
file at `baseline`, the baseline is being introduced and nothing has grown;
|
|
157
|
+
so pointing `baseline` at a new path also compares no growth for that run.
|
|
158
|
+
`--base HEAD` compares the tree with HEAD alone: committed growth passes and
|
|
159
|
+
only uncommitted growth is refused. `--no-base` compares no growth and needs
|
|
160
|
+
no base; the run prints `not checked: baseline growth (--no-base)`. `--base`
|
|
161
|
+
with `--no-base`, and an empty `--base ""`, are usage errors.
|
|
162
|
+
|
|
163
|
+
Exit 2, since nothing was checked: a config with an unknown, missing or bad
|
|
164
|
+
key, a path that is not a normalized root-relative POSIX path (a backslash or
|
|
165
|
+
a NUL included), a pair `kind` its id does not fit, an exemption,
|
|
166
|
+
`outOfModel` or `lfOnly` shorter than `minNoteLength`, an id registered twice; a baseline that is missing or not `{ "handParsers":
|
|
167
|
+
[ids], "pairs": [ids] }` with no other list; a merge-base baseline whose
|
|
168
|
+
lists are malformed; a root holding no tracked file; discovery finding no
|
|
169
|
+
hand parser, or no pair, since a kind found nowhere is checked nowhere; no
|
|
170
|
+
base without `--no-base`; a git call that fails. Needs `typescript`, as
|
|
171
|
+
`coverage-by-type` does.
|
|
172
|
+
|
|
65
173
|
## `coverage-by-type`
|
|
66
174
|
|
|
67
175
|
```sh
|
|
@@ -252,6 +360,83 @@ do not change what it reads. With no base, or when git cannot read the
|
|
|
252
360
|
range (no merge base, a shallow clone), it exits 2: nothing was checked,
|
|
253
361
|
so it is not a pass.
|
|
254
362
|
|
|
363
|
+
## `fault-injection-registry`
|
|
364
|
+
|
|
365
|
+
```sh
|
|
366
|
+
pariksha fault-injection-registry --config <file> [--base <ref> | --no-base] [--root <dir>]
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
A CLI whose ledger write fails after it published a plan, or a service whose
|
|
370
|
+
best-effort cache write fails after its database commit, behaves in a way no
|
|
371
|
+
happy-path test reaches. So every such unit runs under the repository's
|
|
372
|
+
fault-injection harness, which fails its dependencies call by call, and its
|
|
373
|
+
suite asserts each declared check (an exit contract, containment, rerun
|
|
374
|
+
convergence) held to zero violations. Discovery reads the tracked files under
|
|
375
|
+
each `discover` group's `roots` that match its `include` and are not tests,
|
|
376
|
+
declarations or under a `fixtures/` folder, and keeps those whose text
|
|
377
|
+
matches its `marker`. Each found source is registered, exempt with a reason,
|
|
378
|
+
or in the baseline, which has one list per group and lists exactly what is
|
|
379
|
+
still unregistered. The JSON `--config` (relative to the root):
|
|
380
|
+
|
|
381
|
+
```json
|
|
382
|
+
{
|
|
383
|
+
"baseline": "scripts/fault-injection.baseline.json",
|
|
384
|
+
"discover": {
|
|
385
|
+
"cli": { "roots": ["scripts"], "include": "\\.(ts|mjs)$", "marker": { "pattern": "isCliEntry\\(|process\\.argv\\b|^main\\(", "flags": "m" } },
|
|
386
|
+
"server": { "roots": ["apps/api/src"], "include": "\\.ts$", "marker": { "pattern": "best[- ]effort|fire-and-forget", "flags": "i" } }
|
|
387
|
+
},
|
|
388
|
+
"collected": ["^scripts/.+\\.test\\.ts$", "^apps/api/src/.+\\.test\\.ts$"],
|
|
389
|
+
"harness": {
|
|
390
|
+
"module": "packages/test-utils/src/fault-injection/index.ts",
|
|
391
|
+
"imports": ["@acme/test-utils/fault-injection"],
|
|
392
|
+
"checks": { "ERR-09": "checkExitContract", "ERR-10": "checkContainment", "STATE-11": "checkRerunConvergence" },
|
|
393
|
+
"heldBy": "assertNoViolations"
|
|
394
|
+
},
|
|
395
|
+
"consumers": [{ "source": "scripts/publish.ts", "unit": "main", "checks": ["ERR-09", "STATE-11"], "test": "scripts/publish.faults.test.ts", "uncovered": "the entry block, which only parses flags", "motivatedBy": "M3933356096" }],
|
|
396
|
+
"exempt": [{ "source": "scripts/report.ts", "reason": "writes no file and holds no state" }],
|
|
397
|
+
"exemptIn": ["cli"],
|
|
398
|
+
"minNoteLength": 21,
|
|
399
|
+
"examples": { "catalogue": { "path": "checks.json", "slice": "C-18" }, "fixtures": { "ERR-09": { "example": "M3933356096", "test": "packages/test-utils/src/fault-injection/err-09.examples.test.ts" } } },
|
|
400
|
+
"findingIds": { "motivatedBy": "^[MV]\\d{10}$", "example": "^[MV]\\d{10}$" }
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
- The harness module exists and exports each `checks` function and `heldBy`
|
|
405
|
+
as a function. Its exports are read with TypeScript: `export { … } from`,
|
|
406
|
+
`export *` and a local name bound by an import (a default import, or
|
|
407
|
+
`export { default as … }`, follows the module's default export) are
|
|
408
|
+
followed through relative specifiers to the declaration, which must be a
|
|
409
|
+
function (a declaration, or a constant holding an arrow or function
|
|
410
|
+
expression), not a type or another value. A package specifier is not followed, and a name it
|
|
411
|
+
would have to supply is refused, naming what was not followed. The harness
|
|
412
|
+
is never imported or run.
|
|
413
|
+
- A consumer's suite exists where a `collected` pattern matches it, imports
|
|
414
|
+
the harness (a relative specifier resolving to `module` or its folder, or
|
|
415
|
+
one of `imports`) and the consumer's source, its imports read from its
|
|
416
|
+
syntax tree as in `codec-registry`, calls each declared check's function and
|
|
417
|
+
`heldBy`, and the name `unit` opens with appears in the source.
|
|
418
|
+
- `exemptIn` names the groups an exemption may sit in: an exempt source must
|
|
419
|
+
be discovered in one of them, so a source only another group finds (here a
|
|
420
|
+
best-effort server step) cannot be exempted away.
|
|
421
|
+
- `minNoteLength` is the fewest characters (trimmed) an exemption `reason`
|
|
422
|
+
or an `uncovered` may have.
|
|
423
|
+
- `examples`, `findingIds`, the opt-out with `null` and the `not checked`
|
|
424
|
+
line work as in `codec-registry`, and each fixture suite also calls its
|
|
425
|
+
check's function.
|
|
426
|
+
|
|
427
|
+
Refused (exit 1): a discovered source that is unaccounted; an exemption
|
|
428
|
+
discovery does not find in an `exemptIn` group; a harness or consumer suite
|
|
429
|
+
missing any part above; a stale baseline entry, or one in the list of a group
|
|
430
|
+
that does not discover it; and growth against the merge base, read as in
|
|
431
|
+
`codec-registry` (the same `--base`, `--base HEAD`,
|
|
432
|
+
`--no-base`, first introduction and merge-base lists). Exit 2: a bad config
|
|
433
|
+
(an unknown, missing or bad key, a path that is not a normalized
|
|
434
|
+
root-relative POSIX path, a check not in `checks`, an `exemptIn` group
|
|
435
|
+
`discover` lacks, a reason or `uncovered` shorter than
|
|
436
|
+
`minNoteLength`, a source registered twice), a missing or malformed
|
|
437
|
+
baseline, a root holding no tracked file, a group discovering nothing, no
|
|
438
|
+
base without `--no-base`, a failed git call, or no `typescript`.
|
|
439
|
+
|
|
255
440
|
## `required-settings`
|
|
256
441
|
|
|
257
442
|
```sh
|
package/dist/cli.js
CHANGED
|
@@ -13,16 +13,21 @@ import { diskReads, formatReads, listFiles, needsTypescript, parseConfig as pars
|
|
|
13
13
|
import { loadTypescript, typescriptLoaders } from "./typescript.js";
|
|
14
14
|
import { checkExportDeletions, formatReport } from "./export-deletions.js";
|
|
15
15
|
import { findMissing, formatFindings, parseRequirements } from "./required-settings.js";
|
|
16
|
-
import { BASE_CANDIDATES, gitAt, gitAtTop, must, resolveBase, topOf } from "./git.js";
|
|
16
|
+
import { BASE_CANDIDATES, gitAt, gitAtTop, must, resolveBase, topOf, trackedUnder } from "./git.js";
|
|
17
|
+
import { discover as discoverCodecs, fileOf as codecFileOf, findings as codecFindings, formatFindings as formatCodecFindings, parseBaseline as parseCodecBaseline, parseConfig as parseCodecConfig } from "./codec-registry.js";
|
|
18
|
+
import { discoverGroup, findings as faultFindings, formatFindings as formatFaultFindings, parseBaseline as parseFaultBaseline, parseConfig as parseFaultConfig } from "./fault-injection-registry.js";
|
|
19
|
+
import { emptyRoots, growthSince, readBaseline, wholeId } from "./registry-ratchet.js";
|
|
17
20
|
export const DENY_FILE = '.veda/deny.sha256';
|
|
18
21
|
const USAGE = [
|
|
19
22
|
'usage: pariksha authority-citations [--mode staged|tree] [--authority <a,b>] [--issue-prefix <A,B>] [--adr-dir <dir>] [--root <dir>]',
|
|
20
23
|
' pariksha boundary [--root <dir>] [--deny <file>]',
|
|
24
|
+
' pariksha codec-registry --config <file> [--base <ref> | --no-base] [--root <dir>]',
|
|
21
25
|
' pariksha coverage-by-type [--config <file>] [--files-from <file>|-] [--root <dir>]',
|
|
22
26
|
' pariksha coverage-exemptions --config <file> [--package <dir>] [--root <dir>]',
|
|
23
27
|
' pariksha doc-budgets --table <file> [--section <name>] [--root <dir>]',
|
|
24
28
|
' pariksha env-gate-switches --config <file> [--mode staged|tree] [--root <dir>]',
|
|
25
29
|
' pariksha export-deletions [--base <ref>] [--root <dir>]',
|
|
30
|
+
' pariksha fault-injection-registry --config <file> [--base <ref> | --no-base] [--root <dir>]',
|
|
26
31
|
' pariksha required-settings --require <file> [--settings <file>] [--root <dir>]',
|
|
27
32
|
' pariksha hash <word>',
|
|
28
33
|
' pariksha --version',
|
|
@@ -45,15 +50,27 @@ export const trackedFiles = (root) => {
|
|
|
45
50
|
}
|
|
46
51
|
return files;
|
|
47
52
|
};
|
|
48
|
-
/**
|
|
49
|
-
|
|
53
|
+
/**
|
|
54
|
+
* `--name value` pairs, each name allowed once and from `allowed`, and bare
|
|
55
|
+
* `switches` (`--no-base`), each once, held as ''; undefined on anything else.
|
|
56
|
+
* An empty value is refused: `--base ""` names no ref.
|
|
57
|
+
*/
|
|
58
|
+
export const flags = (rest, allowed, switches = []) => {
|
|
50
59
|
const out = new Map();
|
|
51
|
-
for (let i = 0; i < rest.length;
|
|
60
|
+
for (let i = 0; i < rest.length;) {
|
|
52
61
|
const name = rest[i];
|
|
62
|
+
if (name === undefined || out.has(name))
|
|
63
|
+
return undefined;
|
|
64
|
+
if (switches.includes(name)) {
|
|
65
|
+
out.set(name, '');
|
|
66
|
+
i += 1;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
53
69
|
const value = rest[i + 1];
|
|
54
|
-
if (
|
|
70
|
+
if (!allowed.includes(name) || !value)
|
|
55
71
|
return undefined;
|
|
56
72
|
out.set(name, value);
|
|
73
|
+
i += 2;
|
|
57
74
|
}
|
|
58
75
|
return out;
|
|
59
76
|
};
|
|
@@ -318,6 +335,90 @@ const envGateSwitches = (f, io) => {
|
|
|
318
335
|
result.err.forEach(io.err);
|
|
319
336
|
return result.ok ? 0 : 1;
|
|
320
337
|
};
|
|
338
|
+
/** Thrown when TypeScript cannot be loaded; carries the loader's message. */
|
|
339
|
+
class NoTypescript extends Error {
|
|
340
|
+
}
|
|
341
|
+
const runRatchet = (spec, f, io) => {
|
|
342
|
+
const P = `pariksha ${spec.name}:`;
|
|
343
|
+
const configPath = f.get('--config');
|
|
344
|
+
const noBase = f.has('--no-base');
|
|
345
|
+
if (configPath === undefined || (noBase && f.has('--base'))) {
|
|
346
|
+
io.err(USAGE);
|
|
347
|
+
return 2;
|
|
348
|
+
}
|
|
349
|
+
const dir = resolve(f.get('--root') ?? '.');
|
|
350
|
+
const root = topOf(dir);
|
|
351
|
+
if (root === undefined) {
|
|
352
|
+
io.err(`${P} ${dir} is not in a git repository`);
|
|
353
|
+
return 2;
|
|
354
|
+
}
|
|
355
|
+
const git = gitAt(root);
|
|
356
|
+
try {
|
|
357
|
+
const config = spec.parse(readFileSync(resolve(root, configPath), 'utf8'));
|
|
358
|
+
const baseline = readBaseline(root, config.baseline, (source, where) => spec.parseBaseline(source, where, config, false));
|
|
359
|
+
const roots = spec.roots(config);
|
|
360
|
+
const files = trackedUnder(git, root, roots);
|
|
361
|
+
const empty = emptyRoots(files, roots);
|
|
362
|
+
if (empty.length > 0)
|
|
363
|
+
throw new Error(`no tracked file lies under ${empty.join(', ')}, so discovery there would read nothing`);
|
|
364
|
+
const ts = () => {
|
|
365
|
+
const loaded = (io.typescript ?? (() => loadTypescript(typescriptLoaders(root))))();
|
|
366
|
+
if (typeof loaded === 'string')
|
|
367
|
+
throw new NoTypescript(loaded);
|
|
368
|
+
return loaded;
|
|
369
|
+
};
|
|
370
|
+
const found = spec.discover(root, files, config, ts);
|
|
371
|
+
// Each kind discovery reads must find something: one finding nothing is a
|
|
372
|
+
// config (or a discovery) that checks nothing of that kind, never a pass.
|
|
373
|
+
const none = Object.entries(found).find(([, ids]) => ids.length === 0)?.[0];
|
|
374
|
+
if (none !== undefined) {
|
|
375
|
+
const { noun, roots: under } = spec.kind(none, config);
|
|
376
|
+
throw new Error(`discovery finds no ${noun} under ${under.join(', ')}, so nothing of that kind would be checked`);
|
|
377
|
+
}
|
|
378
|
+
let grown = [];
|
|
379
|
+
if (!noBase) {
|
|
380
|
+
const asked = f.get('--base');
|
|
381
|
+
const base = asked === undefined ? resolveBase(git) : resolveBase(git, [asked]);
|
|
382
|
+
if (base === undefined) {
|
|
383
|
+
throw new Error(asked === undefined ? `none of ${BASE_CANDIDATES.join(', ')} names a commit, so the baseline cannot be held to its merge base; fetch one, pass --base, or pass --no-base` : `--base ${asked} names no commit`);
|
|
384
|
+
}
|
|
385
|
+
grown = growthSince(git, base, config.baseline, baseline, (source, where) => spec.parseBaseline(source, where, config, true), spec.fileOf);
|
|
386
|
+
}
|
|
387
|
+
const notChecked = [...config.skipped, ...(noBase ? ['baseline growth (--no-base)'] : [])];
|
|
388
|
+
const result = spec.format(spec.findings(root, config, found, baseline, grown, ts), found, baseline, notChecked, !noBase);
|
|
389
|
+
result.out.forEach(io.out);
|
|
390
|
+
result.err.forEach(io.err);
|
|
391
|
+
return result.ok ? 0 : 1;
|
|
392
|
+
}
|
|
393
|
+
catch (e) {
|
|
394
|
+
io.err(e instanceof NoTypescript ? `${P} ${e.message}` : `${P} ${e.message}; nothing was checked, so this is not a pass`);
|
|
395
|
+
return 2;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
const CODEC_REGISTRY = {
|
|
399
|
+
name: 'codec-registry',
|
|
400
|
+
parse: parseCodecConfig,
|
|
401
|
+
parseBaseline: (source, where, _config, atBase) => parseCodecBaseline(source, where, atBase),
|
|
402
|
+
roots: (config) => config.roots,
|
|
403
|
+
discover: (root, files, config, ts) => discoverCodecs(ts(), root, files, new Set(Object.keys(config.oracles))),
|
|
404
|
+
kind: (list, config) => ({ noun: list === 'handParsers' ? 'hand parser' : 'producer/consumer pair', roots: config.roots }),
|
|
405
|
+
fileOf: codecFileOf,
|
|
406
|
+
findings: (root, config, found, baseline, grown, ts) => codecFindings(ts(), root, config, found, baseline, grown),
|
|
407
|
+
format: (list, found, baseline, notChecked, compared) => formatCodecFindings(list, found, baseline, notChecked, compared),
|
|
408
|
+
};
|
|
409
|
+
const FAULT_INJECTION_REGISTRY = {
|
|
410
|
+
name: 'fault-injection-registry',
|
|
411
|
+
parse: parseFaultConfig,
|
|
412
|
+
parseBaseline: parseFaultBaseline,
|
|
413
|
+
roots: (config) => [...new Set(Object.values(config.discover).flatMap((g) => g.roots))],
|
|
414
|
+
discover: (root, files, config) => Object.fromEntries(Object.entries(config.discover).map(([name, group]) => [name, discoverGroup(root, files.filter((file) => group.roots.some((r) => file === r || file.startsWith(`${r}/`))), group)])),
|
|
415
|
+
kind: (list, config) => ({ noun: `${list} source`, roots: config.discover[list]?.roots ?? [] }),
|
|
416
|
+
fileOf: wholeId,
|
|
417
|
+
findings: (root, config, found, baseline, grown, ts) => faultFindings(ts(), root, config, found, baseline, grown),
|
|
418
|
+
format: formatFaultFindings,
|
|
419
|
+
};
|
|
420
|
+
const codecRegistry = (f, io) => runRatchet(CODEC_REGISTRY, f, io);
|
|
421
|
+
const faultInjectionRegistry = (f, io) => runRatchet(FAULT_INJECTION_REGISTRY, f, io);
|
|
321
422
|
export const DEFAULT_SETTINGS = '.claude/settings.json';
|
|
322
423
|
const requiredSettings = (f, io) => {
|
|
323
424
|
const P = 'pariksha required-settings:';
|
|
@@ -366,7 +467,9 @@ const requiredSettings = (f, io) => {
|
|
|
366
467
|
return result.ok ? 0 : 1;
|
|
367
468
|
};
|
|
368
469
|
const COMMANDS = {
|
|
470
|
+
'codec-registry': { allowed: ['--config', '--base', '--root'], switches: ['--no-base'], run: codecRegistry },
|
|
369
471
|
'coverage-exemptions': { allowed: ['--config', '--package', '--root'], run: coverageExemptions },
|
|
472
|
+
'fault-injection-registry': { allowed: ['--config', '--base', '--root'], switches: ['--no-base'], run: faultInjectionRegistry },
|
|
370
473
|
'authority-citations': { allowed: ['--mode', '--authority', '--issue-prefix', '--adr-dir', '--root'], run: authorityCitations },
|
|
371
474
|
boundary: { allowed: ['--root', '--deny'], run: boundary },
|
|
372
475
|
'coverage-by-type': { allowed: ['--config', '--files-from', '--root'], run: coverageByType },
|
|
@@ -386,7 +489,7 @@ export const run = (args, io, version = ownVersion(), env = process.env) => {
|
|
|
386
489
|
return 0;
|
|
387
490
|
}
|
|
388
491
|
const spec = command !== undefined && Object.hasOwn(COMMANDS, command) ? COMMANDS[command] : undefined;
|
|
389
|
-
const f = spec && flags(rest, spec.allowed);
|
|
492
|
+
const f = spec && flags(rest, spec.allowed, spec.switches);
|
|
390
493
|
if (!spec || !f) {
|
|
391
494
|
io.err(USAGE);
|
|
392
495
|
return 2;
|