@nerviq/cli 1.10.0 → 1.12.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/README.md +176 -47
- package/bin/cli.js +842 -287
- package/package.json +2 -2
- package/src/activity.js +225 -59
- package/src/adoption-advisor.js +299 -0
- package/src/aider/freshness.js +28 -25
- package/src/aider/techniques.js +16 -11
- package/src/analyze.js +131 -1
- package/src/anti-patterns.js +17 -2
- package/src/audit.js +197 -96
- package/src/behavioral-drift.js +801 -0
- package/src/benchmark.js +15 -10
- package/src/continuous-ops.js +681 -0
- package/src/cost-tracking.js +61 -0
- package/src/cursor/techniques.js +17 -12
- package/src/deep-review.js +83 -0
- package/src/diff-only.js +280 -0
- package/src/doctor.js +118 -55
- package/src/governance.js +72 -50
- package/src/hook-validation.js +342 -0
- package/src/index.js +7 -1
- package/src/integrations.js +144 -60
- package/src/mcp-validation.js +337 -0
- package/src/opencode/techniques.js +12 -7
- package/src/operating-profile.js +574 -0
- package/src/org.js +97 -13
- package/src/permission-rules.js +218 -0
- package/src/plans.js +192 -8
- package/src/platform-change-manifest.js +86 -0
- package/src/policy-layers.js +210 -0
- package/src/profiles.js +4 -1
- package/src/prompt-injection.js +74 -0
- package/src/repo-archetype.js +386 -0
- package/src/secret-patterns.js +9 -0
- package/src/server.js +398 -3
- package/src/setup.js +36 -2
- package/src/source-urls.js +132 -132
- package/src/supplemental-checks.js +13 -12
- package/src/techniques/api.js +407 -0
- package/src/techniques/automation.js +316 -0
- package/src/techniques/compliance.js +257 -0
- package/src/techniques/hygiene.js +294 -0
- package/src/techniques/instructions.js +243 -0
- package/src/techniques/observability.js +226 -0
- package/src/techniques/optimization.js +142 -0
- package/src/techniques/quality.js +317 -0
- package/src/techniques/security.js +237 -0
- package/src/techniques/shared.js +443 -0
- package/src/techniques/stacks.js +2294 -0
- package/src/techniques/tools.js +106 -0
- package/src/techniques/workflow.js +413 -0
- package/src/techniques.js +78 -5611
- package/src/terminology.js +73 -0
- package/src/token-estimate.js +35 -0
- package/src/watch.js +18 -0
- package/src/windsurf/techniques.js +17 -12
- package/src/workspace.js +105 -8
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nerviq/cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "The intelligent nervous system for AI coding agents — 2,
|
|
3
|
+
"version": "1.12.0",
|
|
4
|
+
"description": "The intelligent nervous system for AI coding agents — 2,441 checks (8 platforms × ~300 governance rules), 10 languages, 62 domain packs. Audit, align, and amplify.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"nerviq": "bin/cli.js",
|
package/src/activity.js
CHANGED
|
@@ -25,6 +25,8 @@ function getUserId() {
|
|
|
25
25
|
let _lastTimestamp = '';
|
|
26
26
|
let _counter = 0;
|
|
27
27
|
|
|
28
|
+
const SNAPSHOT_MILESTONES = ['baseline', 'post-fix', 'pre-upgrade', 'release'];
|
|
29
|
+
|
|
28
30
|
function timestampId() {
|
|
29
31
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
30
32
|
if (ts === _lastTimestamp) {
|
|
@@ -133,9 +135,66 @@ function summarizeSnapshot(snapshotKind, payload) {
|
|
|
133
135
|
};
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
if (snapshotKind === 'behavioral-drift') {
|
|
139
|
+
return {
|
|
140
|
+
score: payload.score,
|
|
141
|
+
sourceFiles: payload.repoSummary?.sourceFiles ?? 0,
|
|
142
|
+
findingCount: Array.isArray(payload.findings) ? payload.findings.length : 0,
|
|
143
|
+
driftLabels: Array.isArray(payload.driftLabels) ? payload.driftLabels.slice(0, 5) : [],
|
|
144
|
+
utilityShare: payload.structuralSignals?.utilityBalance?.utilityShare ?? null,
|
|
145
|
+
layerBreaks: payload.structuralSignals?.layering?.count ?? 0,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
136
149
|
return {};
|
|
137
150
|
}
|
|
138
151
|
|
|
152
|
+
function normalizeSnapshotTags(input) {
|
|
153
|
+
const values = Array.isArray(input) ? input : (input ? [input] : []);
|
|
154
|
+
const seen = new Set();
|
|
155
|
+
const tags = [];
|
|
156
|
+
|
|
157
|
+
for (const value of values) {
|
|
158
|
+
const parts = `${value || ''}`
|
|
159
|
+
.split(',')
|
|
160
|
+
.map((item) => item.replace(/\s+/g, ' ').trim())
|
|
161
|
+
.filter(Boolean);
|
|
162
|
+
|
|
163
|
+
for (const part of parts) {
|
|
164
|
+
const key = part.toLowerCase();
|
|
165
|
+
if (seen.has(key)) continue;
|
|
166
|
+
seen.add(key);
|
|
167
|
+
tags.push(part.slice(0, 48));
|
|
168
|
+
if (tags.length >= 8) {
|
|
169
|
+
return tags;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return tags;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function formatSnapshotTags(tags = []) {
|
|
178
|
+
const normalized = normalizeSnapshotTags(tags);
|
|
179
|
+
if (normalized.length === 0) return '';
|
|
180
|
+
return ` [${normalized.join(', ')}]`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function normalizeSnapshotMilestone(value) {
|
|
184
|
+
if (value === null || value === undefined || value === '') return null;
|
|
185
|
+
const normalized = `${value}`.trim().toLowerCase();
|
|
186
|
+
if (!SNAPSHOT_MILESTONES.includes(normalized)) {
|
|
187
|
+
throw new Error(`snapshot milestone must be one of: ${SNAPSHOT_MILESTONES.join(', ')}`);
|
|
188
|
+
}
|
|
189
|
+
return normalized;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function formatSnapshotMilestone(value) {
|
|
193
|
+
const milestone = normalizeSnapshotMilestone(value);
|
|
194
|
+
if (!milestone) return '';
|
|
195
|
+
return ` (${milestone})`;
|
|
196
|
+
}
|
|
197
|
+
|
|
139
198
|
function updateSnapshotIndex(snapshotDir, record) {
|
|
140
199
|
const indexPath = path.join(snapshotDir, 'index.json');
|
|
141
200
|
let entries = [];
|
|
@@ -173,6 +232,12 @@ function writeSnapshotArtifact(dir, snapshotKind, payload, meta = {}) {
|
|
|
173
232
|
const { snapshotDir } = ensureArtifactDirs(dir);
|
|
174
233
|
const filePath = path.join(snapshotDir, `${id}-${snapshotKind}.json`);
|
|
175
234
|
const summary = summarizeSnapshot(snapshotKind, payload);
|
|
235
|
+
const metaTags = normalizeSnapshotTags([
|
|
236
|
+
...(Array.isArray(meta.tags) ? meta.tags : (meta.tags ? [meta.tags] : [])),
|
|
237
|
+
...(meta.tag ? [meta.tag] : []),
|
|
238
|
+
]);
|
|
239
|
+
const milestone = normalizeSnapshotMilestone(meta.milestone);
|
|
240
|
+
const { tags: _ignoredTags, tag: _ignoredTag, ...restMeta } = meta;
|
|
176
241
|
const envelope = {
|
|
177
242
|
schemaVersion: 1,
|
|
178
243
|
artifactType: 'snapshot',
|
|
@@ -183,7 +248,9 @@ function writeSnapshotArtifact(dir, snapshotKind, payload, meta = {}) {
|
|
|
183
248
|
generatedBy: `nerviq@${version}`,
|
|
184
249
|
directory: dir,
|
|
185
250
|
summary,
|
|
186
|
-
|
|
251
|
+
tags: metaTags,
|
|
252
|
+
milestone,
|
|
253
|
+
...restMeta,
|
|
187
254
|
payload,
|
|
188
255
|
};
|
|
189
256
|
|
|
@@ -194,6 +261,8 @@ function writeSnapshotArtifact(dir, snapshotKind, payload, meta = {}) {
|
|
|
194
261
|
snapshotKind,
|
|
195
262
|
createdAt: envelope.createdAt,
|
|
196
263
|
relativePath: path.relative(dir, filePath),
|
|
264
|
+
tags: metaTags,
|
|
265
|
+
milestone,
|
|
197
266
|
summary,
|
|
198
267
|
};
|
|
199
268
|
updateSnapshotIndex(snapshotDir, record);
|
|
@@ -236,6 +305,79 @@ function getHistory(dir, limit = 20) {
|
|
|
236
305
|
.slice(0, limit);
|
|
237
306
|
}
|
|
238
307
|
|
|
308
|
+
function buildCheckDiffDetail(previousResult, currentResult) {
|
|
309
|
+
const source = currentResult || previousResult || {};
|
|
310
|
+
const previousState = previousResult ? previousResult.passed : undefined;
|
|
311
|
+
const currentState = currentResult ? currentResult.passed : undefined;
|
|
312
|
+
return {
|
|
313
|
+
key: source.key,
|
|
314
|
+
name: source.name || source.key,
|
|
315
|
+
impact: source.impact || null,
|
|
316
|
+
category: source.category || null,
|
|
317
|
+
previousState,
|
|
318
|
+
currentState,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function collectCheckDiff(previousResults = [], currentResults = []) {
|
|
323
|
+
const prevMap = new Map();
|
|
324
|
+
const currMap = new Map();
|
|
325
|
+
|
|
326
|
+
for (const result of previousResults) {
|
|
327
|
+
if (result && result.key) prevMap.set(result.key, result);
|
|
328
|
+
}
|
|
329
|
+
for (const result of currentResults) {
|
|
330
|
+
if (result && result.key) currMap.set(result.key, result);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const regressions = [];
|
|
334
|
+
const improvements = [];
|
|
335
|
+
const newlyApplicable = [];
|
|
336
|
+
const noLongerApplicable = [];
|
|
337
|
+
const newChecks = [];
|
|
338
|
+
const removedChecks = [];
|
|
339
|
+
|
|
340
|
+
const allKeys = [...new Set([...prevMap.keys(), ...currMap.keys()])].sort();
|
|
341
|
+
for (const key of allKeys) {
|
|
342
|
+
const previousResult = prevMap.get(key);
|
|
343
|
+
const currentResult = currMap.get(key);
|
|
344
|
+
const previousState = previousResult ? previousResult.passed : undefined;
|
|
345
|
+
const currentState = currentResult ? currentResult.passed : undefined;
|
|
346
|
+
const detail = buildCheckDiffDetail(previousResult, currentResult);
|
|
347
|
+
|
|
348
|
+
if (!previousResult) {
|
|
349
|
+
if (currentState !== undefined) {
|
|
350
|
+
newChecks.push(detail);
|
|
351
|
+
}
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (!currentResult) {
|
|
356
|
+
removedChecks.push(detail);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (previousState === true && currentState === false) {
|
|
361
|
+
regressions.push(detail);
|
|
362
|
+
} else if (previousState === false && currentState === true) {
|
|
363
|
+
improvements.push(detail);
|
|
364
|
+
} else if ((previousState === null || previousState === undefined) && (currentState === true || currentState === false)) {
|
|
365
|
+
newlyApplicable.push(detail);
|
|
366
|
+
} else if ((currentState === null || currentState === undefined) && (previousState === true || previousState === false)) {
|
|
367
|
+
noLongerApplicable.push(detail);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
regressions,
|
|
373
|
+
improvements,
|
|
374
|
+
newlyApplicable,
|
|
375
|
+
noLongerApplicable,
|
|
376
|
+
newChecks,
|
|
377
|
+
removedChecks,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
239
381
|
/**
|
|
240
382
|
* Compare the two most recent audit snapshots and return the delta.
|
|
241
383
|
* @param {string} dir - Project root directory.
|
|
@@ -247,6 +389,8 @@ function compareLatest(dir) {
|
|
|
247
389
|
|
|
248
390
|
const current = audits[0];
|
|
249
391
|
const previous = audits[1];
|
|
392
|
+
const currentPayload = loadSnapshotPayload(dir, current);
|
|
393
|
+
const previousPayload = loadSnapshotPayload(dir, previous);
|
|
250
394
|
|
|
251
395
|
const delta = {
|
|
252
396
|
score: (current.summary?.score || 0) - (previous.summary?.score || 0),
|
|
@@ -254,26 +398,66 @@ function compareLatest(dir) {
|
|
|
254
398
|
passed: (current.summary?.passed || 0) - (previous.summary?.passed || 0),
|
|
255
399
|
};
|
|
256
400
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
401
|
+
let regressionDetails = [];
|
|
402
|
+
let improvementDetails = [];
|
|
403
|
+
let newlyApplicableDetails = [];
|
|
404
|
+
let noLongerApplicableDetails = [];
|
|
405
|
+
let newChecks = [];
|
|
406
|
+
let removedChecks = [];
|
|
407
|
+
let regressions = [];
|
|
408
|
+
let improvements = [];
|
|
409
|
+
let detailedDiffAvailable = false;
|
|
410
|
+
|
|
411
|
+
if (currentPayload && previousPayload && Array.isArray(currentPayload.results) && Array.isArray(previousPayload.results)) {
|
|
412
|
+
const diff = collectCheckDiff(previousPayload.results, currentPayload.results);
|
|
413
|
+
regressionDetails = diff.regressions;
|
|
414
|
+
improvementDetails = diff.improvements;
|
|
415
|
+
newlyApplicableDetails = diff.newlyApplicable;
|
|
416
|
+
noLongerApplicableDetails = diff.noLongerApplicable;
|
|
417
|
+
newChecks = diff.newChecks;
|
|
418
|
+
removedChecks = diff.removedChecks;
|
|
419
|
+
regressions = regressionDetails.map((item) => item.key);
|
|
420
|
+
improvements = improvementDetails.map((item) => item.key);
|
|
421
|
+
detailedDiffAvailable = true;
|
|
422
|
+
} else {
|
|
423
|
+
const prevKeys = new Set(previous.summary?.topActionKeys || []);
|
|
424
|
+
const currKeys = new Set(current.summary?.topActionKeys || []);
|
|
425
|
+
for (const key of currKeys) {
|
|
426
|
+
if (!prevKeys.has(key)) regressions.push(key);
|
|
427
|
+
}
|
|
428
|
+
for (const key of prevKeys) {
|
|
429
|
+
if (!currKeys.has(key)) improvements.push(key);
|
|
430
|
+
}
|
|
268
431
|
}
|
|
269
432
|
|
|
270
433
|
return {
|
|
271
434
|
scoreType: 'audit-snapshot-score',
|
|
272
|
-
current: {
|
|
273
|
-
|
|
435
|
+
current: {
|
|
436
|
+
date: current.createdAt,
|
|
437
|
+
score: current.summary?.score,
|
|
438
|
+
passed: current.summary?.passed,
|
|
439
|
+
tags: current.tags || [],
|
|
440
|
+
milestone: current.milestone || null,
|
|
441
|
+
scoreType: 'audit-snapshot-score',
|
|
442
|
+
},
|
|
443
|
+
previous: {
|
|
444
|
+
date: previous.createdAt,
|
|
445
|
+
score: previous.summary?.score,
|
|
446
|
+
passed: previous.summary?.passed,
|
|
447
|
+
tags: previous.tags || [],
|
|
448
|
+
milestone: previous.milestone || null,
|
|
449
|
+
scoreType: 'audit-snapshot-score',
|
|
450
|
+
},
|
|
274
451
|
delta,
|
|
275
452
|
regressions,
|
|
276
453
|
improvements,
|
|
454
|
+
regressionDetails,
|
|
455
|
+
improvementDetails,
|
|
456
|
+
newlyApplicableDetails,
|
|
457
|
+
noLongerApplicableDetails,
|
|
458
|
+
newChecks,
|
|
459
|
+
removedChecks,
|
|
460
|
+
detailedDiffAvailable,
|
|
277
461
|
trend: delta.score > 0 ? 'improving' : delta.score < 0 ? 'regressing' : 'stable',
|
|
278
462
|
};
|
|
279
463
|
}
|
|
@@ -303,13 +487,13 @@ function formatSnapshotBootstrap(dir, goal = 'history') {
|
|
|
303
487
|
|
|
304
488
|
if (snapshotCount === 0) {
|
|
305
489
|
lines.push(' Bootstrap it with:');
|
|
306
|
-
lines.push(' 1. Run `nerviq audit --snapshot` to save the baseline.');
|
|
490
|
+
lines.push(' 1. Run `nerviq audit --snapshot --milestone baseline --tag "baseline"` to save the baseline.');
|
|
307
491
|
lines.push(' 2. Make a meaningful repo change (`nerviq setup --auto` or `nerviq fix --all-critical --auto`).');
|
|
308
|
-
lines.push(' 3. Run `nerviq audit --snapshot`
|
|
492
|
+
lines.push(' 3. Run `nerviq audit --snapshot --milestone post-fix --tag "after-change"` to capture the next state.');
|
|
309
493
|
} else {
|
|
310
494
|
lines.push(' Next:');
|
|
311
495
|
lines.push(' 1. Make a meaningful repo change (`nerviq setup --auto` or `nerviq fix --all-critical --auto`).');
|
|
312
|
-
lines.push(' 2. Run `nerviq audit --snapshot` again.');
|
|
496
|
+
lines.push(' 2. Run `nerviq audit --snapshot --milestone post-fix --tag "after-change"` again.');
|
|
313
497
|
}
|
|
314
498
|
|
|
315
499
|
if (goal === 'compare') {
|
|
@@ -340,7 +524,7 @@ function formatHistory(dir) {
|
|
|
340
524
|
const score = entry.summary?.score ?? '?';
|
|
341
525
|
const passed = entry.summary?.passed ?? '?';
|
|
342
526
|
const total = entry.summary?.checkCount ?? '?';
|
|
343
|
-
lines.push(` ${dateDisplay} snapshot ${score}/100 (${passed}/${total} checks passing)`);
|
|
527
|
+
lines.push(` ${dateDisplay} snapshot${formatSnapshotMilestone(entry.milestone)}${formatSnapshotTags(entry.tags)} ${score}/100 (${passed}/${total} checks passing)`);
|
|
344
528
|
}
|
|
345
529
|
|
|
346
530
|
const comparison = compareLatest(dir);
|
|
@@ -348,6 +532,12 @@ function formatHistory(dir) {
|
|
|
348
532
|
lines.push('');
|
|
349
533
|
const sign = comparison.delta.score >= 0 ? '+' : '';
|
|
350
534
|
lines.push(` Latest snapshot trend: ${comparison.trend} (${sign}${comparison.delta.score} since previous snapshot)`);
|
|
535
|
+
if ((comparison.previous.tags || []).length > 0 || (comparison.current.tags || []).length > 0) {
|
|
536
|
+
lines.push(` Snapshot tags: previous${formatSnapshotTags(comparison.previous.tags)} -> current${formatSnapshotTags(comparison.current.tags)}`);
|
|
537
|
+
}
|
|
538
|
+
if (comparison.previous.milestone || comparison.current.milestone) {
|
|
539
|
+
lines.push(` Lifecycle: previous${formatSnapshotMilestone(comparison.previous.milestone)} -> current${formatSnapshotMilestone(comparison.current.milestone)}`);
|
|
540
|
+
}
|
|
351
541
|
if (comparison.improvements.length > 0) {
|
|
352
542
|
lines.push(` Fixed: ${comparison.improvements.join(', ')}`);
|
|
353
543
|
}
|
|
@@ -378,21 +568,23 @@ function exportTrendReport(dir) {
|
|
|
378
568
|
'',
|
|
379
569
|
'## Audit Snapshot History',
|
|
380
570
|
'',
|
|
381
|
-
'| Date | Score | Passed | Checks |',
|
|
382
|
-
'
|
|
571
|
+
'| Date | Milestone | Tags | Score | Passed | Checks |',
|
|
572
|
+
'|------|-----------|------|-------|--------|--------|',
|
|
383
573
|
];
|
|
384
574
|
|
|
385
575
|
for (const entry of history) {
|
|
386
576
|
const date = entry.createdAt?.split('T')[0] || '?';
|
|
387
|
-
|
|
577
|
+
const milestone = entry.milestone || '-';
|
|
578
|
+
const tags = (entry.tags || []).length > 0 ? entry.tags.join(', ') : '-';
|
|
579
|
+
lines.push(`| ${date} | ${milestone} | ${tags} | ${entry.summary?.score ?? '?'}/100 | ${entry.summary?.passed ?? '?'} | ${entry.summary?.checkCount ?? '?'} |`);
|
|
388
580
|
}
|
|
389
581
|
|
|
390
582
|
if (comparison) {
|
|
391
583
|
lines.push('');
|
|
392
584
|
lines.push('## Latest Comparison');
|
|
393
585
|
lines.push('');
|
|
394
|
-
lines.push(`- **Previous snapshot score:** ${comparison.previous.score}/100 (${comparison.previous.date?.split('T')[0]})`);
|
|
395
|
-
lines.push(`- **Current snapshot score:** ${comparison.current.score}/100 (${comparison.current.date?.split('T')[0]})`);
|
|
586
|
+
lines.push(`- **Previous snapshot score:** ${comparison.previous.score}/100 (${comparison.previous.date?.split('T')[0]})${formatSnapshotMilestone(comparison.previous.milestone)}${formatSnapshotTags(comparison.previous.tags)}`);
|
|
587
|
+
lines.push(`- **Current snapshot score:** ${comparison.current.score}/100 (${comparison.current.date?.split('T')[0]})${formatSnapshotMilestone(comparison.current.milestone)}${formatSnapshotTags(comparison.current.tags)}`);
|
|
396
588
|
lines.push(`- **Snapshot delta:** ${comparison.delta.score >= 0 ? '+' : ''}${comparison.delta.score} points`);
|
|
397
589
|
lines.push(`- **Trend:** ${comparison.trend}`);
|
|
398
590
|
if (comparison.improvements.length > 0) lines.push(`- **Fixed:** ${comparison.improvements.join(', ')}`);
|
|
@@ -627,42 +819,11 @@ function checkHealth(dir) {
|
|
|
627
819
|
|
|
628
820
|
const currentResults = currentPayload.results || [];
|
|
629
821
|
const previousResults = previousPayload.results || [];
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}
|
|
636
|
-
const currMap = {};
|
|
637
|
-
for (const r of currentResults) {
|
|
638
|
-
if (r.key) currMap[r.key] = r.passed;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
const regressions = []; // was passing → now failing
|
|
642
|
-
const improvements = []; // was failing → now passing
|
|
643
|
-
const newChecks = []; // not in previous
|
|
644
|
-
const removedChecks = []; // not in current
|
|
645
|
-
|
|
646
|
-
for (const r of currentResults) {
|
|
647
|
-
if (!r.key) continue;
|
|
648
|
-
const prev = prevMap[r.key];
|
|
649
|
-
const curr = r.passed;
|
|
650
|
-
if (prev === undefined) {
|
|
651
|
-
if (curr !== null) newChecks.push({ key: r.key, name: r.name, impact: r.impact, passed: curr });
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
if (prev === true && curr === false) {
|
|
655
|
-
regressions.push({ key: r.key, name: r.name, impact: r.impact, category: r.category });
|
|
656
|
-
} else if (prev === false && curr === true) {
|
|
657
|
-
improvements.push({ key: r.key, name: r.name, impact: r.impact, category: r.category });
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
for (const r of previousResults) {
|
|
662
|
-
if (r.key && currMap[r.key] === undefined) {
|
|
663
|
-
removedChecks.push({ key: r.key, name: r.name });
|
|
664
|
-
}
|
|
665
|
-
}
|
|
822
|
+
const diff = collectCheckDiff(previousResults, currentResults);
|
|
823
|
+
const regressions = diff.regressions;
|
|
824
|
+
const improvements = diff.improvements;
|
|
825
|
+
const newChecks = diff.newChecks;
|
|
826
|
+
const removedChecks = diff.removedChecks;
|
|
666
827
|
|
|
667
828
|
// Detect potential platform format changes:
|
|
668
829
|
// If 3+ checks in the same category regressed, flag it
|
|
@@ -855,6 +1016,11 @@ module.exports = {
|
|
|
855
1016
|
writeActivityArtifact,
|
|
856
1017
|
writeRollbackArtifact,
|
|
857
1018
|
writeSnapshotArtifact,
|
|
1019
|
+
normalizeSnapshotTags,
|
|
1020
|
+
formatSnapshotTags,
|
|
1021
|
+
normalizeSnapshotMilestone,
|
|
1022
|
+
formatSnapshotMilestone,
|
|
1023
|
+
SNAPSHOT_MILESTONES,
|
|
858
1024
|
readSnapshotIndex,
|
|
859
1025
|
getHistory,
|
|
860
1026
|
compareLatest,
|