@veda-hq/pariksha 0.5.13 → 0.5.17
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/NOTICE +6 -0
- package/README.md +280 -5
- package/dist/absolute-paths.js +59 -0
- package/dist/aggregate-gate-needs.js +153 -0
- package/dist/cli.js +130 -0
- package/dist/doc-command-rules.js +179 -0
- package/dist/doc-command-targets.js +122 -0
- package/dist/doc-commands.js +187 -0
- package/dist/doc-files.js +74 -0
- package/dist/doc-flag-spec.js +87 -0
- package/dist/doc-identifiers.js +348 -0
- package/dist/doc-links.js +183 -0
- package/dist/doc-paths.js +267 -0
- package/dist/doc-references-config.js +172 -0
- package/dist/doc-references-gate.js +338 -0
- package/dist/doc-references.js +535 -0
- package/dist/doc-scripts.js +224 -0
- package/dist/non-synced-resources.js +164 -0
- package/dist/pnpm-settings.js +350 -0
- package/dist/semver-range.js +320 -0
- package/dist/shell-text.js +193 -0
- package/package.json +5 -3
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// The doc-references gate: DOC-DRIFT-01 (paths, links, anchors, file:line),
|
|
2
|
+
// -02 (documented scripts and commands) and -04 (code identifiers), each
|
|
3
|
+
// held to a per-file baseline whose counts only fall.
|
|
4
|
+
//
|
|
5
|
+
// staged a commit: the index. Staged docs and comments, plus any doc
|
|
6
|
+
// naming a path the commit deletes or renames. DOC-DRIFT-01, -02.
|
|
7
|
+
// range a push: the pushed tree against its merge base. Changed docs, and
|
|
8
|
+
// docs naming a deleted or renamed path, a changed file by
|
|
9
|
+
// `name:line`, or an identifier, script or flag the diff removed.
|
|
10
|
+
// All three checks. A finding the merge base already had is legacy.
|
|
11
|
+
// all every reference file in the worktree, every check; it alone may
|
|
12
|
+
// lower (`update`) or seed (`write`) the baseline.
|
|
13
|
+
//
|
|
14
|
+
// The baseline holds counts for files whose references predate the check. A
|
|
15
|
+
// checked file may not exceed its count; a file under its count must lower
|
|
16
|
+
// it in the same change; the baseline file may only lose entries or lower
|
|
17
|
+
// counts; an entry for a file this change removed must go.
|
|
18
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
|
+
import { join, posix } from 'node:path';
|
|
20
|
+
import { findCommandProblems } from "./doc-command-rules.js";
|
|
21
|
+
import { findIdentifierProblems, manifestObject } from "./doc-identifiers.js";
|
|
22
|
+
import { findLinkProblems } from "./doc-links.js";
|
|
23
|
+
import { findPathProblems } from "./doc-paths.js";
|
|
24
|
+
import { applyBaseline, baselineGrowth, CHECK_IDS, createSnapshot, docKind, emptyBaseline, git, gitOrUndefined, parseBaseline, referenceFiles, serializeBaseline, takenFromMerge } from "./doc-references.js";
|
|
25
|
+
import { definedScripts, findScriptProblems, scriptBodies, scriptScopes } from "./doc-scripts.js";
|
|
26
|
+
/** Runs `checks` over `files` in `snapshot`; one finding once. */
|
|
27
|
+
export const runChecks = (config, snapshot, files, checks, links) => {
|
|
28
|
+
const problems = [];
|
|
29
|
+
const read = (path) => snapshot.read(path);
|
|
30
|
+
if (checks.includes('DOC-DRIFT-01'))
|
|
31
|
+
problems.push(...findPathProblems(config, snapshot, files), ...findLinkProblems(config, snapshot, files, links));
|
|
32
|
+
if (checks.includes('DOC-DRIFT-02')) {
|
|
33
|
+
const docs = files.filter((file) => docKind(config, file) === 'markdown');
|
|
34
|
+
const { manifests } = config.scripts;
|
|
35
|
+
const scripts = findScriptProblems(config, docs, definedScripts(snapshot.root, manifests, read), scriptScopes(snapshot.root, manifests, read), read);
|
|
36
|
+
const commands = findCommandProblems(config, { read, exists: (path) => snapshot.files.has(path), ...scriptBodies(manifests, read) }, docs);
|
|
37
|
+
for (const p of [...scripts, ...commands])
|
|
38
|
+
problems.push({ check: 'DOC-DRIFT-02', file: p.file, line: p.line, ref: p.script, reason: p.reason });
|
|
39
|
+
}
|
|
40
|
+
if (checks.includes('DOC-DRIFT-04'))
|
|
41
|
+
problems.push(...findIdentifierProblems(config, snapshot, files));
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
return problems.filter((p) => {
|
|
44
|
+
const key = `${p.check}\0${p.file}\0${p.line}\0${p.ref}\0${p.reason}`;
|
|
45
|
+
if (seen.has(key))
|
|
46
|
+
return false;
|
|
47
|
+
seen.add(key);
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The failures this change introduced. A push is judged against its merge
|
|
53
|
+
* base: a finding the base already had (same file, check and reference) is
|
|
54
|
+
* legacy, however far it runs past the file's count, because the branch did
|
|
55
|
+
* not write it. `baseOf` maps a file to its path at the base (a rename's old
|
|
56
|
+
* path). Each base finding excuses one failure. With no failure the base is
|
|
57
|
+
* never read.
|
|
58
|
+
*/
|
|
59
|
+
export const introducedFailures = (failures, baseProblems, baseOf) => {
|
|
60
|
+
if (failures.length === 0)
|
|
61
|
+
return [];
|
|
62
|
+
const key = (file, p) => `${file}\0${p.check}\0${p.ref}`;
|
|
63
|
+
const legacy = new Map();
|
|
64
|
+
for (const p of baseProblems([...new Set(failures.map((f) => baseOf(f.file)))]))
|
|
65
|
+
legacy.set(key(p.file, p), (legacy.get(key(p.file, p)) ?? 0) + 1);
|
|
66
|
+
return failures.filter((p) => {
|
|
67
|
+
const k = key(baseOf(p.file), p);
|
|
68
|
+
const left = legacy.get(k) ?? 0;
|
|
69
|
+
if (left === 0)
|
|
70
|
+
return true;
|
|
71
|
+
legacy.set(k, left - 1);
|
|
72
|
+
return false;
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
/** `git diff --name-status -z` entries. */
|
|
76
|
+
export const parseNameStatus = (output) => {
|
|
77
|
+
const fields = output.split('\0').filter(Boolean);
|
|
78
|
+
const changes = [];
|
|
79
|
+
for (let i = 0; i < fields.length;) {
|
|
80
|
+
const status = String(fields[i]);
|
|
81
|
+
if (/^[RC]/.test(status)) {
|
|
82
|
+
changes.push({ status: status.charAt(0), from: String(fields[i + 1]), path: String(fields[i + 2]) });
|
|
83
|
+
i += 3;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
changes.push({ status: status.charAt(0), path: String(fields[i + 1]), from: String(fields[i + 1]) });
|
|
87
|
+
i += 2;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return changes;
|
|
91
|
+
};
|
|
92
|
+
const IDENTIFIER_WORD = /[A-Za-z_$][\w$]*/g;
|
|
93
|
+
const LOOKS_LIKE_IDENTIFIER = /^(?:[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+|[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)+|[A-Z][a-z0-9]+(?:[A-Z][a-z0-9]*)+)$/;
|
|
94
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
95
|
+
/**
|
|
96
|
+
* Reference files that cite something this change took away: a deleted or
|
|
97
|
+
* renamed path (by basename, so relative links count), a changed file by
|
|
98
|
+
* `name:line`, an identifier whose last definition the diff removed, or a
|
|
99
|
+
* removed term (a script name, a `--flag`).
|
|
100
|
+
*/
|
|
101
|
+
export const referrers = (config, snapshot, removedPaths, lineCited, removedWords, removedTerms = []) => {
|
|
102
|
+
if (removedPaths.length === 0 && lineCited.length === 0 && removedWords.size === 0 && removedTerms.length === 0)
|
|
103
|
+
return [];
|
|
104
|
+
const names = [...new Set([...removedPaths.map((path) => posix.basename(path)), ...removedTerms])];
|
|
105
|
+
// The basename starts a path segment: `check-x.sh:11` does not cite a changed `x.sh`.
|
|
106
|
+
const cited = [...new Set(lineCited.map((path) => posix.basename(path)))].map((name) => new RegExp(`(?<![\\w.-])${escapeRegExp(name)}(?::|#L)\\d`));
|
|
107
|
+
const out = [];
|
|
108
|
+
for (const [file, text] of snapshot.readMany(referenceFiles(config, snapshot))) {
|
|
109
|
+
if (names.some((name) => text.includes(name)) || cited.some((pattern) => pattern.test(text))) {
|
|
110
|
+
out.push(file);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (removedWords.size !== 0 && docKind(config, file) === 'markdown') {
|
|
114
|
+
for (const match of text.matchAll(IDENTIFIER_WORD)) {
|
|
115
|
+
if (removedWords.has(match[0])) {
|
|
116
|
+
out.push(file);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
};
|
|
124
|
+
const readBaseline = (text, where) => (text === undefined ? emptyBaseline() : parseBaseline(text, where));
|
|
125
|
+
/** Script names a manifest change removed (`pnpm <name>` now runs nothing); an unparsable manifest names none. */
|
|
126
|
+
const removedScripts = (before, after) => {
|
|
127
|
+
const names = (text) => {
|
|
128
|
+
const scripts = text === undefined ? undefined : manifestObject(text)?.['scripts'];
|
|
129
|
+
return new Set(typeof scripts === 'object' && scripts !== null && !Array.isArray(scripts) ? Object.keys(scripts) : []);
|
|
130
|
+
};
|
|
131
|
+
const now = names(after);
|
|
132
|
+
return [...names(before)].filter((name) => !now.has(name));
|
|
133
|
+
};
|
|
134
|
+
/** Files whose baseline entry an edit changed: each is re-checked. */
|
|
135
|
+
const baselineEdits = (previous, current) => {
|
|
136
|
+
if (previous === undefined)
|
|
137
|
+
return Object.keys(current.files);
|
|
138
|
+
const files = new Set([...Object.keys(previous.files), ...Object.keys(current.files)]);
|
|
139
|
+
return [...files].filter((file) => JSON.stringify(previous.files[file] ?? {}) !== JSON.stringify(current.files[file] ?? {}));
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* The hunk lines of a unified diff: everything after a file's first `@@`
|
|
143
|
+
* until the next `diff --git`. The `---`/`+++` file headers come before the
|
|
144
|
+
* first hunk, so a removed line whose content starts `--` is still read.
|
|
145
|
+
*/
|
|
146
|
+
export const hunkLines = (diff) => {
|
|
147
|
+
const lines = [];
|
|
148
|
+
let inHunk = false;
|
|
149
|
+
for (const line of diff.split('\n')) {
|
|
150
|
+
if (line.startsWith('diff --git '))
|
|
151
|
+
inHunk = false;
|
|
152
|
+
else if (line.startsWith('@@'))
|
|
153
|
+
inHunk = true;
|
|
154
|
+
else if (inHunk)
|
|
155
|
+
lines.push(line);
|
|
156
|
+
}
|
|
157
|
+
return lines;
|
|
158
|
+
};
|
|
159
|
+
/** What the diff's removed lines held that its added lines do not, by `pattern`, kept when `keep` says so. */
|
|
160
|
+
const removedTokens = (diff, pattern, keep) => {
|
|
161
|
+
const removed = new Set();
|
|
162
|
+
const added = new Set();
|
|
163
|
+
for (const line of hunkLines(diff)) {
|
|
164
|
+
const target = line.startsWith('-') ? removed : line.startsWith('+') ? added : null;
|
|
165
|
+
if (target === null)
|
|
166
|
+
continue;
|
|
167
|
+
for (const match of line.matchAll(pattern))
|
|
168
|
+
if (keep(match[0]))
|
|
169
|
+
target.add(match[0]);
|
|
170
|
+
}
|
|
171
|
+
for (const token of added)
|
|
172
|
+
removed.delete(token);
|
|
173
|
+
return removed;
|
|
174
|
+
};
|
|
175
|
+
/** `--flags` the diff's removed lines held that its added lines do not. */
|
|
176
|
+
export const removedFlags = (diff) => [...removedTokens(diff, /--[a-z][\w-]+/g, () => true)];
|
|
177
|
+
/** Identifier-shaped words the diff's removed lines held that its added lines do not. */
|
|
178
|
+
export const removedIdentifiers = (diff) => removedTokens(diff, IDENTIFIER_WORD, (word) => LOOKS_LIKE_IDENTIFIER.test(word));
|
|
179
|
+
export const runGate = (options) => {
|
|
180
|
+
const { root, config, mode, checks, links } = options;
|
|
181
|
+
const baselineFile = config.baseline;
|
|
182
|
+
let snapshot;
|
|
183
|
+
let changed;
|
|
184
|
+
let checked;
|
|
185
|
+
let previous;
|
|
186
|
+
// A push's merge base and head; a commit and --all have none.
|
|
187
|
+
let range;
|
|
188
|
+
// A file's path at the merge base: a rename's old path.
|
|
189
|
+
let baseOf = (file) => file;
|
|
190
|
+
// Baseline entries whose file this change answers for if it is gone: every
|
|
191
|
+
// entry under --all; otherwise the paths the change removed and the entries
|
|
192
|
+
// it edited. A doc another change deleted is that change's to clear.
|
|
193
|
+
let answersForGone = () => true;
|
|
194
|
+
if (mode.kind === 'all') {
|
|
195
|
+
snapshot = createSnapshot(root, { kind: 'worktree' });
|
|
196
|
+
checked = referenceFiles(config, snapshot);
|
|
197
|
+
changed = checked;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
let diffArgs;
|
|
201
|
+
// What the change is compared with: HEAD for a commit, the merge base for a push.
|
|
202
|
+
let before;
|
|
203
|
+
// Mid-merge, what the merge took from MERGE_HEAD unchanged is that
|
|
204
|
+
// branch's to judge, however it comes in. A push skips nothing.
|
|
205
|
+
let taken = new Set();
|
|
206
|
+
if (mode.kind === 'staged') {
|
|
207
|
+
taken = takenFromMerge(root);
|
|
208
|
+
snapshot = createSnapshot(root, { kind: 'index' });
|
|
209
|
+
const hasHead = gitOrUndefined(root, ['show', 'HEAD:']) !== undefined;
|
|
210
|
+
diffArgs = ['diff', '--cached', '--name-status', '-z', '-M', ...(hasHead ? ['HEAD'] : [])];
|
|
211
|
+
before = (path) => (hasHead ? gitOrUndefined(root, ['show', `HEAD:${path}`]) : undefined);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const base = git(root, ['merge-base', mode.base, mode.head]).trim();
|
|
215
|
+
range = { base, head: mode.head };
|
|
216
|
+
snapshot = createSnapshot(root, { kind: 'ref', ref: mode.head });
|
|
217
|
+
diffArgs = ['diff', '--name-status', '-z', '-M', base, mode.head];
|
|
218
|
+
before = (path) => gitOrUndefined(root, ['show', `${base}:${path}`]);
|
|
219
|
+
}
|
|
220
|
+
const priorText = before(baselineFile);
|
|
221
|
+
previous = priorText === undefined ? undefined : parseBaseline(priorText, `${baselineFile} (before this change)`);
|
|
222
|
+
const changes = parseNameStatus(git(root, diffArgs)).filter((change) => !taken.has(change.path));
|
|
223
|
+
const renamed = new Map(changes.filter((change) => change.status === 'R').map((change) => [change.path, change.from]));
|
|
224
|
+
baseOf = (file) => renamed.get(file) ?? file;
|
|
225
|
+
changed = changes.filter((change) => change.status !== 'D' && docKind(config, change.path) !== null).map((change) => change.path);
|
|
226
|
+
const removedPaths = changes.flatMap((change) => (change.status === 'D' ? [change.path] : change.status === 'R' ? [change.from] : []));
|
|
227
|
+
// A script removed from a manifest strands every doc that runs it.
|
|
228
|
+
const terms = changes.filter((change) => change.path.endsWith('package.json')).flatMap((change) => removedScripts(before(change.from), snapshot.read(change.path)));
|
|
229
|
+
let lineCited = [];
|
|
230
|
+
let words = new Set();
|
|
231
|
+
if (range !== undefined) {
|
|
232
|
+
// Pushed: a changed file can move the line a doc cites, and a removed
|
|
233
|
+
// definition or flag strands the docs that name it.
|
|
234
|
+
lineCited = changes.filter((change) => change.status === 'M').map((change) => change.path);
|
|
235
|
+
const diff = git(root, ['diff', '-U0', '--no-color', '--no-ext-diff', '--no-textconv', range.base, range.head, '--', '.', ...config.identifiers.diffExclude.map((p) => `:(exclude)${p}`)]);
|
|
236
|
+
terms.push(...removedFlags(diff));
|
|
237
|
+
if (checks.includes('DOC-DRIFT-04'))
|
|
238
|
+
words = removedIdentifiers(diff);
|
|
239
|
+
}
|
|
240
|
+
// Ignore rules decide which absent paths count, so a change to them re-checks every reference.
|
|
241
|
+
const everything = changes.some((change) => posix.basename(change.path) === '.gitignore');
|
|
242
|
+
const edited = baselineEdits(previous, readBaseline(snapshot.read(baselineFile), baselineFile));
|
|
243
|
+
// A seed (no earlier baseline) edits every entry, but deleted none of their files.
|
|
244
|
+
const answered = new Set([...removedPaths, ...(previous === undefined ? [] : edited)]);
|
|
245
|
+
answersForGone = (file) => answered.has(file);
|
|
246
|
+
checked = (everything
|
|
247
|
+
? referenceFiles(config, snapshot)
|
|
248
|
+
: [
|
|
249
|
+
...new Set([
|
|
250
|
+
...changed,
|
|
251
|
+
...referrers(config, snapshot, removedPaths, lineCited, words, terms),
|
|
252
|
+
// An entry dropped for a file the check no longer reads re-checks nothing.
|
|
253
|
+
...edited.filter((file) => snapshot.files.has(file) && docKind(config, file) !== null),
|
|
254
|
+
]),
|
|
255
|
+
].sort()).filter((file) => !taken.has(file));
|
|
256
|
+
}
|
|
257
|
+
// --all reads the worktree, where a baseline being written is not yet
|
|
258
|
+
// tracked; every other mode reads the snapshot it checks.
|
|
259
|
+
const current = readBaseline(mode.kind === 'all' && existsSync(join(root, baselineFile)) ? readFileSync(join(root, baselineFile), 'utf8') : snapshot.read(baselineFile), baselineFile);
|
|
260
|
+
// One batched read: an index or ref snapshot otherwise spawns a git process per file.
|
|
261
|
+
snapshot.readMany(checked);
|
|
262
|
+
const problems = runChecks(config, snapshot, checked, checks, links);
|
|
263
|
+
if (mode.kind === 'all' && options.baseline !== undefined) {
|
|
264
|
+
const counts = emptyBaseline();
|
|
265
|
+
for (const p of problems) {
|
|
266
|
+
const entry = (counts.files[p.file] ??= {});
|
|
267
|
+
entry[p.check] = (entry[p.check] ?? 0) + 1;
|
|
268
|
+
}
|
|
269
|
+
if (options.baseline === 'update') {
|
|
270
|
+
// Only ever lower: a count can fall to what remains, never rise.
|
|
271
|
+
for (const [file, entry] of Object.entries(counts.files)) {
|
|
272
|
+
for (const check of CHECK_IDS) {
|
|
273
|
+
const now = entry[check];
|
|
274
|
+
if (now !== undefined)
|
|
275
|
+
entry[check] = Math.min(now, current.files[file]?.[check] ?? 0);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const text = serializeBaseline(counts);
|
|
280
|
+
writeFileSync(join(root, baselineFile), text);
|
|
281
|
+
return { checked, changed, problems, failures: [], slack: [], growth: [], wrote: Object.keys(parseBaseline(text, baselineFile).files).length, exitCode: 0 };
|
|
282
|
+
}
|
|
283
|
+
const growth = [
|
|
284
|
+
...(previous === undefined ? [] : baselineGrowth(previous, current)),
|
|
285
|
+
// An entry for a file that is gone would hold an allowance for nothing.
|
|
286
|
+
...Object.keys(current.files)
|
|
287
|
+
.filter((file) => !snapshot.files.has(file) && answersForGone(file))
|
|
288
|
+
.map((file) => `${file}: no such file; remove its entry`),
|
|
289
|
+
];
|
|
290
|
+
const judged = applyBaseline(problems, current, checked, checks);
|
|
291
|
+
let { failures } = judged;
|
|
292
|
+
if (range !== undefined) {
|
|
293
|
+
// Over its count, a file still fails only for what the merge base did not already have.
|
|
294
|
+
const baseRef = range.base;
|
|
295
|
+
failures = introducedFailures(failures, (files) => {
|
|
296
|
+
const base = createSnapshot(root, { kind: 'ref', ref: baseRef });
|
|
297
|
+
const present = files.filter((file) => base.files.has(file));
|
|
298
|
+
base.readMany(present);
|
|
299
|
+
return runChecks(config, base, present, checks, links);
|
|
300
|
+
}, baseOf);
|
|
301
|
+
}
|
|
302
|
+
const changedSet = new Set(changed);
|
|
303
|
+
const blocking = judged.slack.some((entry) => changedSet.has(entry.file));
|
|
304
|
+
return { checked, changed, problems, failures, slack: judged.slack, growth, wrote: undefined, exitCode: failures.length > 0 || growth.length > 0 || blocking ? 1 : 0 };
|
|
305
|
+
};
|
|
306
|
+
const P = 'pariksha doc-references:';
|
|
307
|
+
/** What the run prints: problems and what to do on stderr; the one-line verdict on stdout. */
|
|
308
|
+
export const formatReport = (report, options, configPath) => {
|
|
309
|
+
const { config, mode, checks } = options;
|
|
310
|
+
const scope = mode.kind === 'staged' ? 'staged' : mode.kind === 'range' ? 'pushed' : 'all';
|
|
311
|
+
if (report.wrote !== undefined) {
|
|
312
|
+
return { ok: true, out: [`${P} wrote ${config.baseline}: ${report.wrote} file(s) hold ${report.problems.length} problem(s) (${report.checked.length} reference file(s) checked)`], err: [] };
|
|
313
|
+
}
|
|
314
|
+
const out = [];
|
|
315
|
+
const err = [];
|
|
316
|
+
for (const p of report.failures)
|
|
317
|
+
err.push(`${P} ${p.file}:${p.line}: [${p.check}] ${p.ref}: ${p.reason}`);
|
|
318
|
+
if (report.failures.length > 0) {
|
|
319
|
+
const files = new Set(report.failures.map((p) => p.file)).size;
|
|
320
|
+
err.push(`${P} ${report.failures.length} unresolved reference(s) in ${files} file(s). Fix each; one that names something absent on purpose (a quoted finding, a deleted or planned file) ends its line with <!-- ${config.exemptWord}: <why> --> (in code, a comment holding ${config.exemptWord}: <why>).`);
|
|
321
|
+
}
|
|
322
|
+
const changed = new Set(report.changed);
|
|
323
|
+
const lower = `pariksha doc-references --config ${configPath} --all --update-baseline`;
|
|
324
|
+
for (const entry of report.slack) {
|
|
325
|
+
const line = `${P} ${config.baseline}: ${entry.file} ${entry.check}: baseline ${entry.baseline}, now ${entry.actual}; lower it: ${lower}`;
|
|
326
|
+
(changed.has(entry.file) ? err : out).push(line);
|
|
327
|
+
}
|
|
328
|
+
for (const message of report.growth)
|
|
329
|
+
err.push(`${P} ${config.baseline}: ${message}`);
|
|
330
|
+
if (report.exitCode === 0) {
|
|
331
|
+
const notRun = CHECK_IDS.filter((id) => !checks.includes(id));
|
|
332
|
+
const ran = notRun.length === 0 ? scope : `${scope}; ${notRun.join(', ')} not run`;
|
|
333
|
+
out.push(`${P} ${report.checked.length} reference file(s) checked (${ran}), no new problem; the baseline does not grow`);
|
|
334
|
+
if (config.skipped.length > 0)
|
|
335
|
+
out.push(`${P} not checked: ${config.skipped.join(', ')}`);
|
|
336
|
+
}
|
|
337
|
+
return { ok: report.exitCode === 0, out, err };
|
|
338
|
+
};
|