canary-test-cli 6.8.0 → 7.0.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/dist/engine/analysis/cli.js +155 -45
- package/dist/engine/analysis/engine.js +9 -9
- package/dist/engine/analysis/reports.js +0 -0
- package/dist/engine/cli-commands.js +2 -2
- package/dist/engine/core/migrator.js +142 -31
- package/dist/engine/core/static-linter.js +2 -2
- package/dist/engine/core/workspace-detect.js +0 -0
- package/dist/engine/guardian/adjudication.js +1 -1
- package/dist/engine/guardian/agent-tier.js +3 -3
- package/dist/engine/guardian/coverage.js +15 -1397
- package/dist/engine/guardian/diff-coverage/formats/cobertura.js +130 -0
- package/dist/engine/guardian/diff-coverage/formats/coverage-json-lint.js +197 -0
- package/dist/engine/guardian/diff-coverage/formats/coverage-json.js +107 -0
- package/dist/engine/guardian/diff-coverage/formats/xml.js +151 -0
- package/dist/engine/guardian/diff-coverage/graph-tier.js +223 -0
- package/dist/engine/guardian/diff-coverage/heuristic-tier.js +150 -0
- package/dist/engine/guardian/diff-coverage/orchestrator.js +125 -0
- package/dist/engine/guardian/diff-coverage/paths.js +164 -0
- package/dist/engine/guardian/diff-coverage/report-tier.js +153 -0
- package/dist/engine/guardian/diff-coverage/type-only.js +150 -0
- package/dist/engine/guardian/diff-coverage/types.js +115 -0
- package/dist/engine/guardian/pr-check.js +22 -7
- package/dist/engine-checks.d.ts +15 -0
- package/dist/engine-checks.js +92 -1
- package/dist/overlay-commands.d.ts +12 -1
- package/dist/overlay-commands.js +28 -2
- package/dist/router.js +17 -5
- package/dist/uninstall-render.d.ts +11 -0
- package/dist/uninstall-render.js +60 -0
- package/dist/uninstall-scan.d.ts +14 -0
- package/dist/uninstall-scan.js +273 -0
- package/dist/uninstall-types.d.ts +46 -0
- package/dist/uninstall-types.js +91 -0
- package/dist/uninstall.d.ts +13 -0
- package/dist/uninstall.js +174 -0
- package/package.json +1 -1
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
27
27
|
import { join } from 'node:path';
|
|
28
|
-
import { Command, Option } from 'commander';
|
|
28
|
+
import { Command, InvalidArgumentError, Option } from 'commander';
|
|
29
29
|
import { jsonIndent2, normalizeUsageExit } from '../cli-common.js';
|
|
30
30
|
import { gateOutcome } from '../core/gate-result.js';
|
|
31
31
|
import { AnalysisEngine } from './engine.js';
|
|
@@ -89,6 +89,101 @@ function abstainOnEmptyHistory(store, deps, json, what) {
|
|
|
89
89
|
}
|
|
90
90
|
return true;
|
|
91
91
|
}
|
|
92
|
+
// --- unit-bearing flags (#673) -----------------------------------------------
|
|
93
|
+
//
|
|
94
|
+
// #670 put the unit in the NAME one layer down -- `analysis/reports.ts` takes
|
|
95
|
+
// `windowRuns`, `deltaPp`, `minRatePct` -- and stopped at the CLI boundary
|
|
96
|
+
// because renaming a flag is user-visible. The flags carried the same silence
|
|
97
|
+
// they always had: the report prints `window: 30 runs`, `20.0pp increase` and
|
|
98
|
+
// `>= 10.0%`, so the unit only ever arrived AFTER the user had guessed. This
|
|
99
|
+
// section closes that gap using the same convention: `<measure><Unit>` in
|
|
100
|
+
// camelCase becomes `--<measure>-<unit>` on the command line.
|
|
101
|
+
//
|
|
102
|
+
// `--delta` is the sharpest case. It is a percentage-POINT threshold that looks
|
|
103
|
+
// like a percentage, so `--delta 20` against a failure rate moving 5% -> 6% is
|
|
104
|
+
// the difference between firing and staying silent, with nothing on screen to
|
|
105
|
+
// say which reading applied.
|
|
106
|
+
/** One decimal or integer, no sign: the only shape these thresholds accept. */
|
|
107
|
+
const UNSIGNED_NUMBER = /^(\d+(\.\d+)?|\.\d+)$/;
|
|
108
|
+
/**
|
|
109
|
+
* A whole count of runs, >= 1.
|
|
110
|
+
*
|
|
111
|
+
* The bare `Number.parseInt` this replaces mapped `--window seven` to `NaN`,
|
|
112
|
+
* which the query layer happily compared against and matched nothing -- a
|
|
113
|
+
* silent abstention wearing a clean fleet's clothes. A value that cannot mean
|
|
114
|
+
* what the flag says is a usage error instead.
|
|
115
|
+
*/
|
|
116
|
+
function parseRuns(flag) {
|
|
117
|
+
return (raw) => {
|
|
118
|
+
if (!/^\d+$/.test(raw.trim()) || Number.parseInt(raw, 10) < 1) {
|
|
119
|
+
throw new InvalidArgumentError(`${flag} takes a whole number of RUNS, at least 1 (e.g. 30); ` +
|
|
120
|
+
`got "${raw}".`);
|
|
121
|
+
}
|
|
122
|
+
return Number.parseInt(raw, 10);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* A 0-100 value on a percentage scale (`percent` for a rate, `percentage
|
|
127
|
+
* points` for a delta). Rejecting >100 is what catches the fraction habit from
|
|
128
|
+
* the other direction; `0.1` is still legal (a tenth of a percent), so the help
|
|
129
|
+
* text carries that half of the warning.
|
|
130
|
+
*/
|
|
131
|
+
function parsePercentScale(flag, unit) {
|
|
132
|
+
return (raw) => {
|
|
133
|
+
const n = Number.parseFloat(raw);
|
|
134
|
+
if (!UNSIGNED_NUMBER.test(raw.trim()) || !Number.isFinite(n) || n > 100) {
|
|
135
|
+
throw new InvalidArgumentError(`${flag} takes ${unit} between 0 and 100 (e.g. 10 means ten ${unit}, ` +
|
|
136
|
+
`not 0.1); got "${raw}".`);
|
|
137
|
+
}
|
|
138
|
+
return n;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const WINDOW_ALIAS = {
|
|
142
|
+
canonical: 'windowRuns',
|
|
143
|
+
legacy: 'window',
|
|
144
|
+
canonicalFlag: '--window-runs',
|
|
145
|
+
legacyFlag: '--window',
|
|
146
|
+
unit: 'a count of runs, not days',
|
|
147
|
+
};
|
|
148
|
+
const DELTA_ALIAS = {
|
|
149
|
+
canonical: 'deltaPp',
|
|
150
|
+
legacy: 'delta',
|
|
151
|
+
canonicalFlag: '--delta-pp',
|
|
152
|
+
legacyFlag: '--delta',
|
|
153
|
+
unit: 'percentage points, not percent',
|
|
154
|
+
};
|
|
155
|
+
const MIN_RATE_ALIAS = {
|
|
156
|
+
canonical: 'minRatePct',
|
|
157
|
+
legacy: 'minRate',
|
|
158
|
+
canonicalFlag: '--min-rate-pct',
|
|
159
|
+
legacyFlag: '--min-rate',
|
|
160
|
+
unit: 'a percentage, 0-100',
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Fold any deprecated spellings the user typed onto their canonical keys.
|
|
164
|
+
*
|
|
165
|
+
* The canonical flag wins when BOTH are given -- the alias is the legacy path,
|
|
166
|
+
* so an explicit new-style flag is the more deliberate statement of intent.
|
|
167
|
+
* `getOptionValueSource` is what separates "the user typed it" from "commander
|
|
168
|
+
* filled in the default"; comparing values would let a canonical default that
|
|
169
|
+
* happens to equal the alias silently discard the alias.
|
|
170
|
+
*/
|
|
171
|
+
function resolveUnitFlags(opts, cmd, deps, aliases) {
|
|
172
|
+
// Commander hands option bags back as plain objects keyed by camelCase flag
|
|
173
|
+
// name; the per-command interfaces describe them but carry no index
|
|
174
|
+
// signature, so the alias keys are reached through one local widening.
|
|
175
|
+
const merged = { ...opts };
|
|
176
|
+
for (const alias of aliases) {
|
|
177
|
+
if (merged[alias.legacy] === undefined)
|
|
178
|
+
continue;
|
|
179
|
+
deps.err(`note: ${alias.legacyFlag} is deprecated; use ${alias.canonicalFlag} ` +
|
|
180
|
+
`(the value is ${alias.unit}).`);
|
|
181
|
+
if (cmd.getOptionValueSource(alias.canonical) !== 'cli') {
|
|
182
|
+
merged[alias.canonical] = merged[alias.legacy];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return merged;
|
|
186
|
+
}
|
|
92
187
|
function writeArtifacts(artifacts, output) {
|
|
93
188
|
mkdirSync(output, { recursive: true });
|
|
94
189
|
for (const [name, content] of Object.entries(artifacts)) {
|
|
@@ -100,12 +195,12 @@ function flakyCmd(opts, deps) {
|
|
|
100
195
|
if (abstainOnEmptyHistory(store, deps, opts.json === true, 'flake rate')) {
|
|
101
196
|
return;
|
|
102
197
|
}
|
|
103
|
-
const rows = store.queryFlaky(opts.
|
|
198
|
+
const rows = store.queryFlaky(opts.windowRuns, opts.suite ?? null, opts.minRatePct);
|
|
104
199
|
if (opts.json) {
|
|
105
200
|
deps.out(jsonIndent2(rows));
|
|
106
201
|
}
|
|
107
202
|
else {
|
|
108
|
-
deps.out(buildFlakyReport(rows, opts.
|
|
203
|
+
deps.out(buildFlakyReport(rows, opts.windowRuns, opts.minRatePct));
|
|
109
204
|
}
|
|
110
205
|
}
|
|
111
206
|
function spikesCmd(opts, deps) {
|
|
@@ -130,7 +225,7 @@ function spikesCmd(opts, deps) {
|
|
|
130
225
|
deps.out(jsonIndent2(rows));
|
|
131
226
|
}
|
|
132
227
|
else {
|
|
133
|
-
deps.out(buildSpikesReport(rows, opts.
|
|
228
|
+
deps.out(buildSpikesReport(rows, opts.deltaPp));
|
|
134
229
|
}
|
|
135
230
|
}
|
|
136
231
|
function areaHealthCmd(opts, deps) {
|
|
@@ -197,8 +292,8 @@ function digestCmd(opts, deps) {
|
|
|
197
292
|
}
|
|
198
293
|
const engine = new AnalysisEngine(store);
|
|
199
294
|
const result = engine.run({
|
|
200
|
-
|
|
201
|
-
|
|
295
|
+
windowRuns: opts.windowRuns,
|
|
296
|
+
deltaPp: opts.deltaPp,
|
|
202
297
|
weeks: opts.weeks,
|
|
203
298
|
minSuites: opts.minSuites,
|
|
204
299
|
suite: opts.suite ?? null,
|
|
@@ -229,6 +324,16 @@ function printSlack(flakyCount, regCount, deps) {
|
|
|
229
324
|
deps.out(lines.join('\n'));
|
|
230
325
|
}
|
|
231
326
|
// --- assembly ----------------------------------------------------------------
|
|
327
|
+
// Option help shared across subcommands. Every analyze option carries a
|
|
328
|
+
// description: a blank one is how the silent unit survived this long, since the
|
|
329
|
+
// value's meaning lived only in the report it eventually printed.
|
|
330
|
+
const DB_URL_ENV = 'CANARY_HISTORY_DB_URL';
|
|
331
|
+
const DB_URL_DESC = 'History store URL (accepted, but analyze reads local NDJSON).';
|
|
332
|
+
const JSON_DESC = 'Emit the rows as JSON instead of a Markdown report.';
|
|
333
|
+
const WINDOW_RUNS_DESC = 'Rolling window measured in RUNS, not days.';
|
|
334
|
+
const MIN_RATE_PCT_DESC = 'Minimum flake rate to report, in PERCENT 0-100 (10 means 10%, not 0.1).';
|
|
335
|
+
const DELTA_PP_DESC = 'Spike threshold as a PERCENTAGE-POINT rise in failure rate ' +
|
|
336
|
+
'(20 means 5% -> 25%, not 5% -> 6%).';
|
|
232
337
|
/** Build a fresh `analyze` command wired to `depsInit`. */
|
|
233
338
|
export function createAnalyzeCommand(depsInit = {}) {
|
|
234
339
|
const deps = { ...defaultAnalyzeDeps(), ...depsInit };
|
|
@@ -239,89 +344,94 @@ export function createAnalyzeCommand(depsInit = {}) {
|
|
|
239
344
|
program
|
|
240
345
|
.command('flaky')
|
|
241
346
|
.description('Fleet-wide flake leaderboard.')
|
|
242
|
-
.addOption(new Option('-w, --window <
|
|
347
|
+
.addOption(new Option('-w, --window-runs <runs>', WINDOW_RUNS_DESC)
|
|
243
348
|
.default(30)
|
|
244
|
-
.argParser((
|
|
245
|
-
.
|
|
246
|
-
.
|
|
349
|
+
.argParser(parseRuns('--window-runs')))
|
|
350
|
+
.addOption(new Option('--window <runs>', 'Deprecated alias for --window-runs.').argParser(parseRuns('--window')))
|
|
351
|
+
.option('-s, --suite <suite>', 'Filter to a specific suite.')
|
|
352
|
+
.addOption(new Option('--min-rate-pct <percent>', MIN_RATE_PCT_DESC)
|
|
247
353
|
.default(10.0)
|
|
248
|
-
.argParser((
|
|
249
|
-
.addOption(new Option('--
|
|
250
|
-
.
|
|
251
|
-
.
|
|
252
|
-
|
|
354
|
+
.argParser(parsePercentScale('--min-rate-pct', 'percent')))
|
|
355
|
+
.addOption(new Option('--min-rate <percent>', 'Deprecated alias for --min-rate-pct.').argParser(parsePercentScale('--min-rate', 'percent')))
|
|
356
|
+
.addOption(new Option('--db-url <url>', DB_URL_DESC).env(DB_URL_ENV))
|
|
357
|
+
.option('--json', JSON_DESC)
|
|
358
|
+
.action((opts, cmd) => {
|
|
359
|
+
flakyCmd(resolveUnitFlags(opts, cmd, deps, [WINDOW_ALIAS, MIN_RATE_ALIAS]), deps);
|
|
253
360
|
});
|
|
254
361
|
program
|
|
255
362
|
.command('spikes')
|
|
256
363
|
.description('Recent failure spikes across suites.')
|
|
257
364
|
.option('--since <date>', 'ISO date filter, e.g. 2026-06-01')
|
|
258
|
-
.addOption(new Option('--delta <
|
|
365
|
+
.addOption(new Option('--delta-pp <points>', DELTA_PP_DESC)
|
|
259
366
|
.default(20.0)
|
|
260
|
-
.argParser((
|
|
261
|
-
.addOption(new Option('--
|
|
262
|
-
.
|
|
263
|
-
.
|
|
264
|
-
|
|
367
|
+
.argParser(parsePercentScale('--delta-pp', 'percentage points')))
|
|
368
|
+
.addOption(new Option('--delta <points>', 'Deprecated alias for --delta-pp.').argParser(parsePercentScale('--delta', 'percentage points')))
|
|
369
|
+
.addOption(new Option('--db-url <url>', DB_URL_DESC).env(DB_URL_ENV))
|
|
370
|
+
.option('--json', JSON_DESC)
|
|
371
|
+
.action((opts, cmd) => {
|
|
372
|
+
spikesCmd(resolveUnitFlags(opts, cmd, deps, [DELTA_ALIAS]), deps);
|
|
265
373
|
});
|
|
266
374
|
program
|
|
267
375
|
.command('area-health')
|
|
268
376
|
.description('Area degradation trends over time.')
|
|
269
|
-
.addOption(new Option('--weeks <
|
|
377
|
+
.addOption(new Option('--weeks <weeks>', 'Trend window, in whole weeks.')
|
|
270
378
|
.default(4)
|
|
271
379
|
.argParser((v) => Number.parseInt(v, 10)))
|
|
272
|
-
.addOption(new Option('--db-url <url>').env(
|
|
273
|
-
.option('--json')
|
|
380
|
+
.addOption(new Option('--db-url <url>', DB_URL_DESC).env(DB_URL_ENV))
|
|
381
|
+
.option('--json', JSON_DESC)
|
|
274
382
|
.action((opts) => {
|
|
275
383
|
areaHealthCmd(opts, deps);
|
|
276
384
|
});
|
|
277
385
|
program
|
|
278
386
|
.command('common-failures')
|
|
279
387
|
.description('Cross-suite failure fingerprinting.')
|
|
280
|
-
.option('--since <date>')
|
|
281
|
-
.addOption(new Option('--min-suites <
|
|
388
|
+
.option('--since <date>', 'ISO date filter, e.g. 2026-06-01')
|
|
389
|
+
.addOption(new Option('--min-suites <suites>', 'Report a failure only once it appears in this many SUITES.')
|
|
282
390
|
.default(2)
|
|
283
391
|
.argParser((v) => Number.parseInt(v, 10)))
|
|
284
|
-
.addOption(new Option('--db-url <url>').env(
|
|
285
|
-
.option('--json')
|
|
392
|
+
.addOption(new Option('--db-url <url>', DB_URL_DESC).env(DB_URL_ENV))
|
|
393
|
+
.option('--json', JSON_DESC)
|
|
286
394
|
.action((opts) => {
|
|
287
395
|
commonFailuresCmd(opts, deps);
|
|
288
396
|
});
|
|
289
397
|
program
|
|
290
398
|
.command('regression-candidates')
|
|
291
399
|
.description('Tests newly and consistently broken after a green streak.')
|
|
292
|
-
.addOption(new Option('--min-green <
|
|
400
|
+
.addOption(new Option('--min-green <runs>', 'Length of the prior green streak, in RUNS.')
|
|
293
401
|
.default(5)
|
|
294
402
|
.argParser((v) => Number.parseInt(v, 10)))
|
|
295
|
-
.addOption(new Option('--recent-failures <
|
|
403
|
+
.addOption(new Option('--recent-failures <runs>', 'Consecutive failing RUNS required after the streak.')
|
|
296
404
|
.default(3)
|
|
297
405
|
.argParser((v) => Number.parseInt(v, 10)))
|
|
298
|
-
.addOption(new Option('--db-url <url>').env(
|
|
299
|
-
.option('--json')
|
|
406
|
+
.addOption(new Option('--db-url <url>', DB_URL_DESC).env(DB_URL_ENV))
|
|
407
|
+
.option('--json', JSON_DESC)
|
|
300
408
|
.action((opts) => {
|
|
301
409
|
regressionCandidatesCmd(opts, deps);
|
|
302
410
|
});
|
|
303
411
|
program
|
|
304
412
|
.command('digest')
|
|
305
413
|
.description('Combined digest of all five report types.')
|
|
306
|
-
.addOption(new Option('--window <
|
|
414
|
+
.addOption(new Option('--window-runs <runs>', WINDOW_RUNS_DESC)
|
|
307
415
|
.default(30)
|
|
308
|
-
.argParser((
|
|
309
|
-
.addOption(new Option('--
|
|
416
|
+
.argParser(parseRuns('--window-runs')))
|
|
417
|
+
.addOption(new Option('--window <runs>', 'Deprecated alias for --window-runs.').argParser(parseRuns('--window')))
|
|
418
|
+
.addOption(new Option('--delta-pp <points>', DELTA_PP_DESC)
|
|
310
419
|
.default(20.0)
|
|
311
|
-
.argParser((
|
|
312
|
-
.addOption(new Option('--
|
|
420
|
+
.argParser(parsePercentScale('--delta-pp', 'percentage points')))
|
|
421
|
+
.addOption(new Option('--delta <points>', 'Deprecated alias for --delta-pp.').argParser(parsePercentScale('--delta', 'percentage points')))
|
|
422
|
+
.addOption(new Option('--weeks <weeks>', 'Area-health trend window, in whole weeks.')
|
|
313
423
|
.default(4)
|
|
314
424
|
.argParser((v) => Number.parseInt(v, 10)))
|
|
315
|
-
.addOption(new Option('--min-suites <
|
|
425
|
+
.addOption(new Option('--min-suites <suites>', 'Report a failure only once it appears in this many SUITES.')
|
|
316
426
|
.default(2)
|
|
317
427
|
.argParser((v) => Number.parseInt(v, 10)))
|
|
318
|
-
.option('--suite <suite>')
|
|
319
|
-
.addOption(new Option('--output <dir>').default('test-results/analysis'))
|
|
320
|
-
.option('--json')
|
|
321
|
-
.option('--slack')
|
|
322
|
-
.addOption(new Option('--db-url <url>').env(
|
|
323
|
-
.action((opts) => {
|
|
324
|
-
digestCmd(opts, deps);
|
|
428
|
+
.option('--suite <suite>', 'Filter to a specific suite.')
|
|
429
|
+
.addOption(new Option('--output <dir>', 'Directory the Markdown artifacts are ' + 'written to.').default('test-results/analysis'))
|
|
430
|
+
.option('--json', 'Emit per-section counts as JSON instead of the digest.')
|
|
431
|
+
.option('--slack', 'Emit a short Slack-formatted summary.')
|
|
432
|
+
.addOption(new Option('--db-url <url>', DB_URL_DESC).env(DB_URL_ENV))
|
|
433
|
+
.action((opts, cmd) => {
|
|
434
|
+
digestCmd(resolveUnitFlags(opts, cmd, deps, [WINDOW_ALIAS, DELTA_ALIAS]), deps);
|
|
325
435
|
});
|
|
326
436
|
for (const sub of program.commands) {
|
|
327
437
|
sub.exitOverride(normalizeUsageExit);
|
|
@@ -32,19 +32,19 @@ export class AnalysisEngine {
|
|
|
32
32
|
this.store = store;
|
|
33
33
|
}
|
|
34
34
|
run(opts = {}) {
|
|
35
|
-
const
|
|
36
|
-
const
|
|
35
|
+
const windowRuns = opts.windowRuns ?? 30;
|
|
36
|
+
const deltaPp = opts.deltaPp ?? 20.0;
|
|
37
37
|
const weeks = opts.weeks ?? 4;
|
|
38
38
|
const minSuites = opts.minSuites ?? 2;
|
|
39
|
-
const
|
|
39
|
+
const minFlakeRatePct = opts.minFlakeRatePct ?? 10.0;
|
|
40
40
|
const minGreen = opts.minGreen ?? 5;
|
|
41
41
|
const recentFailures = opts.recentFailures ?? 3;
|
|
42
42
|
const suite = opts.suite ?? null;
|
|
43
|
-
const flaky = this.store.queryFlaky(
|
|
43
|
+
const flaky = this.store.queryFlaky(windowRuns, suite, minFlakeRatePct);
|
|
44
44
|
const suitesToQuery = suite ? [suite] : this.discoverSuites();
|
|
45
45
|
const spikesRows = [];
|
|
46
46
|
for (const s of suitesToQuery) {
|
|
47
|
-
const summary = this.store.querySummary(s,
|
|
47
|
+
const summary = this.store.querySummary(s, windowRuns * 2);
|
|
48
48
|
for (const row of def(summary.runs, [])) {
|
|
49
49
|
// query_summary rows omit suite; the spikes builder groups by it, so
|
|
50
50
|
// tag each pooled row with the suite it came from (matches Python).
|
|
@@ -67,14 +67,14 @@ export class AnalysisEngine {
|
|
|
67
67
|
areaHealth: areaRows,
|
|
68
68
|
commonFailures: commonRows,
|
|
69
69
|
regressionCandidates,
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
windowRuns,
|
|
71
|
+
deltaPp,
|
|
72
72
|
weeks,
|
|
73
73
|
minSuites,
|
|
74
74
|
});
|
|
75
75
|
const artifacts = {
|
|
76
|
-
'flaky.md': buildFlakyReport(flaky,
|
|
77
|
-
'spikes.md': buildSpikesReport(spikesRows,
|
|
76
|
+
'flaky.md': buildFlakyReport(flaky, windowRuns, minFlakeRatePct),
|
|
77
|
+
'spikes.md': buildSpikesReport(spikesRows, deltaPp),
|
|
78
78
|
'area-health.md': buildAreaHealthReport(areaRows, weeks),
|
|
79
79
|
'common-failures.md': buildCommonFailuresReport(commonRows, minSuites),
|
|
80
80
|
'regression-candidates.md': buildRegressionCandidatesReport(regressionCandidates),
|
|
Binary file
|
|
@@ -19,7 +19,7 @@ import { ckInitCmd } from './company-knowledge-cli.js';
|
|
|
19
19
|
import { extractFrameworkHint } from './core/classifier.js';
|
|
20
20
|
import { VALID_CATEGORIES, buildFeedback } from './core/feedback.js';
|
|
21
21
|
import { OverlayNotFound, listOverlays, resolveOverlay, } from './core/overlays.js';
|
|
22
|
-
import { JS_TEST_EXTENSIONS,
|
|
22
|
+
import { JS_TEST_EXTENSIONS, frameworkForPath } from './core/static-linter.js';
|
|
23
23
|
import { RunSummary } from './core/ticket-updater.js';
|
|
24
24
|
import { renderBanner } from './ui/banner.js';
|
|
25
25
|
import { ARROW, CHECK, CHECK_MARK, CROSS, EM_DASH, HAMMER, NEXT, REDX, ROCKET, WARN, WRENCH, } from './main-deps.js';
|
|
@@ -462,7 +462,7 @@ function abstainOnZeroFiles(files, path, deps, json) {
|
|
|
462
462
|
* from a scanner that could not parse the input is an abstention, not a pass.
|
|
463
463
|
*/
|
|
464
464
|
function abstainOnUnlintableFile(path, deps, json) {
|
|
465
|
-
if (
|
|
465
|
+
if (frameworkForPath(path) !== null)
|
|
466
466
|
return;
|
|
467
467
|
const ext = extname(path) || basename(path);
|
|
468
468
|
abstain(`Cannot lint ${ext} — no ruleset parses it, so a clean result would be ` +
|