@veda-hq/pariksha 0.5.7 → 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 +206 -5
- package/baselines/claude-settings.json +14 -0
- 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/dist/required-settings.js +54 -20
- package/package.json +6 -4
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
// A setting a project depends on (Claude Code's `advisorModel`, say) lives in
|
|
4
4
|
// one committed line, and one edit deletes it without any test noticing. The
|
|
5
5
|
// project lists what it requires, with why, and this check refuses a settings
|
|
6
|
-
// file that lacks a key
|
|
6
|
+
// file that lacks a key, holds another value, or drops an entry from a list
|
|
7
|
+
// (a permission rule, say) the project requires.
|
|
7
8
|
import { isDeepStrictEqual } from 'node:util';
|
|
8
9
|
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
9
10
|
/**
|
|
10
|
-
* Parses the requirements file:
|
|
11
|
+
* Parses the requirements file:
|
|
12
|
+
* `{ "<key>": { "value": <any JSON>, "why": "…" } | { "includes": [<any JSON>, …], "why": "…" }, … }`.
|
|
11
13
|
* Throws on anything else, so a malformed list never passes as an empty one.
|
|
12
14
|
*/
|
|
13
15
|
export function parseRequirements(text) {
|
|
@@ -26,14 +28,26 @@ export function parseRequirements(text) {
|
|
|
26
28
|
continue;
|
|
27
29
|
if (key === '' || key.split('.').some((part) => part === ''))
|
|
28
30
|
throw new Error(`"${key}" is not a key or dotted path`);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
const shape = `"${key}" needs { "value": …, "why": "…" } or { "includes": […], "why": "…" }`;
|
|
32
|
+
if (!isRecord(spec))
|
|
33
|
+
throw new Error(shape);
|
|
34
|
+
const hasValue = Object.hasOwn(spec, 'value');
|
|
35
|
+
const hasIncludes = Object.hasOwn(spec, 'includes');
|
|
36
|
+
if (hasValue === hasIncludes)
|
|
37
|
+
throw new Error(shape);
|
|
38
|
+
const extra = Object.keys(spec).filter((k) => k !== 'value' && k !== 'includes' && k !== 'why');
|
|
32
39
|
if (extra.length > 0)
|
|
33
40
|
throw new Error(`"${key}": unknown key ${extra.map((k) => `"${k}"`).join(', ')}`);
|
|
34
41
|
if (typeof spec.why !== 'string' || spec.why.trim() === '')
|
|
35
42
|
throw new Error(`"${key}" needs a "why"`);
|
|
36
|
-
|
|
43
|
+
if (!hasIncludes) {
|
|
44
|
+
out.push({ key, kind: 'value', expected: spec.value, why: spec.why });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const includes = spec.includes;
|
|
48
|
+
if (!Array.isArray(includes) || includes.length === 0)
|
|
49
|
+
throw new Error(`"${key}": "includes" must list at least one entry`);
|
|
50
|
+
out.push({ key, kind: 'includes', expected: includes, why: spec.why });
|
|
37
51
|
}
|
|
38
52
|
if (out.length === 0)
|
|
39
53
|
throw new Error('the requirements list no key');
|
|
@@ -49,7 +63,25 @@ const lookup = (settings, key) => {
|
|
|
49
63
|
}
|
|
50
64
|
return { missing: false, value: at };
|
|
51
65
|
};
|
|
52
|
-
/**
|
|
66
|
+
/** The finding for one requirement, or undefined when the settings meet it. */
|
|
67
|
+
const judge = (settings, r) => {
|
|
68
|
+
const got = lookup(settings, r.key);
|
|
69
|
+
const base = { key: r.key, kind: r.kind, why: r.why };
|
|
70
|
+
if (r.kind === 'value') {
|
|
71
|
+
if (got.missing)
|
|
72
|
+
return { ...base, expected: r.expected };
|
|
73
|
+
return isDeepStrictEqual(got.value, r.expected) ? undefined : { ...base, expected: r.expected, found: got.value };
|
|
74
|
+
}
|
|
75
|
+
const wanted = r.expected;
|
|
76
|
+
if (got.missing)
|
|
77
|
+
return { ...base, expected: wanted };
|
|
78
|
+
if (!Array.isArray(got.value))
|
|
79
|
+
return { ...base, expected: wanted, found: got.value };
|
|
80
|
+
const list = got.value;
|
|
81
|
+
const lacking = wanted.filter((entry) => !list.some((have) => isDeepStrictEqual(have, entry)));
|
|
82
|
+
return lacking.length === 0 ? undefined : { ...base, expected: lacking, found: got.value };
|
|
83
|
+
};
|
|
84
|
+
/** Every requirement the settings break: a missing key, a different value, or a list without a required entry. */
|
|
53
85
|
export function findMissing(settingsText, requirements) {
|
|
54
86
|
let settings;
|
|
55
87
|
try {
|
|
@@ -60,16 +92,21 @@ export function findMissing(settingsText, requirements) {
|
|
|
60
92
|
}
|
|
61
93
|
if (!isRecord(settings))
|
|
62
94
|
throw new Error('the settings must hold an object');
|
|
63
|
-
|
|
64
|
-
for (const r of requirements) {
|
|
65
|
-
const got = lookup(settings, r.key);
|
|
66
|
-
if (got.missing)
|
|
67
|
-
findings.push({ key: r.key, expected: r.value, why: r.why });
|
|
68
|
-
else if (!isDeepStrictEqual(got.value, r.value))
|
|
69
|
-
findings.push({ key: r.key, expected: r.value, found: got.value, why: r.why });
|
|
70
|
-
}
|
|
71
|
-
return findings;
|
|
95
|
+
return requirements.flatMap((r) => judge(settings, r) ?? []);
|
|
72
96
|
}
|
|
97
|
+
/** One finding as a line: what is wrong and how to fix it. */
|
|
98
|
+
const describe = (f) => {
|
|
99
|
+
if (f.kind === 'value') {
|
|
100
|
+
const has = Object.hasOwn(f, 'found') ? `holds ${JSON.stringify(f.found)}` : 'is missing';
|
|
101
|
+
return `"${f.key}" ${has}; it must be ${JSON.stringify(f.expected)}`;
|
|
102
|
+
}
|
|
103
|
+
const entries = f.expected.map((e) => JSON.stringify(e)).join(', ');
|
|
104
|
+
if (!Object.hasOwn(f, 'found'))
|
|
105
|
+
return `"${f.key}" is missing; it must be a list holding ${entries}`;
|
|
106
|
+
if (!Array.isArray(f.found))
|
|
107
|
+
return `"${f.key}" holds ${JSON.stringify(f.found)}, not a list; it must be a list holding ${entries}`;
|
|
108
|
+
return `"${f.key}" lacks ${entries}`;
|
|
109
|
+
};
|
|
73
110
|
/** The check's lines: one per finding on err, or one pass line on out. */
|
|
74
111
|
export function formatFindings(findings, count, settingsPath, requirePath) {
|
|
75
112
|
const P = 'pariksha required-settings:';
|
|
@@ -78,9 +115,6 @@ export function formatFindings(findings, count, settingsPath, requirePath) {
|
|
|
78
115
|
return {
|
|
79
116
|
ok: false,
|
|
80
117
|
out: [],
|
|
81
|
-
err: findings.map((f) => {
|
|
82
|
-
const has = Object.hasOwn(f, 'found') ? `holds ${JSON.stringify(f.found)}` : 'is missing';
|
|
83
|
-
return `${P} ${settingsPath}: "${f.key}" ${has}; it must be ${JSON.stringify(f.expected)} (${f.why}). Put it back; to change the requirement, edit ${requirePath}.`;
|
|
84
|
-
}),
|
|
118
|
+
err: findings.map((f) => `${P} ${settingsPath}: ${describe(f)} (${f.why}). Put it back; to change the requirement, edit ${requirePath}.`),
|
|
85
119
|
};
|
|
86
120
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veda-hq/pariksha",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Repository checks: doc drift, rule enforcement, test-quality ratchets. Checks: authority-citations, boundary, coverage-by-type, coverage-exemptions, doc-budgets, env-gate-switches, export-deletions, required-settings. Also markdownlint rules for Markdown that renders mangled: @veda-hq/pariksha/markdownlint.",
|
|
3
|
+
"version": "0.5.9",
|
|
4
|
+
"description": "Repository checks: doc drift, rule enforcement, test-quality ratchets. Checks: authority-citations, boundary, codec-registry, coverage-by-type, coverage-exemptions, doc-budgets, env-gate-switches, export-deletions, fault-injection-registry, required-settings. Also markdownlint rules for Markdown that renders mangled: @veda-hq/pariksha/markdownlint.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"repository checks",
|
|
7
7
|
"lint",
|
|
@@ -31,10 +31,12 @@
|
|
|
31
31
|
"exports": {
|
|
32
32
|
"./markdownlint": "./dist/markdownlint-rules.js",
|
|
33
33
|
"./dist/*": "./dist/*",
|
|
34
|
+
"./baselines/*": "./baselines/*",
|
|
34
35
|
"./package.json": "./package.json"
|
|
35
36
|
},
|
|
36
37
|
"files": [
|
|
37
|
-
"dist"
|
|
38
|
+
"dist",
|
|
39
|
+
"baselines"
|
|
38
40
|
],
|
|
39
41
|
"peerDependencies": {
|
|
40
42
|
"typescript": ">=5.4 <6"
|
|
@@ -52,7 +54,7 @@
|
|
|
52
54
|
"build": "tsc -p tsconfig.build.json",
|
|
53
55
|
"typecheck": "tsc -p tsconfig.json",
|
|
54
56
|
"test": "node --test --experimental-test-coverage --test-coverage-lines=95 --test-coverage-functions=95 --test-coverage-branches=90 --test-coverage-exclude=src/**/*.test.ts src/**/*.test.ts",
|
|
55
|
-
"smoke": "node dist/cli.js --version && node dist/cli.js boundary --root ../.. && node dist/cli.js required-settings --require .veda/required-settings.json --root ../.. &&node --input-type=module -e \"import { createRequire } from 'node:module'; import { pathToFileURL } from 'node:url'; const id = '@veda-hq/pariksha/markdownlint'; const req = createRequire(process.cwd() + '/'); const required = req(id); const imported = (await import(pathToFileURL(req.resolve(id, { paths: [process.cwd()] })).href)).default; if (!Array.isArray(required) || required !== imported || required.length !== 12) throw new Error(id + ' does not load as markdownlint-cli2 loads it');\"",
|
|
57
|
+
"smoke": "node dist/cli.js --version && node dist/cli.js boundary --root ../.. && node dist/cli.js required-settings --require .veda/required-settings.json --root ../.. && node dist/cli.js required-settings --require packages/pariksha/baselines/claude-settings.json --root ../.. && node --input-type=module -e \"import { createRequire } from 'node:module'; import { pathToFileURL } from 'node:url'; const id = '@veda-hq/pariksha/markdownlint'; const req = createRequire(process.cwd() + '/'); const required = req(id); const imported = (await import(pathToFileURL(req.resolve(id, { paths: [process.cwd()] })).href)).default; if (!Array.isArray(required) || required !== imported || required.length !== 12) throw new Error(id + ' does not load as markdownlint-cli2 loads it');\"",
|
|
56
58
|
"check": "pnpm typecheck && pnpm test && pnpm build && pnpm smoke"
|
|
57
59
|
}
|
|
58
60
|
}
|