@nerviq/cli 1.9.0 → 1.11.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 +106 -60
- package/bin/cli.js +382 -208
- package/package.json +1 -1
- package/src/activity.js +245 -63
- package/src/aider/freshness.js +149 -146
- package/src/analyze.js +3 -1
- package/src/anti-patterns.js +17 -13
- package/src/audit.js +106 -79
- package/src/auto-suggest.js +62 -9
- package/src/benchmark.js +67 -51
- package/src/dashboard.js +36 -14
- package/src/governance.js +13 -7
- package/src/index.js +2 -1
- package/src/instruction-surfaces.js +185 -0
- package/src/integrations.js +102 -55
- package/src/locales/en.json +1 -1
- package/src/locales/es.json +1 -1
- package/src/permission-rules.js +218 -0
- package/src/secret-patterns.js +9 -0
- package/src/server.js +398 -3
- package/src/setup.js +2 -2
- package/src/stack-checks.js +1 -1
- package/src/synergy/report.js +1 -0
- package/src/techniques.js +102 -103
- package/src/terminology.js +73 -0
- package/src/token-estimate.js +35 -0
- package/src/workspace.js +155 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nerviq/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "The intelligent nervous system for AI coding agents — 2,438 checks (8 platforms × ~300 governance rules), 10 languages, 62 domain packs. Audit, align, and amplify.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/activity.js
CHANGED
|
@@ -136,6 +136,37 @@ function summarizeSnapshot(snapshotKind, payload) {
|
|
|
136
136
|
return {};
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
function normalizeSnapshotTags(input) {
|
|
140
|
+
const values = Array.isArray(input) ? input : (input ? [input] : []);
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
const tags = [];
|
|
143
|
+
|
|
144
|
+
for (const value of values) {
|
|
145
|
+
const parts = `${value || ''}`
|
|
146
|
+
.split(',')
|
|
147
|
+
.map((item) => item.replace(/\s+/g, ' ').trim())
|
|
148
|
+
.filter(Boolean);
|
|
149
|
+
|
|
150
|
+
for (const part of parts) {
|
|
151
|
+
const key = part.toLowerCase();
|
|
152
|
+
if (seen.has(key)) continue;
|
|
153
|
+
seen.add(key);
|
|
154
|
+
tags.push(part.slice(0, 48));
|
|
155
|
+
if (tags.length >= 8) {
|
|
156
|
+
return tags;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return tags;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function formatSnapshotTags(tags = []) {
|
|
165
|
+
const normalized = normalizeSnapshotTags(tags);
|
|
166
|
+
if (normalized.length === 0) return '';
|
|
167
|
+
return ` [${normalized.join(', ')}]`;
|
|
168
|
+
}
|
|
169
|
+
|
|
139
170
|
function updateSnapshotIndex(snapshotDir, record) {
|
|
140
171
|
const indexPath = path.join(snapshotDir, 'index.json');
|
|
141
172
|
let entries = [];
|
|
@@ -173,6 +204,11 @@ function writeSnapshotArtifact(dir, snapshotKind, payload, meta = {}) {
|
|
|
173
204
|
const { snapshotDir } = ensureArtifactDirs(dir);
|
|
174
205
|
const filePath = path.join(snapshotDir, `${id}-${snapshotKind}.json`);
|
|
175
206
|
const summary = summarizeSnapshot(snapshotKind, payload);
|
|
207
|
+
const metaTags = normalizeSnapshotTags([
|
|
208
|
+
...(Array.isArray(meta.tags) ? meta.tags : (meta.tags ? [meta.tags] : [])),
|
|
209
|
+
...(meta.tag ? [meta.tag] : []),
|
|
210
|
+
]);
|
|
211
|
+
const { tags: _ignoredTags, tag: _ignoredTag, ...restMeta } = meta;
|
|
176
212
|
const envelope = {
|
|
177
213
|
schemaVersion: 1,
|
|
178
214
|
artifactType: 'snapshot',
|
|
@@ -183,7 +219,8 @@ function writeSnapshotArtifact(dir, snapshotKind, payload, meta = {}) {
|
|
|
183
219
|
generatedBy: `nerviq@${version}`,
|
|
184
220
|
directory: dir,
|
|
185
221
|
summary,
|
|
186
|
-
|
|
222
|
+
tags: metaTags,
|
|
223
|
+
...restMeta,
|
|
187
224
|
payload,
|
|
188
225
|
};
|
|
189
226
|
|
|
@@ -194,6 +231,7 @@ function writeSnapshotArtifact(dir, snapshotKind, payload, meta = {}) {
|
|
|
194
231
|
snapshotKind,
|
|
195
232
|
createdAt: envelope.createdAt,
|
|
196
233
|
relativePath: path.relative(dir, filePath),
|
|
234
|
+
tags: metaTags,
|
|
197
235
|
summary,
|
|
198
236
|
};
|
|
199
237
|
updateSnapshotIndex(snapshotDir, record);
|
|
@@ -236,6 +274,79 @@ function getHistory(dir, limit = 20) {
|
|
|
236
274
|
.slice(0, limit);
|
|
237
275
|
}
|
|
238
276
|
|
|
277
|
+
function buildCheckDiffDetail(previousResult, currentResult) {
|
|
278
|
+
const source = currentResult || previousResult || {};
|
|
279
|
+
const previousState = previousResult ? previousResult.passed : undefined;
|
|
280
|
+
const currentState = currentResult ? currentResult.passed : undefined;
|
|
281
|
+
return {
|
|
282
|
+
key: source.key,
|
|
283
|
+
name: source.name || source.key,
|
|
284
|
+
impact: source.impact || null,
|
|
285
|
+
category: source.category || null,
|
|
286
|
+
previousState,
|
|
287
|
+
currentState,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function collectCheckDiff(previousResults = [], currentResults = []) {
|
|
292
|
+
const prevMap = new Map();
|
|
293
|
+
const currMap = new Map();
|
|
294
|
+
|
|
295
|
+
for (const result of previousResults) {
|
|
296
|
+
if (result && result.key) prevMap.set(result.key, result);
|
|
297
|
+
}
|
|
298
|
+
for (const result of currentResults) {
|
|
299
|
+
if (result && result.key) currMap.set(result.key, result);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const regressions = [];
|
|
303
|
+
const improvements = [];
|
|
304
|
+
const newlyApplicable = [];
|
|
305
|
+
const noLongerApplicable = [];
|
|
306
|
+
const newChecks = [];
|
|
307
|
+
const removedChecks = [];
|
|
308
|
+
|
|
309
|
+
const allKeys = [...new Set([...prevMap.keys(), ...currMap.keys()])].sort();
|
|
310
|
+
for (const key of allKeys) {
|
|
311
|
+
const previousResult = prevMap.get(key);
|
|
312
|
+
const currentResult = currMap.get(key);
|
|
313
|
+
const previousState = previousResult ? previousResult.passed : undefined;
|
|
314
|
+
const currentState = currentResult ? currentResult.passed : undefined;
|
|
315
|
+
const detail = buildCheckDiffDetail(previousResult, currentResult);
|
|
316
|
+
|
|
317
|
+
if (!previousResult) {
|
|
318
|
+
if (currentState !== undefined) {
|
|
319
|
+
newChecks.push(detail);
|
|
320
|
+
}
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!currentResult) {
|
|
325
|
+
removedChecks.push(detail);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (previousState === true && currentState === false) {
|
|
330
|
+
regressions.push(detail);
|
|
331
|
+
} else if (previousState === false && currentState === true) {
|
|
332
|
+
improvements.push(detail);
|
|
333
|
+
} else if ((previousState === null || previousState === undefined) && (currentState === true || currentState === false)) {
|
|
334
|
+
newlyApplicable.push(detail);
|
|
335
|
+
} else if ((currentState === null || currentState === undefined) && (previousState === true || previousState === false)) {
|
|
336
|
+
noLongerApplicable.push(detail);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
regressions,
|
|
342
|
+
improvements,
|
|
343
|
+
newlyApplicable,
|
|
344
|
+
noLongerApplicable,
|
|
345
|
+
newChecks,
|
|
346
|
+
removedChecks,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
239
350
|
/**
|
|
240
351
|
* Compare the two most recent audit snapshots and return the delta.
|
|
241
352
|
* @param {string} dir - Project root directory.
|
|
@@ -247,6 +358,8 @@ function compareLatest(dir) {
|
|
|
247
358
|
|
|
248
359
|
const current = audits[0];
|
|
249
360
|
const previous = audits[1];
|
|
361
|
+
const currentPayload = loadSnapshotPayload(dir, current);
|
|
362
|
+
const previousPayload = loadSnapshotPayload(dir, previous);
|
|
250
363
|
|
|
251
364
|
const delta = {
|
|
252
365
|
score: (current.summary?.score || 0) - (previous.summary?.score || 0),
|
|
@@ -254,34 +367,122 @@ function compareLatest(dir) {
|
|
|
254
367
|
passed: (current.summary?.passed || 0) - (previous.summary?.passed || 0),
|
|
255
368
|
};
|
|
256
369
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
370
|
+
let regressionDetails = [];
|
|
371
|
+
let improvementDetails = [];
|
|
372
|
+
let newlyApplicableDetails = [];
|
|
373
|
+
let noLongerApplicableDetails = [];
|
|
374
|
+
let newChecks = [];
|
|
375
|
+
let removedChecks = [];
|
|
376
|
+
let regressions = [];
|
|
377
|
+
let improvements = [];
|
|
378
|
+
let detailedDiffAvailable = false;
|
|
379
|
+
|
|
380
|
+
if (currentPayload && previousPayload && Array.isArray(currentPayload.results) && Array.isArray(previousPayload.results)) {
|
|
381
|
+
const diff = collectCheckDiff(previousPayload.results, currentPayload.results);
|
|
382
|
+
regressionDetails = diff.regressions;
|
|
383
|
+
improvementDetails = diff.improvements;
|
|
384
|
+
newlyApplicableDetails = diff.newlyApplicable;
|
|
385
|
+
noLongerApplicableDetails = diff.noLongerApplicable;
|
|
386
|
+
newChecks = diff.newChecks;
|
|
387
|
+
removedChecks = diff.removedChecks;
|
|
388
|
+
regressions = regressionDetails.map((item) => item.key);
|
|
389
|
+
improvements = improvementDetails.map((item) => item.key);
|
|
390
|
+
detailedDiffAvailable = true;
|
|
391
|
+
} else {
|
|
392
|
+
const prevKeys = new Set(previous.summary?.topActionKeys || []);
|
|
393
|
+
const currKeys = new Set(current.summary?.topActionKeys || []);
|
|
394
|
+
for (const key of currKeys) {
|
|
395
|
+
if (!prevKeys.has(key)) regressions.push(key);
|
|
396
|
+
}
|
|
397
|
+
for (const key of prevKeys) {
|
|
398
|
+
if (!currKeys.has(key)) improvements.push(key);
|
|
399
|
+
}
|
|
268
400
|
}
|
|
269
401
|
|
|
270
402
|
return {
|
|
271
|
-
|
|
272
|
-
|
|
403
|
+
scoreType: 'audit-snapshot-score',
|
|
404
|
+
current: {
|
|
405
|
+
date: current.createdAt,
|
|
406
|
+
score: current.summary?.score,
|
|
407
|
+
passed: current.summary?.passed,
|
|
408
|
+
tags: current.tags || [],
|
|
409
|
+
scoreType: 'audit-snapshot-score',
|
|
410
|
+
},
|
|
411
|
+
previous: {
|
|
412
|
+
date: previous.createdAt,
|
|
413
|
+
score: previous.summary?.score,
|
|
414
|
+
passed: previous.summary?.passed,
|
|
415
|
+
tags: previous.tags || [],
|
|
416
|
+
scoreType: 'audit-snapshot-score',
|
|
417
|
+
},
|
|
273
418
|
delta,
|
|
274
419
|
regressions,
|
|
275
420
|
improvements,
|
|
421
|
+
regressionDetails,
|
|
422
|
+
improvementDetails,
|
|
423
|
+
newlyApplicableDetails,
|
|
424
|
+
noLongerApplicableDetails,
|
|
425
|
+
newChecks,
|
|
426
|
+
removedChecks,
|
|
427
|
+
detailedDiffAvailable,
|
|
276
428
|
trend: delta.score > 0 ? 'improving' : delta.score < 0 ? 'regressing' : 'stable',
|
|
277
429
|
};
|
|
278
430
|
}
|
|
279
431
|
|
|
432
|
+
function formatSnapshotBootstrap(dir, goal = 'history') {
|
|
433
|
+
const snapshotCount = getHistory(dir, 50).length;
|
|
434
|
+
const lines = [];
|
|
435
|
+
const snapshotLabel = snapshotCount === 1
|
|
436
|
+
? '1 saved audit snapshot'
|
|
437
|
+
: `${snapshotCount} saved audit snapshots`;
|
|
438
|
+
|
|
439
|
+
if (goal === 'compare') {
|
|
440
|
+
lines.push(snapshotCount === 0
|
|
441
|
+
? 'Compare needs 2 audit snapshots.'
|
|
442
|
+
: 'Compare needs one more audit snapshot.');
|
|
443
|
+
} else if (goal === 'trend') {
|
|
444
|
+
lines.push(snapshotCount === 0
|
|
445
|
+
? 'Trend needs 2 audit snapshots to start.'
|
|
446
|
+
: 'Trend needs one more audit snapshot to become meaningful.');
|
|
447
|
+
} else {
|
|
448
|
+
lines.push(snapshotCount === 0
|
|
449
|
+
? 'No audit snapshots found yet.'
|
|
450
|
+
: 'History is initialized, but compare/trend still need one more snapshot.');
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
lines.push(` Current state: ${snapshotLabel}.`);
|
|
454
|
+
|
|
455
|
+
if (snapshotCount === 0) {
|
|
456
|
+
lines.push(' Bootstrap it with:');
|
|
457
|
+
lines.push(' 1. Run `nerviq audit --snapshot --tag "baseline"` to save the baseline.');
|
|
458
|
+
lines.push(' 2. Make a meaningful repo change (`nerviq setup --auto` or `nerviq fix --all-critical --auto`).');
|
|
459
|
+
lines.push(' 3. Run `nerviq audit --snapshot --tag "after-change"` to capture the next state.');
|
|
460
|
+
} else {
|
|
461
|
+
lines.push(' Next:');
|
|
462
|
+
lines.push(' 1. Make a meaningful repo change (`nerviq setup --auto` or `nerviq fix --all-critical --auto`).');
|
|
463
|
+
lines.push(' 2. Run `nerviq audit --snapshot --tag "after-change"` again.');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (goal === 'compare') {
|
|
467
|
+
lines.push(' Then rerun `nerviq compare`.');
|
|
468
|
+
} else if (goal === 'trend') {
|
|
469
|
+
lines.push(' Then rerun `nerviq trend`.');
|
|
470
|
+
} else {
|
|
471
|
+
lines.push(' Then rerun `nerviq history`, `nerviq compare`, or `nerviq trend`.');
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return lines.join('\n');
|
|
475
|
+
}
|
|
476
|
+
|
|
280
477
|
function formatHistory(dir) {
|
|
281
478
|
const history = getHistory(dir, 10);
|
|
282
|
-
if (history.length === 0) return
|
|
479
|
+
if (history.length === 0) return formatSnapshotBootstrap(dir, 'history');
|
|
283
480
|
|
|
284
|
-
const lines = [
|
|
481
|
+
const lines = [
|
|
482
|
+
'Audit snapshot history (most recent first):',
|
|
483
|
+
' Score type: saved audit snapshot scores only (not live audits or benchmark projections).',
|
|
484
|
+
'',
|
|
485
|
+
];
|
|
285
486
|
for (const entry of history) {
|
|
286
487
|
const dateStr = entry.createdAt || 'unknown';
|
|
287
488
|
const date = dateStr.split('T')[0] || 'unknown';
|
|
@@ -290,14 +491,17 @@ function formatHistory(dir) {
|
|
|
290
491
|
const score = entry.summary?.score ?? '?';
|
|
291
492
|
const passed = entry.summary?.passed ?? '?';
|
|
292
493
|
const total = entry.summary?.checkCount ?? '?';
|
|
293
|
-
lines.push(` ${dateDisplay} ${score}/100 (${passed}/${total} passing)`);
|
|
494
|
+
lines.push(` ${dateDisplay} snapshot${formatSnapshotTags(entry.tags)} ${score}/100 (${passed}/${total} checks passing)`);
|
|
294
495
|
}
|
|
295
496
|
|
|
296
497
|
const comparison = compareLatest(dir);
|
|
297
498
|
if (comparison) {
|
|
298
499
|
lines.push('');
|
|
299
500
|
const sign = comparison.delta.score >= 0 ? '+' : '';
|
|
300
|
-
lines.push(`
|
|
501
|
+
lines.push(` Latest snapshot trend: ${comparison.trend} (${sign}${comparison.delta.score} since previous snapshot)`);
|
|
502
|
+
if ((comparison.previous.tags || []).length > 0 || (comparison.current.tags || []).length > 0) {
|
|
503
|
+
lines.push(` Snapshot tags: previous${formatSnapshotTags(comparison.previous.tags)} -> current${formatSnapshotTags(comparison.current.tags)}`);
|
|
504
|
+
}
|
|
301
505
|
if (comparison.improvements.length > 0) {
|
|
302
506
|
lines.push(` Fixed: ${comparison.improvements.join(', ')}`);
|
|
303
507
|
}
|
|
@@ -306,6 +510,11 @@ function formatHistory(dir) {
|
|
|
306
510
|
}
|
|
307
511
|
}
|
|
308
512
|
|
|
513
|
+
if (history.length === 1) {
|
|
514
|
+
lines.push('');
|
|
515
|
+
lines.push(formatSnapshotBootstrap(dir, 'history'));
|
|
516
|
+
}
|
|
517
|
+
|
|
309
518
|
return lines.join('\n');
|
|
310
519
|
}
|
|
311
520
|
|
|
@@ -315,30 +524,31 @@ function exportTrendReport(dir) {
|
|
|
315
524
|
|
|
316
525
|
const comparison = compareLatest(dir);
|
|
317
526
|
const lines = [
|
|
318
|
-
'#
|
|
527
|
+
'# Nerviq Audit Snapshot Trend Report',
|
|
319
528
|
'',
|
|
320
529
|
`**Project:** ${path.basename(dir)}`,
|
|
321
530
|
`**Generated:** ${new Date().toISOString().split('T')[0]}`,
|
|
322
|
-
`**
|
|
531
|
+
`**Audit snapshots:** ${history.length}`,
|
|
323
532
|
'',
|
|
324
|
-
'##
|
|
533
|
+
'## Audit Snapshot History',
|
|
325
534
|
'',
|
|
326
|
-
'| Date | Score | Passed | Checks |',
|
|
327
|
-
'
|
|
535
|
+
'| Date | Tags | Score | Passed | Checks |',
|
|
536
|
+
'|------|------|-------|--------|--------|',
|
|
328
537
|
];
|
|
329
538
|
|
|
330
539
|
for (const entry of history) {
|
|
331
540
|
const date = entry.createdAt?.split('T')[0] || '?';
|
|
332
|
-
|
|
541
|
+
const tags = (entry.tags || []).length > 0 ? entry.tags.join(', ') : '-';
|
|
542
|
+
lines.push(`| ${date} | ${tags} | ${entry.summary?.score ?? '?'}/100 | ${entry.summary?.passed ?? '?'} | ${entry.summary?.checkCount ?? '?'} |`);
|
|
333
543
|
}
|
|
334
544
|
|
|
335
545
|
if (comparison) {
|
|
336
546
|
lines.push('');
|
|
337
547
|
lines.push('## Latest Comparison');
|
|
338
548
|
lines.push('');
|
|
339
|
-
lines.push(`- **Previous:** ${comparison.previous.score}/100 (${comparison.previous.date?.split('T')[0]})`);
|
|
340
|
-
lines.push(`- **Current:** ${comparison.current.score}/100 (${comparison.current.date?.split('T')[0]})`);
|
|
341
|
-
lines.push(`- **
|
|
549
|
+
lines.push(`- **Previous snapshot score:** ${comparison.previous.score}/100 (${comparison.previous.date?.split('T')[0]})${formatSnapshotTags(comparison.previous.tags)}`);
|
|
550
|
+
lines.push(`- **Current snapshot score:** ${comparison.current.score}/100 (${comparison.current.date?.split('T')[0]})${formatSnapshotTags(comparison.current.tags)}`);
|
|
551
|
+
lines.push(`- **Snapshot delta:** ${comparison.delta.score >= 0 ? '+' : ''}${comparison.delta.score} points`);
|
|
342
552
|
lines.push(`- **Trend:** ${comparison.trend}`);
|
|
343
553
|
if (comparison.improvements.length > 0) lines.push(`- **Fixed:** ${comparison.improvements.join(', ')}`);
|
|
344
554
|
if (comparison.regressions.length > 0) lines.push(`- **New gaps:** ${comparison.regressions.join(', ')}`);
|
|
@@ -572,42 +782,11 @@ function checkHealth(dir) {
|
|
|
572
782
|
|
|
573
783
|
const currentResults = currentPayload.results || [];
|
|
574
784
|
const previousResults = previousPayload.results || [];
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
const currMap = {};
|
|
582
|
-
for (const r of currentResults) {
|
|
583
|
-
if (r.key) currMap[r.key] = r.passed;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
const regressions = []; // was passing → now failing
|
|
587
|
-
const improvements = []; // was failing → now passing
|
|
588
|
-
const newChecks = []; // not in previous
|
|
589
|
-
const removedChecks = []; // not in current
|
|
590
|
-
|
|
591
|
-
for (const r of currentResults) {
|
|
592
|
-
if (!r.key) continue;
|
|
593
|
-
const prev = prevMap[r.key];
|
|
594
|
-
const curr = r.passed;
|
|
595
|
-
if (prev === undefined) {
|
|
596
|
-
if (curr !== null) newChecks.push({ key: r.key, name: r.name, impact: r.impact, passed: curr });
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
if (prev === true && curr === false) {
|
|
600
|
-
regressions.push({ key: r.key, name: r.name, impact: r.impact, category: r.category });
|
|
601
|
-
} else if (prev === false && curr === true) {
|
|
602
|
-
improvements.push({ key: r.key, name: r.name, impact: r.impact, category: r.category });
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
for (const r of previousResults) {
|
|
607
|
-
if (r.key && currMap[r.key] === undefined) {
|
|
608
|
-
removedChecks.push({ key: r.key, name: r.name });
|
|
609
|
-
}
|
|
610
|
-
}
|
|
785
|
+
const diff = collectCheckDiff(previousResults, currentResults);
|
|
786
|
+
const regressions = diff.regressions;
|
|
787
|
+
const improvements = diff.improvements;
|
|
788
|
+
const newChecks = diff.newChecks;
|
|
789
|
+
const removedChecks = diff.removedChecks;
|
|
611
790
|
|
|
612
791
|
// Detect potential platform format changes:
|
|
613
792
|
// If 3+ checks in the same category regressed, flag it
|
|
@@ -800,9 +979,12 @@ module.exports = {
|
|
|
800
979
|
writeActivityArtifact,
|
|
801
980
|
writeRollbackArtifact,
|
|
802
981
|
writeSnapshotArtifact,
|
|
982
|
+
normalizeSnapshotTags,
|
|
983
|
+
formatSnapshotTags,
|
|
803
984
|
readSnapshotIndex,
|
|
804
985
|
getHistory,
|
|
805
986
|
compareLatest,
|
|
987
|
+
formatSnapshotBootstrap,
|
|
806
988
|
formatHistory,
|
|
807
989
|
exportTrendReport,
|
|
808
990
|
readOutcomeIndex,
|