mjolnir-qa 0.5.33 → 0.5.34
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 +6 -0
- package/dist/cli.d.mts +10 -1
- package/dist/cli.mjs +62 -2
- package/dist/mcp/stdio.mjs +62 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,12 @@ Rule behavior changes (new rules, FP-rate changes against the corpus,
|
|
|
9
9
|
severity changes) are first-class entries here — rule IDs are immutable
|
|
10
10
|
once shipped, so this file is the record of what changed between versions.
|
|
11
11
|
|
|
12
|
+
## [0.5.34] — 2026-09-08
|
|
13
|
+
|
|
14
|
+
### Changes since 0
|
|
15
|
+
|
|
16
|
+
- p2: structural anti-dilution — deduction-mass ceilings close the padding vector (plan 1788853205786 P2, decision 4) (#64)
|
|
17
|
+
|
|
12
18
|
## [0.5.33] — 2026-09-08
|
|
13
19
|
|
|
14
20
|
### Changes since 0.5.32
|
package/dist/cli.d.mts
CHANGED
|
@@ -201,6 +201,15 @@ interface ScanResult {
|
|
|
201
201
|
testDeclarationCount?: number;
|
|
202
202
|
/** Raw deduction total before normalization (Phase 5 — transparency). */
|
|
203
203
|
rawDeductions?: number;
|
|
204
|
+
/**
|
|
205
|
+
* The evidence-discounted deduction mass the P2 anti-dilution ceiling
|
|
206
|
+
* caps against (equals rawDeductions today — E0 charges 0, E1 halves;
|
|
207
|
+
* the ceiling input is deliberately the full discount surface).
|
|
208
|
+
* Additive within schemaVersion 1; present so consumers recompute the
|
|
209
|
+
* ceiling from docs/SCORING.md formula v2 without re-deriving
|
|
210
|
+
* evidence levels.
|
|
211
|
+
*/
|
|
212
|
+
effectiveDeductions?: number;
|
|
204
213
|
/** Number of findings suppressed by active config entries (suppression transparency). */
|
|
205
214
|
suppressionCount?: number;
|
|
206
215
|
/**
|
|
@@ -728,7 +737,7 @@ declare const runScan: typeof runScan$1, buildUniversalRules: typeof buildUniver
|
|
|
728
737
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
729
738
|
* `tests/version-consistency.spec.ts` locally.
|
|
730
739
|
*/
|
|
731
|
-
declare const CLI_VERSION = "0.5.
|
|
740
|
+
declare const CLI_VERSION = "0.5.34";
|
|
732
741
|
/** A usage-error detail: the offending token, when one exists. */
|
|
733
742
|
interface UsageErrorDetail {
|
|
734
743
|
/** The unknown flag or rejected value (e.g. `--nope`, `loud`). */
|
package/dist/cli.mjs
CHANGED
|
@@ -10074,6 +10074,58 @@ function computeDimensions(findings) {
|
|
|
10074
10074
|
for (const dim of byCategory.values()) dim.score = Math.max(0, 100 - deductions.get(dim.category));
|
|
10075
10075
|
return [...byCategory.values()].sort((a, b) => a.category.localeCompare(b.category));
|
|
10076
10076
|
}
|
|
10077
|
+
/**
|
|
10078
|
+
* Deduction-mass ceilings (product-gap-remediation master plan P2,
|
|
10079
|
+
* plan 1788853205786 — structural anti-dilution, decision 4).
|
|
10080
|
+
*
|
|
10081
|
+
* The Goodhart vector the density formula permits: fixed deduction mass
|
|
10082
|
+
* ÷ padded denominator → score → 99. A repo with 80 warning-points of
|
|
10083
|
+
* real findings and 10,000 test declarations reads 99+ ("one minor
|
|
10084
|
+
* issue") because the RATE is tiny — the padding is free. Density stays
|
|
10085
|
+
* as the legitimate differentiator for small masses (a lone warning in
|
|
10086
|
+
* a 10k suite must still read ≥ 95), but it can no longer dilute a
|
|
10087
|
+
* large mass: each absolute deduction-mass band caps the score. Padding
|
|
10088
|
+
* cannot move a ceiling — the vector is structurally closed. 80
|
|
10089
|
+
* warning-pts read ≤ 75 in ANY suite size.
|
|
10090
|
+
*
|
|
10091
|
+
* The error-severity floor (95) is the ≥ 8 band seen from the other
|
|
10092
|
+
* side: 1 error ≥ 8 pts, so it is subsumed and left standing as a named
|
|
10093
|
+
* special case for prose continuity. The > 0 band is the honesty guard
|
|
10094
|
+
* (clampWithFindings) — subsumed as well.
|
|
10095
|
+
*
|
|
10096
|
+
* Calibration (P2.4): these boundaries were chosen to hold the three
|
|
10097
|
+
* known data points (self 100, golden 49 via suiteVoided, demo 67) and
|
|
10098
|
+
* are then FROZEN with a changelog entry. Never tune them to flatter a
|
|
10099
|
+
* repo (docs/SCORING.md Correction section is the precedent).
|
|
10100
|
+
*/
|
|
10101
|
+
const DEDUCTION_MASS_CEILINGS = [
|
|
10102
|
+
{
|
|
10103
|
+
minMass: 160,
|
|
10104
|
+
ceiling: 65
|
|
10105
|
+
},
|
|
10106
|
+
{
|
|
10107
|
+
minMass: 80,
|
|
10108
|
+
ceiling: 75
|
|
10109
|
+
},
|
|
10110
|
+
{
|
|
10111
|
+
minMass: 40,
|
|
10112
|
+
ceiling: 85
|
|
10113
|
+
},
|
|
10114
|
+
{
|
|
10115
|
+
minMass: 8,
|
|
10116
|
+
ceiling: 95
|
|
10117
|
+
}
|
|
10118
|
+
];
|
|
10119
|
+
/**
|
|
10120
|
+
* The score ceiling for a given evidence-discounted deduction mass.
|
|
10121
|
+
* 0 → no ceiling (the 100 honesty guard lives in clampWithFindings);
|
|
10122
|
+
* below the smallest band → no ceiling beyond the named guards.
|
|
10123
|
+
* Exported for the JSON contract (effectiveDeductions) and the tests.
|
|
10124
|
+
*/
|
|
10125
|
+
function massCeiling(totalDeduction) {
|
|
10126
|
+
for (const band of DEDUCTION_MASS_CEILINGS) if (totalDeduction >= band.minMass) return band.ceiling;
|
|
10127
|
+
return null;
|
|
10128
|
+
}
|
|
10077
10129
|
function computeTotal(dimensions, findings, exposure) {
|
|
10078
10130
|
if (findings.length === 0) return 100;
|
|
10079
10131
|
let totalDeduction = 0;
|
|
@@ -10086,6 +10138,8 @@ function computeTotal(dimensions, findings, exposure) {
|
|
|
10086
10138
|
const rate = totalDeduction / (declarations + 1);
|
|
10087
10139
|
score = clampWithFindings(100 - Math.min(100, rate * 5), totalDeduction);
|
|
10088
10140
|
} else score = clampWithFindings(100 - totalDeduction, totalDeduction);
|
|
10141
|
+
const ceiling = massCeiling(totalDeduction);
|
|
10142
|
+
if (ceiling !== null && score > ceiling) score = ceiling;
|
|
10089
10143
|
if (findings.some((f) => f.severity === "error" && deductionFor(f) > 0) && score > 95) score = 95;
|
|
10090
10144
|
return suiteVoided ? Math.min(score, 49) : score;
|
|
10091
10145
|
}
|
|
@@ -12322,6 +12376,7 @@ async function runScan$1(args, hooks = {}) {
|
|
|
12322
12376
|
});
|
|
12323
12377
|
const dimensions = computeDimensions(findings);
|
|
12324
12378
|
const rawDeductions = findings.reduce((sum, f) => sum + deductionFor(f), 0);
|
|
12379
|
+
const effectiveDeductions = rawDeductions;
|
|
12325
12380
|
const total = computeTotal(dimensions, findings, {
|
|
12326
12381
|
testDeclarations: testDeclarationCount,
|
|
12327
12382
|
testFileCount,
|
|
@@ -12346,6 +12401,7 @@ async function runScan$1(args, hooks = {}) {
|
|
|
12346
12401
|
testFileCount,
|
|
12347
12402
|
testDeclarationCount,
|
|
12348
12403
|
rawDeductions,
|
|
12404
|
+
effectiveDeductions,
|
|
12349
12405
|
suppressionCount,
|
|
12350
12406
|
...pluginsLoaded.length > 0 ? { plugins: pluginsLoaded } : {},
|
|
12351
12407
|
agenticProfile: computeAgenticProfile(fileProvenance, findings),
|
|
@@ -12900,6 +12956,10 @@ function appendScoreSection(lines, result, p, width, ascii) {
|
|
|
12900
12956
|
lines.push(` ${scoreGauge(result.score, p, gaugeWidth, ascii)}`);
|
|
12901
12957
|
lines.push(` ${p.dim(headlineFor(state, result.findings.length))}`);
|
|
12902
12958
|
if (result.rawDeductions !== void 0 && result.testDeclarationCount) lines.push(` ${p.dim(`(${result.rawDeductions} raw pts / ${result.testDeclarationCount} test declarations — normalized)`)}`);
|
|
12959
|
+
if (result.effectiveDeductions !== void 0 && result.score !== null) {
|
|
12960
|
+
const ceiling = massCeiling(result.effectiveDeductions);
|
|
12961
|
+
if (ceiling !== null && result.score <= ceiling && result.rawDeductions !== void 0) lines.push(` ${p.dim(`(capped: deduction mass ${result.effectiveDeductions} pts — absolute ceiling ${ceiling})`)}`);
|
|
12962
|
+
}
|
|
12903
12963
|
if (result.suppressionCount && result.suppressionCount > 0) lines.push(` ${p.dim(`(${result.suppressionCount} finding(s) suppressed by config)`)}`);
|
|
12904
12964
|
lines.push("");
|
|
12905
12965
|
}
|
|
@@ -13290,7 +13350,7 @@ function renderSarif(result, repoRootUri) {
|
|
|
13290
13350
|
tool: { driver: {
|
|
13291
13351
|
name: "Mjölnir",
|
|
13292
13352
|
informationUri: "https://github.com/Sergey-Bar/Mjolnir",
|
|
13293
|
-
version: "0.5.
|
|
13353
|
+
version: "0.5.34",
|
|
13294
13354
|
rules: [...rules.values()].map((r) => {
|
|
13295
13355
|
const meta = RULES.find((x) => x.id === r.id);
|
|
13296
13356
|
return {
|
|
@@ -18788,7 +18848,7 @@ const { runScan, buildUniversalRules, fallbackWorkspace, pathMatchesGlob, isVali
|
|
|
18788
18848
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
18789
18849
|
* `tests/version-consistency.spec.ts` locally.
|
|
18790
18850
|
*/
|
|
18791
|
-
const CLI_VERSION = "0.5.
|
|
18851
|
+
const CLI_VERSION = "0.5.34";
|
|
18792
18852
|
function parseArgs(argv, onError) {
|
|
18793
18853
|
const args = {
|
|
18794
18854
|
target: ".",
|
package/dist/mcp/stdio.mjs
CHANGED
|
@@ -10073,6 +10073,58 @@ function computeDimensions(findings) {
|
|
|
10073
10073
|
for (const dim of byCategory.values()) dim.score = Math.max(0, 100 - deductions.get(dim.category));
|
|
10074
10074
|
return [...byCategory.values()].sort((a, b) => a.category.localeCompare(b.category));
|
|
10075
10075
|
}
|
|
10076
|
+
/**
|
|
10077
|
+
* Deduction-mass ceilings (product-gap-remediation master plan P2,
|
|
10078
|
+
* plan 1788853205786 — structural anti-dilution, decision 4).
|
|
10079
|
+
*
|
|
10080
|
+
* The Goodhart vector the density formula permits: fixed deduction mass
|
|
10081
|
+
* ÷ padded denominator → score → 99. A repo with 80 warning-points of
|
|
10082
|
+
* real findings and 10,000 test declarations reads 99+ ("one minor
|
|
10083
|
+
* issue") because the RATE is tiny — the padding is free. Density stays
|
|
10084
|
+
* as the legitimate differentiator for small masses (a lone warning in
|
|
10085
|
+
* a 10k suite must still read ≥ 95), but it can no longer dilute a
|
|
10086
|
+
* large mass: each absolute deduction-mass band caps the score. Padding
|
|
10087
|
+
* cannot move a ceiling — the vector is structurally closed. 80
|
|
10088
|
+
* warning-pts read ≤ 75 in ANY suite size.
|
|
10089
|
+
*
|
|
10090
|
+
* The error-severity floor (95) is the ≥ 8 band seen from the other
|
|
10091
|
+
* side: 1 error ≥ 8 pts, so it is subsumed and left standing as a named
|
|
10092
|
+
* special case for prose continuity. The > 0 band is the honesty guard
|
|
10093
|
+
* (clampWithFindings) — subsumed as well.
|
|
10094
|
+
*
|
|
10095
|
+
* Calibration (P2.4): these boundaries were chosen to hold the three
|
|
10096
|
+
* known data points (self 100, golden 49 via suiteVoided, demo 67) and
|
|
10097
|
+
* are then FROZEN with a changelog entry. Never tune them to flatter a
|
|
10098
|
+
* repo (docs/SCORING.md Correction section is the precedent).
|
|
10099
|
+
*/
|
|
10100
|
+
const DEDUCTION_MASS_CEILINGS = [
|
|
10101
|
+
{
|
|
10102
|
+
minMass: 160,
|
|
10103
|
+
ceiling: 65
|
|
10104
|
+
},
|
|
10105
|
+
{
|
|
10106
|
+
minMass: 80,
|
|
10107
|
+
ceiling: 75
|
|
10108
|
+
},
|
|
10109
|
+
{
|
|
10110
|
+
minMass: 40,
|
|
10111
|
+
ceiling: 85
|
|
10112
|
+
},
|
|
10113
|
+
{
|
|
10114
|
+
minMass: 8,
|
|
10115
|
+
ceiling: 95
|
|
10116
|
+
}
|
|
10117
|
+
];
|
|
10118
|
+
/**
|
|
10119
|
+
* The score ceiling for a given evidence-discounted deduction mass.
|
|
10120
|
+
* 0 → no ceiling (the 100 honesty guard lives in clampWithFindings);
|
|
10121
|
+
* below the smallest band → no ceiling beyond the named guards.
|
|
10122
|
+
* Exported for the JSON contract (effectiveDeductions) and the tests.
|
|
10123
|
+
*/
|
|
10124
|
+
function massCeiling(totalDeduction) {
|
|
10125
|
+
for (const band of DEDUCTION_MASS_CEILINGS) if (totalDeduction >= band.minMass) return band.ceiling;
|
|
10126
|
+
return null;
|
|
10127
|
+
}
|
|
10076
10128
|
function computeTotal(dimensions, findings, exposure) {
|
|
10077
10129
|
if (findings.length === 0) return 100;
|
|
10078
10130
|
let totalDeduction = 0;
|
|
@@ -10085,6 +10137,8 @@ function computeTotal(dimensions, findings, exposure) {
|
|
|
10085
10137
|
const rate = totalDeduction / (declarations + 1);
|
|
10086
10138
|
score = clampWithFindings(100 - Math.min(100, rate * 5), totalDeduction);
|
|
10087
10139
|
} else score = clampWithFindings(100 - totalDeduction, totalDeduction);
|
|
10140
|
+
const ceiling = massCeiling(totalDeduction);
|
|
10141
|
+
if (ceiling !== null && score > ceiling) score = ceiling;
|
|
10088
10142
|
if (findings.some((f) => f.severity === "error" && deductionFor(f) > 0) && score > 95) score = 95;
|
|
10089
10143
|
return suiteVoided ? Math.min(score, 49) : score;
|
|
10090
10144
|
}
|
|
@@ -12321,6 +12375,7 @@ async function runScan$1(args, hooks = {}) {
|
|
|
12321
12375
|
});
|
|
12322
12376
|
const dimensions = computeDimensions(findings);
|
|
12323
12377
|
const rawDeductions = findings.reduce((sum, f) => sum + deductionFor(f), 0);
|
|
12378
|
+
const effectiveDeductions = rawDeductions;
|
|
12324
12379
|
const total = computeTotal(dimensions, findings, {
|
|
12325
12380
|
testDeclarations: testDeclarationCount,
|
|
12326
12381
|
testFileCount,
|
|
@@ -12345,6 +12400,7 @@ async function runScan$1(args, hooks = {}) {
|
|
|
12345
12400
|
testFileCount,
|
|
12346
12401
|
testDeclarationCount,
|
|
12347
12402
|
rawDeductions,
|
|
12403
|
+
effectiveDeductions,
|
|
12348
12404
|
suppressionCount,
|
|
12349
12405
|
...pluginsLoaded.length > 0 ? { plugins: pluginsLoaded } : {},
|
|
12350
12406
|
agenticProfile: computeAgenticProfile(fileProvenance, findings),
|
|
@@ -12899,6 +12955,10 @@ function appendScoreSection(lines, result, p, width, ascii) {
|
|
|
12899
12955
|
lines.push(` ${scoreGauge(result.score, p, gaugeWidth, ascii)}`);
|
|
12900
12956
|
lines.push(` ${p.dim(headlineFor(state, result.findings.length))}`);
|
|
12901
12957
|
if (result.rawDeductions !== void 0 && result.testDeclarationCount) lines.push(` ${p.dim(`(${result.rawDeductions} raw pts / ${result.testDeclarationCount} test declarations — normalized)`)}`);
|
|
12958
|
+
if (result.effectiveDeductions !== void 0 && result.score !== null) {
|
|
12959
|
+
const ceiling = massCeiling(result.effectiveDeductions);
|
|
12960
|
+
if (ceiling !== null && result.score <= ceiling && result.rawDeductions !== void 0) lines.push(` ${p.dim(`(capped: deduction mass ${result.effectiveDeductions} pts — absolute ceiling ${ceiling})`)}`);
|
|
12961
|
+
}
|
|
12902
12962
|
if (result.suppressionCount && result.suppressionCount > 0) lines.push(` ${p.dim(`(${result.suppressionCount} finding(s) suppressed by config)`)}`);
|
|
12903
12963
|
lines.push("");
|
|
12904
12964
|
}
|
|
@@ -13289,7 +13349,7 @@ function renderSarif(result, repoRootUri) {
|
|
|
13289
13349
|
tool: { driver: {
|
|
13290
13350
|
name: "Mjölnir",
|
|
13291
13351
|
informationUri: "https://github.com/Sergey-Bar/Mjolnir",
|
|
13292
|
-
version: "0.5.
|
|
13352
|
+
version: "0.5.34",
|
|
13293
13353
|
rules: [...rules.values()].map((r) => {
|
|
13294
13354
|
const meta = RULES.find((x) => x.id === r.id);
|
|
13295
13355
|
return {
|
|
@@ -18424,7 +18484,7 @@ const { runScan, buildUniversalRules, fallbackWorkspace, pathMatchesGlob, isVali
|
|
|
18424
18484
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
18425
18485
|
* `tests/version-consistency.spec.ts` locally.
|
|
18426
18486
|
*/
|
|
18427
|
-
const CLI_VERSION = "0.5.
|
|
18487
|
+
const CLI_VERSION = "0.5.34";
|
|
18428
18488
|
function parseArgs(argv, onError) {
|
|
18429
18489
|
const args = {
|
|
18430
18490
|
target: ".",
|
package/package.json
CHANGED