mjolnir-qa 0.5.5 → 0.5.6
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/CHANGELOG.md +11 -0
- package/README.md +3 -3
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +389 -167
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -74,6 +74,17 @@ scan`. Unknown categories are a usage error (exit 10).
|
|
|
74
74
|
- help registry gained `why`, `handoff`, `install` and the new flags;
|
|
75
75
|
site/reference/cli.md documents the handoff trust model.
|
|
76
76
|
|
|
77
|
+
## [0.5.6] — 2026-09-06
|
|
78
|
+
|
|
79
|
+
### Changes since 0.5.5
|
|
80
|
+
|
|
81
|
+
- Merge pull request #39 from Sergey-Bar/eng/verification-trust-2.0
|
|
82
|
+
- docs: regenerate readme SVGs under the revision-2 CI-rule measurements
|
|
83
|
+
- docs: regenerate rule-doc occurrence tables (yarnpkg-berry corpus lane)
|
|
84
|
+
- fix: eslint pragma for the file-wide consumer fallback regex
|
|
85
|
+
- fix: rev-2 enforcement arm in QA-CI-008 was dead code + coverage arms for CI rules
|
|
86
|
+
- ci-family trust repair: M2 detector re-adjudication, revision 2 re-measurement, workflow corpus lane
|
|
87
|
+
|
|
77
88
|
## [0.5.5] — 2026-09-06
|
|
78
89
|
|
|
79
90
|
### Audit Remediation 1.0 (engine correctness + trust boundary)
|
package/README.md
CHANGED
|
@@ -284,13 +284,13 @@ firewall.
|
|
|
284
284
|
|
|
285
285
|
### How much of this is measured
|
|
286
286
|
|
|
287
|
-
**
|
|
288
|
-
[docs/FP-AUDIT.md](docs/FP-AUDIT.md)). The other
|
|
287
|
+
**76 of 99 rules carry a false-positive rate measured against real OSS code** (≥ 10 hand-classified findings each; see
|
|
288
|
+
[docs/FP-AUDIT.md](docs/FP-AUDIT.md)). The other 23 ship on the author's
|
|
289
289
|
estimate. Every scan footer tells you how many of the rules that _fired_
|
|
290
290
|
are measured; `mjolnir rules --unmeasured` lists the ones that aren't;
|
|
291
291
|
every rule's `mjolnir explain` page states its status. We publish the rate
|
|
292
292
|
even when it's ugly — QA-CS-103 audits at 95% and is quarantined for it.
|
|
293
|
-
Growing that
|
|
293
|
+
Growing that number is the project's continuing work.
|
|
294
294
|
|
|
295
295
|
### Rule tiers and language maturity
|
|
296
296
|
|
package/dist/cli.d.mts
CHANGED
|
@@ -690,7 +690,7 @@ declare const runScan: typeof runScan$1, buildUniversalRules: typeof buildUniver
|
|
|
690
690
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
691
691
|
* `tests/version-consistency.spec.ts` locally.
|
|
692
692
|
*/
|
|
693
|
-
declare const CLI_VERSION = "0.5.
|
|
693
|
+
declare const CLI_VERSION = "0.5.6";
|
|
694
694
|
/** A usage-error detail: the offending token, when one exists. */
|
|
695
695
|
interface UsageErrorDetail {
|
|
696
696
|
/** The unknown flag or rejected value (e.g. `--nope`, `loud`). */
|
package/dist/cli.mjs
CHANGED
|
@@ -1766,8 +1766,10 @@ function parseWorkflow(text) {
|
|
|
1766
1766
|
const step = s;
|
|
1767
1767
|
return {
|
|
1768
1768
|
...typeof step["name"] === "string" ? { name: step["name"] } : {},
|
|
1769
|
+
...typeof step["id"] === "string" ? { id: step["id"] } : {},
|
|
1769
1770
|
...typeof step["run"] === "string" ? { run: step["run"] } : {},
|
|
1770
1771
|
...typeof step["uses"] === "string" ? { uses: step["uses"] } : {},
|
|
1772
|
+
...typeof step["if"] === "string" ? { if: step["if"] } : {},
|
|
1771
1773
|
...step["with"] && typeof step["with"] === "object" && !Array.isArray(step["with"]) ? { with: { ...step["with"] } } : {},
|
|
1772
1774
|
...typeof step["continue-on-error"] === "boolean" || typeof step["continue-on-error"] === "string" ? { "continue-on-error": step["continue-on-error"] } : {}
|
|
1773
1775
|
};
|
|
@@ -3546,7 +3548,7 @@ const VERIFICATION_GATE_RE = new RegExp([
|
|
|
3546
3548
|
String.raw`\bplaywright\s+test\b`,
|
|
3547
3549
|
String.raw`\b(?:pytest|tox|nox)\b`,
|
|
3548
3550
|
String.raw`\bpython\s+-m\s+(?:pytest|unittest)\b`,
|
|
3549
|
-
String.raw`\bmvn
|
|
3551
|
+
String.raw`\bmvn[wd]?\b[^\n]+\b(?:test|verify)\b`,
|
|
3550
3552
|
String.raw`\b(?:\./)?gradlew?\b[^\n]+\btest\b`,
|
|
3551
3553
|
String.raw`\bdotnet\s+test\b`,
|
|
3552
3554
|
String.raw`\bgo\s+test\b`,
|
|
@@ -3578,6 +3580,45 @@ function stepIsVerificationGate(step) {
|
|
|
3578
3580
|
}
|
|
3579
3581
|
return false;
|
|
3580
3582
|
}
|
|
3583
|
+
/**
|
|
3584
|
+
* detectorRevision 2 (M2, 2026-09-04): legitimate continue-on-error shapes
|
|
3585
|
+
* the rev-1 detector flagged. Each class was proven by adjudication
|
|
3586
|
+
* (docs/FP-AUDIT.md notes, 2026-09-02):
|
|
3587
|
+
* 1. Re-run idiom (appsmith ci-test-playwright.yml): the gate runs under
|
|
3588
|
+
* continue-on-error ONLY to convert attempt 1 into a non-failing
|
|
3589
|
+
* outcome so a follow-up step can re-run the SAME gate; the follow-up
|
|
3590
|
+
* runs unconditionally and its failure fails the job — the gate still
|
|
3591
|
+
* blocks.
|
|
3592
|
+
* 2. Non-gate shapes that matched the rev-1 run-text regex: report
|
|
3593
|
+
* aggregation (`test --merge-reports`), test-ID collection, shard
|
|
3594
|
+
* rebalancing helpers — they execute no verification.
|
|
3595
|
+
* 3. Steps whose `if:`/name explicitly mark them advisory/non-blocking
|
|
3596
|
+
* inside jobs that run the real gates (vault custom-linter,
|
|
3597
|
+
* github-docs sync-sdk-docs).
|
|
3598
|
+
*/
|
|
3599
|
+
const RERUN_OUTCOME_RE = /steps\.[\w-]+\.outcome\s*==\s*['"]?failure['"]?|steps\.[\w-]+\.conclusion\s*==\s*['"]?failure['"]?/;
|
|
3600
|
+
function jobReRunsTheMaskedGate(steps, maskedIndex) {
|
|
3601
|
+
return steps.some((s, i) => {
|
|
3602
|
+
if (i === maskedIndex) return false;
|
|
3603
|
+
const cond = typeof s?.if === "string" ? s.if : "";
|
|
3604
|
+
if (!RERUN_OUTCOME_RE.test(cond)) return false;
|
|
3605
|
+
return stepIsVerificationGate(s);
|
|
3606
|
+
});
|
|
3607
|
+
}
|
|
3608
|
+
/** Explicit advisory/non-verification shapes in run text. */
|
|
3609
|
+
const NON_GATE_RUN_RE = /--merge-reports\b|\bmerge-reports\b|\bcollect[- ]only\b|list[- ]tests\b|\brebalance\b/i;
|
|
3610
|
+
/**
|
|
3611
|
+
* Steps whose own name declares the non-blocking intent (adjudicated FP:
|
|
3612
|
+
* vault code-checker.yml "Check custom linters (non-blocking)" — the
|
|
3613
|
+
* workflow's own vocabulary marks the advisory contract, so the green
|
|
3614
|
+
* check hides nothing the author claimed would gate).
|
|
3615
|
+
*/
|
|
3616
|
+
const NON_BLOCKING_NAME_RE = /\(\s*non-?blocking\s*\)|\[.*non-?blocking.*\]/i;
|
|
3617
|
+
function stepIsGateExcludingNonVerification(step) {
|
|
3618
|
+
if (step.name && NON_BLOCKING_NAME_RE.test(step.name)) return false;
|
|
3619
|
+
if (step.run && NON_GATE_RUN_RE.test(step.run)) return false;
|
|
3620
|
+
return stepIsVerificationGate(step);
|
|
3621
|
+
}
|
|
3581
3622
|
const continueOnError = defineRule({
|
|
3582
3623
|
id: "QA-CI-001",
|
|
3583
3624
|
category: "QA-CI",
|
|
@@ -3604,13 +3645,13 @@ const continueOnError = defineRule({
|
|
|
3604
3645
|
for (const [jobName, job] of Object.entries(jobs)) {
|
|
3605
3646
|
const steps = job?.steps ?? [];
|
|
3606
3647
|
if (job && job["continue-on-error"] === true) {
|
|
3607
|
-
if (steps.some(
|
|
3648
|
+
if (steps.some(stepIsGateExcludingNonVerification)) findings.push({
|
|
3608
3649
|
severity: "error",
|
|
3609
3650
|
confidence: "high",
|
|
3610
3651
|
findingType: "deterministic-defect",
|
|
3611
3652
|
qaImpact: "FALSE-GREEN",
|
|
3612
3653
|
file: ctx.path,
|
|
3613
|
-
line: findLine$
|
|
3654
|
+
line: findLine$2(ctx.text, new RegExp(`^\\s{2,6}${escapeRe$1(jobName)}:`, "m")),
|
|
3614
3655
|
column: 1,
|
|
3615
3656
|
message: `Job \`${jobName}\` runs a verification gate under \`continue-on-error: true\`.`,
|
|
3616
3657
|
why: "This job can fail every day and CI will still show green. The checkmark on this workflow cannot be trusted.",
|
|
@@ -3619,7 +3660,8 @@ const continueOnError = defineRule({
|
|
|
3619
3660
|
}
|
|
3620
3661
|
for (const [i, step] of steps.entries()) {
|
|
3621
3662
|
if (!step || step["continue-on-error"] !== true) continue;
|
|
3622
|
-
if (!
|
|
3663
|
+
if (!stepIsGateExcludingNonVerification(step)) continue;
|
|
3664
|
+
if (jobReRunsTheMaskedGate(steps, i)) continue;
|
|
3623
3665
|
findings.push({
|
|
3624
3666
|
severity: "error",
|
|
3625
3667
|
confidence: "high",
|
|
@@ -3681,7 +3723,7 @@ function locateStepContinueOnError(text, step) {
|
|
|
3681
3723
|
if (anchorAt !== -1) return lineOf(text, anchorAt);
|
|
3682
3724
|
return 1;
|
|
3683
3725
|
}
|
|
3684
|
-
function findLine$
|
|
3726
|
+
function findLine$2(text, re) {
|
|
3685
3727
|
const m = re.exec(text);
|
|
3686
3728
|
if (!m) return 1;
|
|
3687
3729
|
return lineOf(text, m.index);
|
|
@@ -3691,7 +3733,7 @@ function lineOf(text, index) {
|
|
|
3691
3733
|
for (let i = 0; i < index; i++) if (text[i] === "\n") line++;
|
|
3692
3734
|
return line;
|
|
3693
3735
|
}
|
|
3694
|
-
function escapeRe$
|
|
3736
|
+
function escapeRe$1(s) {
|
|
3695
3737
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3696
3738
|
}
|
|
3697
3739
|
//#endregion
|
|
@@ -3716,6 +3758,7 @@ const swallowedExitCode = defineRule({
|
|
|
3716
3758
|
detectionStrategy: "LEXICAL",
|
|
3717
3759
|
introduced: "0.1.0",
|
|
3718
3760
|
tier: "extended",
|
|
3761
|
+
detectorRevision: 2,
|
|
3719
3762
|
run(ctx) {
|
|
3720
3763
|
const findings = [];
|
|
3721
3764
|
const re = /\|\|\s*true\b|:\s*(?:npm|yarn|pnpm|make|pytest|go)\b[^`\n]*\|\|\s*echo/g;
|
|
@@ -3767,6 +3810,7 @@ const retryMasking = defineRule({
|
|
|
3767
3810
|
detectionStrategy: "LEXICAL",
|
|
3768
3811
|
introduced: "0.1.0",
|
|
3769
3812
|
tier: "extended",
|
|
3813
|
+
detectorRevision: 2,
|
|
3770
3814
|
run(ctx) {
|
|
3771
3815
|
const findings = [];
|
|
3772
3816
|
const doc = ctx.ast;
|
|
@@ -3782,38 +3826,74 @@ const retryMasking = defineRule({
|
|
|
3782
3826
|
findingType: "deterministic-defect",
|
|
3783
3827
|
qaImpact: "FLAKY-RISK",
|
|
3784
3828
|
file: ctx.path,
|
|
3785
|
-
line:
|
|
3829
|
+
line: findStepUsesLine(ctx.text, step.uses, command),
|
|
3786
3830
|
column: 1,
|
|
3787
3831
|
message: `Job \`${jobName}\` wraps a test command in an automatic retry action.`,
|
|
3788
3832
|
why: "Retrying tests until they pass hides flaky and intermittent failures — the green check no longer means the suite passed.",
|
|
3789
3833
|
fix: "Remove the retry wrapper; investigate the underlying flakiness instead."
|
|
3790
3834
|
});
|
|
3791
3835
|
}
|
|
3792
|
-
if (step?.run
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3836
|
+
if (step?.run) {
|
|
3837
|
+
const LOOP_RE = /\bfor\b[^\n]*\$\(\s*(?:seq|range)\b|\bfor\b[^\n]*\{\d+\.\.\d+\}|\bfor\s+\w+\s+in\s+\d+(?:[,\t ]+\d+)*[;\s]*do\b|\bwhile\b[^\n]*\btrue\b|\bwhile\s+:;|\bmax_attempts\b|\buntil\b[^\n]*\bsucceed\b/i;
|
|
3838
|
+
const TEST_GATE_RE = /\b(?:npm|yarn|pnpm|bun)\s+(?:run\s+)?(?:test|t)\b|\bnpx\s+(?:vitest|jest|mocha|ava|playwright\s+test)\b|\b(?:vitest|jest|mocha|ava|tap)\b|\bplaywright\s+test\b|\b(?:pytest|tox|nox)\b|\bpython\s+-m\s+(?:pytest|unittest)\b|\bmvn[wd]?\b[^\n]+\b(?:test|verify)\b|\b(?:[.]\/)?gradlew?\b[^\n]+\btest\b|\bdotnet\s+test\b|\bgo\s+test\b|\bcargo\s+test\b|\bmake\s+[\w./\\-]*test\b/i;
|
|
3839
|
+
const isCurlProbe = /\b(?:curl|wget)\b/.test(step.run) && !TEST_GATE_RE.test(step.run);
|
|
3840
|
+
if (LOOP_RE.test(step.run) && TEST_GATE_RE.test(step.run) && !isCurlProbe) findings.push({
|
|
3841
|
+
severity: "warning",
|
|
3842
|
+
confidence: "medium",
|
|
3843
|
+
findingType: "heuristic-risk",
|
|
3844
|
+
qaImpact: "FLAKY-RISK",
|
|
3845
|
+
file: ctx.path,
|
|
3846
|
+
line: findStepLoopLine(ctx.text, step.run, LOOP_RE),
|
|
3847
|
+
column: 1,
|
|
3848
|
+
message: `Job \`${jobName}\` contains a shell retry loop around tests.`,
|
|
3849
|
+
why: "Retry-until-pass loops mask intermittent failures instead of surfacing them.",
|
|
3850
|
+
fix: "Run tests once; track and fix flakes explicitly."
|
|
3851
|
+
});
|
|
3852
|
+
}
|
|
3804
3853
|
}
|
|
3805
3854
|
return findings;
|
|
3806
3855
|
}
|
|
3807
3856
|
});
|
|
3808
|
-
|
|
3857
|
+
/**
|
|
3858
|
+
* Line of the loop construct belonging to THIS step.
|
|
3859
|
+
*
|
|
3860
|
+
* detectorRevision 2: anchors at the step's first run line, then finds the
|
|
3861
|
+
* loop construct at or after it — a file-wide LOOP_RE search reported every
|
|
3862
|
+
* matching step on the first loop occurrence in the file.
|
|
3863
|
+
*/
|
|
3864
|
+
function findStepLoopLine(text, run, loopRe) {
|
|
3865
|
+
const firstLine = run.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
|
|
3866
|
+
const anchorAt = text.indexOf(firstLine);
|
|
3867
|
+
const re = new RegExp(loopRe.source, "gi");
|
|
3868
|
+
re.lastIndex = Math.max(0, anchorAt);
|
|
3809
3869
|
const m = re.exec(text);
|
|
3810
3870
|
if (!m) return 1;
|
|
3811
3871
|
let line = 1;
|
|
3812
3872
|
for (let i = 0; i < m.index; i++) if (text[i] === "\n") line++;
|
|
3813
3873
|
return line;
|
|
3814
3874
|
}
|
|
3815
|
-
|
|
3816
|
-
|
|
3875
|
+
/**
|
|
3876
|
+
* Line of the retry-action `uses:` belonging to THIS step.
|
|
3877
|
+
*
|
|
3878
|
+
* detectorRevision 2: anchors at the step's own `with.command` text and
|
|
3879
|
+
* takes the nearest `uses:` occurrence at or before it — a file-wide search
|
|
3880
|
+
* reported every wrapping step on the first occurrence when several jobs
|
|
3881
|
+
* use the same retry action at the same version.
|
|
3882
|
+
*/
|
|
3883
|
+
function findStepUsesLine(text, uses, command) {
|
|
3884
|
+
const needle = uses.trim();
|
|
3885
|
+
const firstCmdLine = command.trim().split("\n")[0]?.trim();
|
|
3886
|
+
const at = nearestBefore(text, Math.max(0, text.indexOf(firstCmdLine)), needle);
|
|
3887
|
+
if (at === -1) return 1;
|
|
3888
|
+
let line = 1;
|
|
3889
|
+
for (let i = 0; i < at; i++) if (text[i] === "\n") line++;
|
|
3890
|
+
return line;
|
|
3891
|
+
}
|
|
3892
|
+
/** The nearest occurrence of `needle` ending at or before `at`. */
|
|
3893
|
+
function nearestBefore(text, at, needle) {
|
|
3894
|
+
const windowStart = Math.max(0, at - 2e3);
|
|
3895
|
+
const rel = text.slice(windowStart, at + 1).lastIndexOf(needle);
|
|
3896
|
+
return rel !== -1 ? windowStart + rel : -1;
|
|
3817
3897
|
}
|
|
3818
3898
|
//#endregion
|
|
3819
3899
|
//#region src/rules/ci/qa-ci-005-report-never-generated.ts
|
|
@@ -3825,15 +3905,76 @@ function escapeRe$1(s) {
|
|
|
3825
3905
|
* that no previous step produces — the gate reads an empty or stale file
|
|
3826
3906
|
* and passes vacuously. Product-MVP §35: "Required report not produced".
|
|
3827
3907
|
*/
|
|
3908
|
+
/**
|
|
3909
|
+
* Producer vocabulary for coverage reports, one regex over run text.
|
|
3910
|
+
*
|
|
3911
|
+
* detectorRevision 2 (M2, 2026-09-04): the rev-1 regex knew only
|
|
3912
|
+
* `vitest|jest|nyc --coverage`. Adjudication against the real corpus
|
|
3913
|
+
* (docs/FP-AUDIT.md rows) proved four blindness classes, each a real
|
|
3914
|
+
* producer the regex could not see:
|
|
3915
|
+
* 1. Java/JaCoCo — `mvn verify` with the jacoco-maven-plugin, Gradle
|
|
3916
|
+
* `jacocoTestReport`
|
|
3917
|
+
* 2. Python — `pytest --cov` (pytest-cov), `coverage run/combine/json/xml`
|
|
3918
|
+
* (coverage.py), `COVERAGE_FILE` env wiring
|
|
3919
|
+
* 3. Go / .NET / Rust — `go test -coverprofile`, `dotnet test --collect`,
|
|
3920
|
+
* `cargo llvm-cov` / `cargo tarpaulin`
|
|
3921
|
+
* 4. Script-name coverage — `npm run test:coverage` and friends, where the
|
|
3922
|
+
* coverage flag lives inside package.json, invisible from the workflow
|
|
3923
|
+
*
|
|
3924
|
+
* Known residual limitation (documented, not fixable single-file): a
|
|
3925
|
+
* producer in a DIFFERENT workflow (reusable workflows, artifact producers
|
|
3926
|
+
* from `workflow_run` jobs) or inside tox.ini / vitest.config is invisible;
|
|
3927
|
+
* cross-workflow consumers remain flaggable — measured rates carry those
|
|
3928
|
+
* verdicts (streamlit ai-test-coverage.yml class).
|
|
3929
|
+
*/
|
|
3930
|
+
/**
|
|
3931
|
+
* Producer vocabulary for coverage reports, tested one literal at a time
|
|
3932
|
+
* over run/`with` text.
|
|
3933
|
+
*
|
|
3934
|
+
* detectorRevision 2 (M2, 2026-09-04): the rev-1 regex knew only
|
|
3935
|
+
* `vitest|jest|nyc --coverage`. Adjudication against the real corpus
|
|
3936
|
+
* (docs/FP-AUDIT.md rows) proved four blindness classes, each a real
|
|
3937
|
+
* producer the regex could not see:
|
|
3938
|
+
* 1. Java/JaCoCo — `mvn verify` with the jacoco-maven-plugin, Gradle
|
|
3939
|
+
* `jacocoTestReport`
|
|
3940
|
+
* 2. Python — `pytest --cov` (pytest-cov), `coverage run/combine/json/xml`
|
|
3941
|
+
* (coverage.py), `COVERAGE_FILE` env wiring
|
|
3942
|
+
* 3. Go / .NET / Rust — `go test -coverprofile`, `dotnet test --collect`,
|
|
3943
|
+
* `cargo llvm-cov` / `cargo tarpaulin`
|
|
3944
|
+
* 4. Script-name coverage — `npm run test:coverage` and friends, where the
|
|
3945
|
+
* coverage flag lives inside package.json, invisible from the workflow
|
|
3946
|
+
*
|
|
3947
|
+
* Known residual limitation (documented, not fixable single-file): a
|
|
3948
|
+
* producer in a DIFFERENT workflow (reusable workflows, artifact producers
|
|
3949
|
+
* from `workflow_run` jobs) or inside tox.ini / vitest.config is invisible;
|
|
3950
|
+
* cross-workflow consumers remain flaggable — measured rates carry those
|
|
3951
|
+
* verdicts (streamlit ai-test-coverage.yml class).
|
|
3952
|
+
*/
|
|
3953
|
+
const COVERAGE_PRODUCERS = [
|
|
3954
|
+
/\b(?:npx\s+)?(?:vitest|jest|nyc)\b[^\n]*--coverage\b/i,
|
|
3955
|
+
/--coverage\b/i,
|
|
3956
|
+
/\b(?:npm|yarn|pnpm|bun)\s+(?:run\s+)?[\w:@/.-]*coverage[\w:@/.-]*/i,
|
|
3957
|
+
/\bmvn[wd]?\b[^\n]*\b(?:jacoco|verify)\b/i,
|
|
3958
|
+
/\b(?:[.]\/)?gradlew?\b[^\n]*\bjacoco\w*\b/i,
|
|
3959
|
+
/\bjacoco\w*Report\b/i,
|
|
3960
|
+
/\bpytest\b[^\n]*--cov\b/i,
|
|
3961
|
+
/\bcoverage\s+(?:run|combine|json|xml|report)\b/i,
|
|
3962
|
+
/\bCOVERAGE_FILE\b/i,
|
|
3963
|
+
/\bpytest-cov\b/i,
|
|
3964
|
+
/\bgo\s+test\b[^\n]*-cover(?:profile|mode|age)\b/i,
|
|
3965
|
+
/\bdotnet\s+test\b[^\n]*(?:--collect|--coverage|collect coverage)/i,
|
|
3966
|
+
/\bcargo\s+(?:llvm-cov|tarpaulin)\b/i,
|
|
3967
|
+
/\b(?:coverage|lcov|cobertura)[\w./-]*\.(?:info|xml|json|lcov)\b/i
|
|
3968
|
+
];
|
|
3828
3969
|
/** Known report-consumption patterns and the commands that produce them. */
|
|
3829
3970
|
const CONSUMERS = [{
|
|
3830
3971
|
re: /codecov|coveralls/i,
|
|
3831
3972
|
stepRe: /upload-artifact/i,
|
|
3832
|
-
|
|
3973
|
+
producers: COVERAGE_PRODUCERS,
|
|
3833
3974
|
label: "coverage artifact"
|
|
3834
3975
|
}, {
|
|
3835
3976
|
re: /codecov|coveralls/i,
|
|
3836
|
-
|
|
3977
|
+
producers: COVERAGE_PRODUCERS,
|
|
3837
3978
|
label: "coverage upload"
|
|
3838
3979
|
}];
|
|
3839
3980
|
const reportNeverGenerated = defineRule({
|
|
@@ -3852,27 +3993,34 @@ const reportNeverGenerated = defineRule({
|
|
|
3852
3993
|
detectionStrategy: "LEXICAL",
|
|
3853
3994
|
introduced: "0.1.0",
|
|
3854
3995
|
tier: "quarantine",
|
|
3996
|
+
detectorRevision: 2,
|
|
3855
3997
|
run(ctx) {
|
|
3856
3998
|
const findings = [];
|
|
3857
3999
|
const doc = ctx.ast;
|
|
3858
4000
|
if (!doc?.jobs) return findings;
|
|
3859
4001
|
const jobEntries = Object.entries(doc.jobs);
|
|
3860
|
-
const
|
|
4002
|
+
const stepText = (s) => [s.run ?? "", ...s.uses?.includes("upload-artifact") ? [] : Object.values(s.with ?? {}).filter((v) => typeof v === "string")].join("\n");
|
|
4003
|
+
const workflowRunText = jobEntries.map(([, j]) => (j?.steps ?? []).map(stepText).join("\n")).join("\n");
|
|
3861
4004
|
for (const [jobName, job] of jobEntries) {
|
|
3862
4005
|
const steps = job?.steps ?? [];
|
|
3863
|
-
const allRunText = steps.map(
|
|
4006
|
+
const allRunText = steps.map(stepText).join("\n");
|
|
3864
4007
|
for (const consumer of CONSUMERS) {
|
|
3865
4008
|
if (!(steps.some((s) => {
|
|
3866
4009
|
if (s.uses && consumer.re.test(s.uses)) return true;
|
|
3867
4010
|
if (consumer.stepRe && s.uses && consumer.stepRe.test(s.uses) && s.with && typeof s.with["path"] === "string" && /coverage|lcov/i.test(s.with["path"])) return true;
|
|
3868
4011
|
return false;
|
|
3869
4012
|
}) || consumer.re.test(allRunText))) continue;
|
|
3870
|
-
|
|
4013
|
+
const coverageDataDownload = steps.some((s) => s.uses?.includes("download-artifact") === true && s.with !== void 0 && [
|
|
4014
|
+
"pattern",
|
|
4015
|
+
"path",
|
|
4016
|
+
"name"
|
|
4017
|
+
].some((k) => typeof s.with?.[k] === "string" && /coverage/i.test(s.with[k])));
|
|
4018
|
+
if (!(consumer.producers.some((p) => p.test(allRunText) || p.test(workflowRunText)) || coverageDataDownload)) findings.push({
|
|
3871
4019
|
severity: "error",
|
|
3872
4020
|
confidence: "high",
|
|
3873
4021
|
findingType: "deterministic-defect",
|
|
3874
4022
|
file: ctx.path,
|
|
3875
|
-
line:
|
|
4023
|
+
line: findJobConsumerLine(ctx.text, jobName, consumer.re),
|
|
3876
4024
|
column: 1,
|
|
3877
4025
|
message: `Job \`${jobName}\` consumes a ${consumer.label} that no step generates.`,
|
|
3878
4026
|
why: "The gate reads a report that is never produced — it passes on empty/stale data while appearing to verify something.",
|
|
@@ -3884,11 +4032,27 @@ const reportNeverGenerated = defineRule({
|
|
|
3884
4032
|
return findings;
|
|
3885
4033
|
}
|
|
3886
4034
|
});
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
4035
|
+
/**
|
|
4036
|
+
* Line of the consumer signal belonging to THIS job.
|
|
4037
|
+
*
|
|
4038
|
+
* detectorRevision 2: `findLine` searched the whole file and always
|
|
4039
|
+
* returned the FIRST consumer occurrence — every consuming job in a
|
|
4040
|
+
* multi-job workflow reported the same line, so distinct findings
|
|
4041
|
+
* collapsed into one verdict position. Anchoring at the job's own
|
|
4042
|
+
* declaration keeps each job's finding on its own line.
|
|
4043
|
+
*/
|
|
4044
|
+
function findJobConsumerLine(text, jobName, consumerRe) {
|
|
4045
|
+
const declMatch = new RegExp(`^\\s{2,6}${jobName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:`, "m").exec(text);
|
|
4046
|
+
const re = new RegExp(consumerRe.source, "gi");
|
|
4047
|
+
re.lastIndex = declMatch ? declMatch.index : 0;
|
|
4048
|
+
const anchored = re.exec(text);
|
|
4049
|
+
if (anchored) return lineOfIndex(text, anchored.index);
|
|
4050
|
+
const anywhere = new RegExp(consumerRe.source, "i").exec(text);
|
|
4051
|
+
return anywhere ? lineOfIndex(text, anywhere.index) : 1;
|
|
4052
|
+
}
|
|
4053
|
+
function lineOfIndex(text, index) {
|
|
3890
4054
|
let line = 1;
|
|
3891
|
-
for (let i = 0; i <
|
|
4055
|
+
for (let i = 0; i < index; i++) if (text[i] === "\n") line++;
|
|
3892
4056
|
return line;
|
|
3893
4057
|
}
|
|
3894
4058
|
//#endregion
|
|
@@ -3901,6 +4065,18 @@ function findLine$2(text, re) {
|
|
|
3901
4065
|
* failure-tolerant pattern can flip a red job green — the classic
|
|
3902
4066
|
* false-green trick.
|
|
3903
4067
|
*/
|
|
4068
|
+
/**
|
|
4069
|
+
* Failure-enforcement shapes that legitimately follow tolerated steps: a
|
|
4070
|
+
* later `if: always()` step that checks the recorded status and exits
|
|
4071
|
+
* non-zero on failure. Adjudication (hashicorp/vault build.yml:889,
|
|
4072
|
+
* ci.yml:492, test-go.yml:992 — 2026-09-02) proved the rev-1 detector
|
|
4073
|
+
* blind to this canonical honest pattern: the tolerated steps' outcomes
|
|
4074
|
+
* ARE enforced after them, so nothing is masked.
|
|
4075
|
+
*/
|
|
4076
|
+
const ENFORCED_FAILURE_LATER = /(?:result|status)[^\n]*!=\s*['"]?success['"]?|exit\s+1\b|FAILURE|has\s*failed/i;
|
|
4077
|
+
function laterStepEnforcesFailure(steps, fromIndex) {
|
|
4078
|
+
return steps.slice(fromIndex + 1).some((s) => typeof s?.if === "string" && /always\s*\(\)/.test(s.if) && /!=\s*['"]?success['"]?|==\s*['"]?failure['"]?|result/i.test(s.if) && typeof s?.run === "string" && ENFORCED_FAILURE_LATER.test(s.run));
|
|
4079
|
+
}
|
|
3904
4080
|
const alwaysSuccessStep = defineRule({
|
|
3905
4081
|
id: "QA-CI-008",
|
|
3906
4082
|
category: "QA-CI",
|
|
@@ -3917,6 +4093,7 @@ const alwaysSuccessStep = defineRule({
|
|
|
3917
4093
|
detectionStrategy: "LEXICAL",
|
|
3918
4094
|
introduced: "0.1.0",
|
|
3919
4095
|
tier: "quarantine",
|
|
4096
|
+
detectorRevision: 2,
|
|
3920
4097
|
run(ctx) {
|
|
3921
4098
|
const findings = [];
|
|
3922
4099
|
const doc = ctx.ast;
|
|
@@ -3926,9 +4103,11 @@ const alwaysSuccessStep = defineRule({
|
|
|
3926
4103
|
if (steps.length < 2) continue;
|
|
3927
4104
|
const last = steps[steps.length - 1];
|
|
3928
4105
|
if (!last?.run) continue;
|
|
3929
|
-
const suspicious = /^\s*exit\s+0\s*$/m.test(last.run) || /^\s*(?:echo|printf)\b[^&|;]*$/.test(last.run.trim()) || /^\s*true\s*$/.test(last.run.trim());
|
|
3930
|
-
const
|
|
3931
|
-
|
|
4106
|
+
const suspicious = !/exit\s+1\b|!=\s*['"]?success['"]?|==\s*['"]?failure['"]?/.test(last.run) && (/^\s*exit\s+0\s*$/m.test(last.run) || /^\s*(?:echo|printf)\b[^&|;]*$/.test(last.run.trim()) || /^\s*true\s*$/.test(last.run.trim()));
|
|
4107
|
+
const earlierTolerantGate = steps.slice(0, -1).some((s) => (s?.["continue-on-error"] === true || /\|\|\s*true\b/.test(s?.run ?? "")) && (looksLikeVerificationGate(s?.run ?? "") || s?.uses !== void 0 && /playwright|cypress|codecov\/codecov-action/i.test(s.uses)));
|
|
4108
|
+
const toleratedIdx = steps.findIndex((s) => (s?.["continue-on-error"] === true || /\|\|\s*true\b/.test(s?.run ?? "")) && (looksLikeVerificationGate(s?.run ?? "") || s?.uses !== void 0 && /playwright|cypress|codecov\/codecov-action/i.test(s.uses)));
|
|
4109
|
+
if (toleratedIdx !== -1 && laterStepEnforcesFailure(steps, toleratedIdx)) continue;
|
|
4110
|
+
if (suspicious && earlierTolerantGate) findings.push({
|
|
3932
4111
|
severity: "error",
|
|
3933
4112
|
confidence: "high",
|
|
3934
4113
|
findingType: "deterministic-defect",
|
|
@@ -3981,6 +4160,8 @@ const exitCodeNotPropagated = defineRule({
|
|
|
3981
4160
|
detectionStrategy: "FRAMEWORK",
|
|
3982
4161
|
detectionNotes: "regex pattern on parsed workflow AST",
|
|
3983
4162
|
introduced: "0.4.0",
|
|
4163
|
+
detectorRevision: 2,
|
|
4164
|
+
tier: "extended",
|
|
3984
4165
|
run(ctx) {
|
|
3985
4166
|
const findings = [];
|
|
3986
4167
|
const doc = ctx.ast;
|
|
@@ -4008,11 +4189,13 @@ const exitCodeNotPropagated = defineRule({
|
|
|
4008
4189
|
});
|
|
4009
4190
|
}
|
|
4010
4191
|
if (/set\s+(?:-[A-Za-df-z]*e[A-Za-z]*|-o\s+errexit)\b/.test(run)) continue;
|
|
4192
|
+
const strippedRun = stripQuoted(run);
|
|
4011
4193
|
const seqRe = new RegExp(`(?:${TEST_CMD$1.source})[^\\n;]*;\\s*[^\\n]+`, "g");
|
|
4012
4194
|
let sm;
|
|
4013
|
-
while ((sm = seqRe.exec(
|
|
4195
|
+
while ((sm = seqRe.exec(strippedRun)) !== null) {
|
|
4014
4196
|
const seg = sm[0];
|
|
4015
4197
|
if (/&&|\|\|/.test(seg)) continue;
|
|
4198
|
+
if (/^\s*(?:do|then|else|fi|done|elif|esac)\b|^\s*\}/.test(seg.slice(seg.indexOf(";") + 1))) continue;
|
|
4016
4199
|
const afterSemi = seg.slice(seg.indexOf(";") + 1);
|
|
4017
4200
|
if (TEST_CMD$1.test(afterSemi)) continue;
|
|
4018
4201
|
findings.push({
|
|
@@ -4032,6 +4215,17 @@ const exitCodeNotPropagated = defineRule({
|
|
|
4032
4215
|
return findings;
|
|
4033
4216
|
}
|
|
4034
4217
|
});
|
|
4218
|
+
/**
|
|
4219
|
+
* Replaces the CONTENT of double- and single-quoted segments with spaces
|
|
4220
|
+
* (length-preserving) so shell separators inside strings — a JS test file
|
|
4221
|
+
* echoed through `tee`, e.g. `echo "it('x'); expect(y)" | tee t.js` — can
|
|
4222
|
+
* never anchor a `;`-sequence finding. Quotes are shell-sensitive; this is
|
|
4223
|
+
* deliberately conservative: only quote-delimited, same-line segments are
|
|
4224
|
+
* stripped.
|
|
4225
|
+
*/
|
|
4226
|
+
function stripQuoted(text) {
|
|
4227
|
+
return text.replace(/"[^"\n]*"|'[^'\n]*'/g, (m) => " ".repeat(m.length));
|
|
4228
|
+
}
|
|
4035
4229
|
function findLine$1(text, needle) {
|
|
4036
4230
|
const idx = text.indexOf(needle);
|
|
4037
4231
|
if (idx === -1) return 1;
|
|
@@ -4057,7 +4251,19 @@ const TEST_CMD = /\b(?:npm|yarn|pnpm)\s+(?:run\s+)?test\b|\b(?:jest|vitest|pytes
|
|
|
4057
4251
|
* `if:` conditions that skip the job on pull requests.
|
|
4058
4252
|
* NOTE: `!=` only — `github.event_name == 'pull_request'` is the OPPOSITE
|
|
4059
4253
|
* (run ONLY on PRs) and must never match here.
|
|
4060
|
-
|
|
4254
|
+
*
|
|
4255
|
+
* detectorRevision 2 (M2, 2026-09-04): a condition that CONTAINS a
|
|
4256
|
+
* push-match alternative can still RUN on PRs via `||` branches —
|
|
4257
|
+
* `github.event_name == 'push' || ... || (pull_request && !draft)` runs on
|
|
4258
|
+
* non-draft PRs (adjudicated FPs: nocodb jest-unit-test.yml, grafana
|
|
4259
|
+
* pr-frontend-unit-tests.yml, streamlit publish jobs). The condition is a
|
|
4260
|
+
* skip only when EVERY PR-shaped alternative excludes PRs: i.e. the
|
|
4261
|
+
* condition contains a PR-inclusive branch (== pull_request, !draft,
|
|
4262
|
+
* labeled trigger) or consists solely of push/schedule/dispatch
|
|
4263
|
+
* alternatives — matched below only when no PR-inclusive alternative
|
|
4264
|
+
* exists anywhere in the condition.
|
|
4265
|
+
*/
|
|
4266
|
+
const PR_INCLUSIVE_RE = /github\.event_name\s*==\s*['"]?pull_request\b|pull_request[^\n]*!\s*=\s*['"]?draft|!\s*github\.event\.pull_request|github\.event\.label\.name|!=\s*['"]?draft['"]?|head\.repo\.fork\s*==\s*['"]?false['"]?/;
|
|
4061
4267
|
const SKIP_ON_PR = /github\.event_name\s*!=\s*['"]?pull_request|github\.event_name\s*==\s*['"]?(?:push|schedule|workflow_dispatch)\b|!\s*github\.event\b|github\.ref\s*==\s*['"]?refs\/heads\/(?:main|master)\b/;
|
|
4062
4268
|
const nonBlockingTestJob = defineRule({
|
|
4063
4269
|
id: "QA-CI-010",
|
|
@@ -4076,6 +4282,7 @@ const nonBlockingTestJob = defineRule({
|
|
|
4076
4282
|
detectionNotes: "regex heuristic on parsed workflow AST",
|
|
4077
4283
|
introduced: "0.4.0",
|
|
4078
4284
|
tier: "quarantine",
|
|
4285
|
+
detectorRevision: 2,
|
|
4079
4286
|
run(ctx) {
|
|
4080
4287
|
const findings = [];
|
|
4081
4288
|
const doc = ctx.ast;
|
|
@@ -4083,6 +4290,7 @@ const nonBlockingTestJob = defineRule({
|
|
|
4083
4290
|
for (const [jobName, job] of Object.entries(doc.jobs)) {
|
|
4084
4291
|
if (!(job?.steps ?? []).some((s) => TEST_CMD.test(s?.run ?? ""))) continue;
|
|
4085
4292
|
const cond = typeof job?.if === "string" ? job.if : "";
|
|
4293
|
+
if (cond && PR_INCLUSIVE_RE.test(cond)) continue;
|
|
4086
4294
|
if (cond && SKIP_ON_PR.test(cond)) findings.push({
|
|
4087
4295
|
severity: "error",
|
|
4088
4296
|
confidence: "medium",
|
|
@@ -8589,46 +8797,53 @@ function getRule(id) {
|
|
|
8589
8797
|
//#region src/rules/measured-fp.generated.ts
|
|
8590
8798
|
const MEASURED_FP = {
|
|
8591
8799
|
"QA-CI-001": {
|
|
8592
|
-
fpRate: .
|
|
8800
|
+
fpRate: .105,
|
|
8593
8801
|
n: 19,
|
|
8594
8802
|
detectorRevision: 2,
|
|
8595
|
-
ciLow: .
|
|
8596
|
-
ciHigh: .
|
|
8803
|
+
ciLow: .0294,
|
|
8804
|
+
ciHigh: .3139
|
|
8597
8805
|
},
|
|
8598
8806
|
"QA-CI-002": {
|
|
8599
|
-
fpRate: .
|
|
8600
|
-
n:
|
|
8601
|
-
detectorRevision:
|
|
8602
|
-
ciLow: .
|
|
8603
|
-
ciHigh: .
|
|
8807
|
+
fpRate: .111,
|
|
8808
|
+
n: 18,
|
|
8809
|
+
detectorRevision: 2,
|
|
8810
|
+
ciLow: .031,
|
|
8811
|
+
ciHigh: .328
|
|
8604
8812
|
},
|
|
8605
8813
|
"QA-CI-005": {
|
|
8606
|
-
fpRate: .
|
|
8607
|
-
n:
|
|
8608
|
-
detectorRevision:
|
|
8609
|
-
ciLow: .
|
|
8610
|
-
ciHigh: .
|
|
8814
|
+
fpRate: .077,
|
|
8815
|
+
n: 13,
|
|
8816
|
+
detectorRevision: 2,
|
|
8817
|
+
ciLow: .0137,
|
|
8818
|
+
ciHigh: .3331
|
|
8611
8819
|
},
|
|
8612
8820
|
"QA-CI-007": {
|
|
8613
|
-
fpRate:
|
|
8821
|
+
fpRate: 0,
|
|
8614
8822
|
n: 11,
|
|
8615
|
-
detectorRevision:
|
|
8616
|
-
ciLow:
|
|
8617
|
-
ciHigh: .
|
|
8823
|
+
detectorRevision: 2,
|
|
8824
|
+
ciLow: 0,
|
|
8825
|
+
ciHigh: .2588
|
|
8618
8826
|
},
|
|
8619
8827
|
"QA-CI-008": {
|
|
8620
|
-
fpRate: .
|
|
8621
|
-
n:
|
|
8622
|
-
detectorRevision:
|
|
8623
|
-
ciLow: .
|
|
8624
|
-
ciHigh: .
|
|
8828
|
+
fpRate: .1,
|
|
8829
|
+
n: 10,
|
|
8830
|
+
detectorRevision: 2,
|
|
8831
|
+
ciLow: .0179,
|
|
8832
|
+
ciHigh: .4042
|
|
8833
|
+
},
|
|
8834
|
+
"QA-CI-009": {
|
|
8835
|
+
fpRate: 0,
|
|
8836
|
+
n: 10,
|
|
8837
|
+
detectorRevision: 2,
|
|
8838
|
+
ciLow: 0,
|
|
8839
|
+
ciHigh: .2775
|
|
8625
8840
|
},
|
|
8626
8841
|
"QA-CI-010": {
|
|
8627
|
-
fpRate: .
|
|
8842
|
+
fpRate: .1,
|
|
8628
8843
|
n: 10,
|
|
8629
|
-
detectorRevision:
|
|
8630
|
-
ciLow: .
|
|
8631
|
-
ciHigh: .
|
|
8844
|
+
detectorRevision: 2,
|
|
8845
|
+
ciLow: .0179,
|
|
8846
|
+
ciHigh: .4042
|
|
8632
8847
|
},
|
|
8633
8848
|
"QA-CS-101": {
|
|
8634
8849
|
fpRate: 0,
|
|
@@ -8659,18 +8874,18 @@ const MEASURED_FP = {
|
|
|
8659
8874
|
ciHigh: .495
|
|
8660
8875
|
},
|
|
8661
8876
|
"QA-CS-108": {
|
|
8662
|
-
fpRate:
|
|
8663
|
-
n:
|
|
8877
|
+
fpRate: .87,
|
|
8878
|
+
n: 23,
|
|
8664
8879
|
detectorRevision: 1,
|
|
8665
|
-
ciLow: .
|
|
8666
|
-
ciHigh:
|
|
8880
|
+
ciLow: .6787,
|
|
8881
|
+
ciHigh: .9546
|
|
8667
8882
|
},
|
|
8668
8883
|
"QA-CS-110": {
|
|
8669
|
-
fpRate:
|
|
8670
|
-
n:
|
|
8884
|
+
fpRate: .769,
|
|
8885
|
+
n: 26,
|
|
8671
8886
|
detectorRevision: 1,
|
|
8672
|
-
ciLow: .
|
|
8673
|
-
ciHigh:
|
|
8887
|
+
ciLow: .5795,
|
|
8888
|
+
ciHigh: .8897
|
|
8674
8889
|
},
|
|
8675
8890
|
"QA-CS-111": {
|
|
8676
8891
|
fpRate: 1,
|
|
@@ -8688,17 +8903,17 @@ const MEASURED_FP = {
|
|
|
8688
8903
|
},
|
|
8689
8904
|
"QA-JV-101": {
|
|
8690
8905
|
fpRate: 0,
|
|
8691
|
-
n:
|
|
8906
|
+
n: 23,
|
|
8692
8907
|
detectorRevision: 1,
|
|
8693
8908
|
ciLow: 0,
|
|
8694
|
-
ciHigh: .
|
|
8909
|
+
ciHigh: .1431
|
|
8695
8910
|
},
|
|
8696
8911
|
"QA-JV-102": {
|
|
8697
|
-
fpRate: .
|
|
8698
|
-
n:
|
|
8912
|
+
fpRate: .261,
|
|
8913
|
+
n: 23,
|
|
8699
8914
|
detectorRevision: 1,
|
|
8700
|
-
ciLow: .
|
|
8701
|
-
ciHigh: .
|
|
8915
|
+
ciLow: .1255,
|
|
8916
|
+
ciHigh: .4647
|
|
8702
8917
|
},
|
|
8703
8918
|
"QA-JV-103": {
|
|
8704
8919
|
fpRate: .259,
|
|
@@ -8722,25 +8937,25 @@ const MEASURED_FP = {
|
|
|
8722
8937
|
ciHigh: .301
|
|
8723
8938
|
},
|
|
8724
8939
|
"QA-JV-108": {
|
|
8725
|
-
fpRate:
|
|
8726
|
-
n:
|
|
8940
|
+
fpRate: .87,
|
|
8941
|
+
n: 23,
|
|
8727
8942
|
detectorRevision: 1,
|
|
8728
|
-
ciLow: .
|
|
8729
|
-
ciHigh:
|
|
8943
|
+
ciLow: .6787,
|
|
8944
|
+
ciHigh: .9546
|
|
8730
8945
|
},
|
|
8731
8946
|
"QA-JV-109": {
|
|
8732
8947
|
fpRate: 0,
|
|
8733
|
-
n:
|
|
8948
|
+
n: 18,
|
|
8734
8949
|
detectorRevision: 1,
|
|
8735
8950
|
ciLow: 0,
|
|
8736
|
-
ciHigh: .
|
|
8951
|
+
ciHigh: .1759
|
|
8737
8952
|
},
|
|
8738
8953
|
"QA-JV-110": {
|
|
8739
|
-
fpRate:
|
|
8740
|
-
n:
|
|
8954
|
+
fpRate: .833,
|
|
8955
|
+
n: 24,
|
|
8741
8956
|
detectorRevision: 1,
|
|
8742
|
-
ciLow: .
|
|
8743
|
-
ciHigh:
|
|
8957
|
+
ciLow: .6415,
|
|
8958
|
+
ciHigh: .9332
|
|
8744
8959
|
},
|
|
8745
8960
|
"QA-JV-111": {
|
|
8746
8961
|
fpRate: 1,
|
|
@@ -8764,11 +8979,11 @@ const MEASURED_FP = {
|
|
|
8764
8979
|
ciHigh: .4042
|
|
8765
8980
|
},
|
|
8766
8981
|
"QA-PW-004": {
|
|
8767
|
-
fpRate: .
|
|
8768
|
-
n:
|
|
8982
|
+
fpRate: .429,
|
|
8983
|
+
n: 14,
|
|
8769
8984
|
detectorRevision: 1,
|
|
8770
|
-
ciLow: .
|
|
8771
|
-
ciHigh: .
|
|
8985
|
+
ciLow: .2138,
|
|
8986
|
+
ciHigh: .6741
|
|
8772
8987
|
},
|
|
8773
8988
|
"QA-PW-005": {
|
|
8774
8989
|
fpRate: 1,
|
|
@@ -8785,11 +9000,11 @@ const MEASURED_FP = {
|
|
|
8785
9000
|
ciHigh: .1611
|
|
8786
9001
|
},
|
|
8787
9002
|
"QA-PW-103": {
|
|
8788
|
-
fpRate:
|
|
8789
|
-
n:
|
|
9003
|
+
fpRate: .29,
|
|
9004
|
+
n: 69,
|
|
8790
9005
|
detectorRevision: 1,
|
|
8791
|
-
ciLow: .
|
|
8792
|
-
ciHigh:
|
|
9006
|
+
ciLow: .1962,
|
|
9007
|
+
ciHigh: .4057
|
|
8793
9008
|
},
|
|
8794
9009
|
"QA-PW-104": {
|
|
8795
9010
|
fpRate: 0,
|
|
@@ -8806,11 +9021,11 @@ const MEASURED_FP = {
|
|
|
8806
9021
|
ciHigh: 1
|
|
8807
9022
|
},
|
|
8808
9023
|
"QA-PW-107": {
|
|
8809
|
-
fpRate:
|
|
8810
|
-
n:
|
|
9024
|
+
fpRate: .952,
|
|
9025
|
+
n: 21,
|
|
8811
9026
|
detectorRevision: 1,
|
|
8812
|
-
ciLow: .
|
|
8813
|
-
ciHigh:
|
|
9027
|
+
ciLow: .7733,
|
|
9028
|
+
ciHigh: .9915
|
|
8814
9029
|
},
|
|
8815
9030
|
"QA-PW-108": {
|
|
8816
9031
|
fpRate: 1,
|
|
@@ -8849,10 +9064,10 @@ const MEASURED_FP = {
|
|
|
8849
9064
|
},
|
|
8850
9065
|
"QA-PW-117": {
|
|
8851
9066
|
fpRate: 0,
|
|
8852
|
-
n:
|
|
9067
|
+
n: 24,
|
|
8853
9068
|
detectorRevision: 1,
|
|
8854
9069
|
ciLow: 0,
|
|
8855
|
-
ciHigh: .
|
|
9070
|
+
ciHigh: .138
|
|
8856
9071
|
},
|
|
8857
9072
|
"QA-PW-118": {
|
|
8858
9073
|
fpRate: 1,
|
|
@@ -8883,11 +9098,11 @@ const MEASURED_FP = {
|
|
|
8883
9098
|
ciHigh: .2425
|
|
8884
9099
|
},
|
|
8885
9100
|
"QA-PW-122": {
|
|
8886
|
-
fpRate: .
|
|
8887
|
-
n:
|
|
9101
|
+
fpRate: .063,
|
|
9102
|
+
n: 80,
|
|
8888
9103
|
detectorRevision: 1,
|
|
8889
|
-
ciLow: .
|
|
8890
|
-
ciHigh: .
|
|
9104
|
+
ciLow: .027,
|
|
9105
|
+
ciHigh: .1381
|
|
8891
9106
|
},
|
|
8892
9107
|
"QA-PW-123": {
|
|
8893
9108
|
fpRate: .455,
|
|
@@ -8904,11 +9119,11 @@ const MEASURED_FP = {
|
|
|
8904
9119
|
ciHigh: .2775
|
|
8905
9120
|
},
|
|
8906
9121
|
"QA-PW-141": {
|
|
8907
|
-
fpRate: .
|
|
8908
|
-
n:
|
|
9122
|
+
fpRate: .091,
|
|
9123
|
+
n: 33,
|
|
8909
9124
|
detectorRevision: 1,
|
|
8910
|
-
ciLow: .
|
|
8911
|
-
ciHigh: .
|
|
9125
|
+
ciLow: .0314,
|
|
9126
|
+
ciHigh: .2357
|
|
8912
9127
|
},
|
|
8913
9128
|
"QA-PW-142": {
|
|
8914
9129
|
fpRate: .182,
|
|
@@ -8918,25 +9133,25 @@ const MEASURED_FP = {
|
|
|
8918
9133
|
ciHigh: .477
|
|
8919
9134
|
},
|
|
8920
9135
|
"QA-PW-143": {
|
|
8921
|
-
fpRate: .
|
|
8922
|
-
n:
|
|
9136
|
+
fpRate: .063,
|
|
9137
|
+
n: 80,
|
|
8923
9138
|
detectorRevision: 1,
|
|
8924
|
-
ciLow: .
|
|
8925
|
-
ciHigh: .
|
|
9139
|
+
ciLow: .027,
|
|
9140
|
+
ciHigh: .1381
|
|
8926
9141
|
},
|
|
8927
9142
|
"QA-PW-144": {
|
|
8928
|
-
fpRate: .
|
|
8929
|
-
n:
|
|
9143
|
+
fpRate: .143,
|
|
9144
|
+
n: 21,
|
|
8930
9145
|
detectorRevision: 1,
|
|
8931
|
-
ciLow: .
|
|
8932
|
-
ciHigh: .
|
|
9146
|
+
ciLow: .0498,
|
|
9147
|
+
ciHigh: .3464
|
|
8933
9148
|
},
|
|
8934
9149
|
"QA-PW-145": {
|
|
8935
|
-
fpRate:
|
|
8936
|
-
n:
|
|
9150
|
+
fpRate: .5,
|
|
9151
|
+
n: 40,
|
|
8937
9152
|
detectorRevision: 1,
|
|
8938
|
-
ciLow: .
|
|
8939
|
-
ciHigh:
|
|
9153
|
+
ciLow: .352,
|
|
9154
|
+
ciHigh: .648
|
|
8940
9155
|
},
|
|
8941
9156
|
"QA-PW-146": {
|
|
8942
9157
|
fpRate: .118,
|
|
@@ -8960,25 +9175,25 @@ const MEASURED_FP = {
|
|
|
8960
9175
|
ciHigh: .2099
|
|
8961
9176
|
},
|
|
8962
9177
|
"QA-PY-003": {
|
|
8963
|
-
fpRate: .
|
|
8964
|
-
n:
|
|
9178
|
+
fpRate: .467,
|
|
9179
|
+
n: 30,
|
|
8965
9180
|
detectorRevision: 3,
|
|
8966
|
-
ciLow: .
|
|
8967
|
-
ciHigh: .
|
|
9181
|
+
ciLow: .3023,
|
|
9182
|
+
ciHigh: .6386
|
|
8968
9183
|
},
|
|
8969
9184
|
"QA-PY-004": {
|
|
8970
|
-
fpRate: .
|
|
8971
|
-
n:
|
|
9185
|
+
fpRate: .533,
|
|
9186
|
+
n: 30,
|
|
8972
9187
|
detectorRevision: 3,
|
|
8973
|
-
ciLow: .
|
|
8974
|
-
ciHigh: .
|
|
9188
|
+
ciLow: .3614,
|
|
9189
|
+
ciHigh: .6977
|
|
8975
9190
|
},
|
|
8976
9191
|
"QA-PY-005": {
|
|
8977
|
-
fpRate: .
|
|
8978
|
-
n:
|
|
9192
|
+
fpRate: .13,
|
|
9193
|
+
n: 23,
|
|
8979
9194
|
detectorRevision: 1,
|
|
8980
|
-
ciLow: .
|
|
8981
|
-
ciHigh: .
|
|
9195
|
+
ciLow: .0454,
|
|
9196
|
+
ciHigh: .3213
|
|
8982
9197
|
},
|
|
8983
9198
|
"QA-PY-006": {
|
|
8984
9199
|
fpRate: 1,
|
|
@@ -9023,39 +9238,46 @@ const MEASURED_FP = {
|
|
|
9023
9238
|
ciHigh: .4042
|
|
9024
9239
|
},
|
|
9025
9240
|
"QA-PY-012": {
|
|
9026
|
-
fpRate: .
|
|
9027
|
-
n:
|
|
9241
|
+
fpRate: .4,
|
|
9242
|
+
n: 30,
|
|
9028
9243
|
detectorRevision: 1,
|
|
9029
|
-
ciLow: .
|
|
9030
|
-
ciHigh: .
|
|
9244
|
+
ciLow: .2459,
|
|
9245
|
+
ciHigh: .5768
|
|
9031
9246
|
},
|
|
9032
9247
|
"QA-PY-103": {
|
|
9033
|
-
fpRate: .
|
|
9034
|
-
n:
|
|
9248
|
+
fpRate: .08,
|
|
9249
|
+
n: 25,
|
|
9035
9250
|
detectorRevision: 1,
|
|
9036
|
-
ciLow: .
|
|
9037
|
-
ciHigh: .
|
|
9251
|
+
ciLow: .0222,
|
|
9252
|
+
ciHigh: .2497
|
|
9253
|
+
},
|
|
9254
|
+
"QA-PY-105": {
|
|
9255
|
+
fpRate: 0,
|
|
9256
|
+
n: 12,
|
|
9257
|
+
detectorRevision: 2,
|
|
9258
|
+
ciLow: 0,
|
|
9259
|
+
ciHigh: .2425
|
|
9038
9260
|
},
|
|
9039
9261
|
"QA-TEST-001": {
|
|
9040
|
-
fpRate: .
|
|
9041
|
-
n:
|
|
9262
|
+
fpRate: .6,
|
|
9263
|
+
n: 20,
|
|
9042
9264
|
detectorRevision: 1,
|
|
9043
|
-
ciLow: .
|
|
9044
|
-
ciHigh: .
|
|
9265
|
+
ciLow: .3866,
|
|
9266
|
+
ciHigh: .7812
|
|
9045
9267
|
},
|
|
9046
9268
|
"QA-TEST-002": {
|
|
9047
|
-
fpRate: .
|
|
9048
|
-
n:
|
|
9269
|
+
fpRate: .619,
|
|
9270
|
+
n: 21,
|
|
9049
9271
|
detectorRevision: 1,
|
|
9050
|
-
ciLow: .
|
|
9051
|
-
ciHigh: .
|
|
9272
|
+
ciLow: .4088,
|
|
9273
|
+
ciHigh: .7925
|
|
9052
9274
|
},
|
|
9053
9275
|
"QA-TEST-003": {
|
|
9054
|
-
fpRate: .
|
|
9055
|
-
n:
|
|
9276
|
+
fpRate: .218,
|
|
9277
|
+
n: 78,
|
|
9056
9278
|
detectorRevision: 1,
|
|
9057
|
-
ciLow: .
|
|
9058
|
-
ciHigh: .
|
|
9279
|
+
ciLow: .1408,
|
|
9280
|
+
ciHigh: .3216
|
|
9059
9281
|
},
|
|
9060
9282
|
"QA-TEST-004": {
|
|
9061
9283
|
fpRate: .3,
|
|
@@ -9072,11 +9294,11 @@ const MEASURED_FP = {
|
|
|
9072
9294
|
ciHigh: .6462
|
|
9073
9295
|
},
|
|
9074
9296
|
"QA-TEST-010": {
|
|
9075
|
-
fpRate: .
|
|
9076
|
-
n:
|
|
9297
|
+
fpRate: .581,
|
|
9298
|
+
n: 31,
|
|
9077
9299
|
detectorRevision: 1,
|
|
9078
|
-
ciLow: .
|
|
9079
|
-
ciHigh: .
|
|
9300
|
+
ciLow: .4077,
|
|
9301
|
+
ciHigh: .7358
|
|
9080
9302
|
},
|
|
9081
9303
|
"QA-TQUAL-001": {
|
|
9082
9304
|
fpRate: 1,
|
|
@@ -9086,11 +9308,11 @@ const MEASURED_FP = {
|
|
|
9086
9308
|
ciHigh: 1
|
|
9087
9309
|
},
|
|
9088
9310
|
"QA-TQUAL-002": {
|
|
9089
|
-
fpRate: .
|
|
9090
|
-
n:
|
|
9311
|
+
fpRate: .531,
|
|
9312
|
+
n: 32,
|
|
9091
9313
|
detectorRevision: 1,
|
|
9092
|
-
ciLow: .
|
|
9093
|
-
ciHigh: .
|
|
9314
|
+
ciLow: .3645,
|
|
9315
|
+
ciHigh: .6913
|
|
9094
9316
|
},
|
|
9095
9317
|
"QA-TQUAL-009": {
|
|
9096
9318
|
fpRate: .786,
|
|
@@ -9100,11 +9322,11 @@ const MEASURED_FP = {
|
|
|
9100
9322
|
ciHigh: .9243
|
|
9101
9323
|
},
|
|
9102
9324
|
"QA-TQUAL-011": {
|
|
9103
|
-
fpRate: .
|
|
9104
|
-
n:
|
|
9325
|
+
fpRate: .24,
|
|
9326
|
+
n: 25,
|
|
9105
9327
|
detectorRevision: 1,
|
|
9106
|
-
ciLow: .
|
|
9107
|
-
ciHigh: .
|
|
9328
|
+
ciLow: .115,
|
|
9329
|
+
ciHigh: .4343
|
|
9108
9330
|
}
|
|
9109
9331
|
};
|
|
9110
9332
|
//#endregion
|
|
@@ -12251,7 +12473,7 @@ function renderSarif(result, repoRootUri) {
|
|
|
12251
12473
|
tool: { driver: {
|
|
12252
12474
|
name: "Mjölnir",
|
|
12253
12475
|
informationUri: "https://github.com/Sergey-Bar/Mjolnir",
|
|
12254
|
-
version: "0.5.
|
|
12476
|
+
version: "0.5.6",
|
|
12255
12477
|
rules: [...rules.values()].map((r) => {
|
|
12256
12478
|
const meta = RULES.find((x) => x.id === r.id);
|
|
12257
12479
|
return {
|
|
@@ -16565,7 +16787,7 @@ const { runScan, buildUniversalRules, fallbackWorkspace, pathMatchesGlob, isVali
|
|
|
16565
16787
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
16566
16788
|
* `tests/version-consistency.spec.ts` locally.
|
|
16567
16789
|
*/
|
|
16568
|
-
const CLI_VERSION = "0.5.
|
|
16790
|
+
const CLI_VERSION = "0.5.6";
|
|
16569
16791
|
function parseArgs(argv, onError) {
|
|
16570
16792
|
const args = {
|
|
16571
16793
|
target: ".",
|
package/package.json
CHANGED