mjolnir-qa 0.5.34 → 0.5.35
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/README.md +6 -0
- package/dist/cli.d.mts +2 -2
- package/dist/cli.mjs +64 -2
- package/dist/mcp/stdio.mjs +64 -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.35] — 2026-09-08
|
|
13
|
+
|
|
14
|
+
### Changes since 0
|
|
15
|
+
|
|
16
|
+
- P3a: GitLab CI — Code Quality report, MR recipe, exit-code discipline (#65)
|
|
17
|
+
|
|
12
18
|
## [0.5.34] — 2026-09-08
|
|
13
19
|
|
|
14
20
|
### Changes since 0
|
package/README.md
CHANGED
|
@@ -203,6 +203,7 @@ pinned to the `v1` major tag) by default, or plain `npx` with
|
|
|
203
203
|
| `mjolnir forensics ./test-results/` | Runtime evidence from a real run |
|
|
204
204
|
| `mjolnir handoff` | Remediation plan for a coding agent |
|
|
205
205
|
| `mjolnir --json` / `--format sarif` | Machine-readable / GitHub Code Scanning |
|
|
206
|
+
| `mjolnir --format codequality` | GitLab Code Quality report (MR widget artifact) |
|
|
206
207
|
| `mjolnir --strict` | Also run quarantine-tier rules (higher FP risk) |
|
|
207
208
|
|
|
208
209
|
<details>
|
|
@@ -565,6 +566,10 @@ Or wire it into GitHub Code Scanning natively via SARIF:
|
|
|
565
566
|
sarif_file: mjolnir.sarif
|
|
566
567
|
```
|
|
567
568
|
|
|
569
|
+
On GitLab, `--format codequality` emits the Code Quality report the MR
|
|
570
|
+
widget and diff annotations consume
|
|
571
|
+
([docs/GITLAB-CI.md](docs/GITLAB-CI.md)).
|
|
572
|
+
|
|
568
573
|
Editor and pipeline setup: [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md).
|
|
569
574
|
|
|
570
575
|
### Changed-scope attribution
|
|
@@ -718,6 +723,7 @@ Rule IDs (`QA-<FAMILY>-NNN`) are immutable once shipped and never reused.
|
|
|
718
723
|
| [docs/VERSIONING.md](docs/VERSIONING.md) | Semver policy, frozen surfaces, deprecation cycle |
|
|
719
724
|
| [docs/machine-contract.md](docs/machine-contract.md) | The canonical machine-readable result |
|
|
720
725
|
| [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | SARIF output + editor/CI setup |
|
|
726
|
+
| [docs/GITLAB-CI.md](docs/GITLAB-CI.md) | GitLab: Code Quality report, MR recipe, gate |
|
|
721
727
|
| [docs/rules/](docs/rules/) | Generated per-rule catalog |
|
|
722
728
|
| [CONTRIBUTING.md](CONTRIBUTING.md) | Dev setup + contribution workflow |
|
|
723
729
|
| [SUPPORT.md](SUPPORT.md) | Where to ask, report and get help |
|
package/dist/cli.d.mts
CHANGED
|
@@ -565,7 +565,7 @@ interface CliArgs {
|
|
|
565
565
|
verbose: boolean;
|
|
566
566
|
maxDurationMs: number;
|
|
567
567
|
scopeChanged: boolean;
|
|
568
|
-
format: "terminal" | "json" | "sarif" | "mermaid";
|
|
568
|
+
format: "terminal" | "json" | "sarif" | "mermaid" | "codequality";
|
|
569
569
|
/** --width override for terminal box/gauge wrapping (Sprint 5 Task 22). */
|
|
570
570
|
width?: number;
|
|
571
571
|
/** --ascii / --no-ascii override for shouldUseAscii()'s heuristic. */
|
|
@@ -737,7 +737,7 @@ declare const runScan: typeof runScan$1, buildUniversalRules: typeof buildUniver
|
|
|
737
737
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
738
738
|
* `tests/version-consistency.spec.ts` locally.
|
|
739
739
|
*/
|
|
740
|
-
declare const CLI_VERSION = "0.5.
|
|
740
|
+
declare const CLI_VERSION = "0.5.35";
|
|
741
741
|
/** A usage-error detail: the offending token, when one exists. */
|
|
742
742
|
interface UsageErrorDetail {
|
|
743
743
|
/** The unknown flag or rejected value (e.g. `--nope`, `loud`). */
|
package/dist/cli.mjs
CHANGED
|
@@ -13350,7 +13350,7 @@ function renderSarif(result, repoRootUri) {
|
|
|
13350
13350
|
tool: { driver: {
|
|
13351
13351
|
name: "Mjölnir",
|
|
13352
13352
|
informationUri: "https://github.com/Sergey-Bar/Mjolnir",
|
|
13353
|
-
version: "0.5.
|
|
13353
|
+
version: "0.5.35",
|
|
13354
13354
|
rules: [...rules.values()].map((r) => {
|
|
13355
13355
|
const meta = RULES.find((x) => x.id === r.id);
|
|
13356
13356
|
return {
|
|
@@ -13422,6 +13422,62 @@ function renderSarif(result, repoRootUri) {
|
|
|
13422
13422
|
return JSON.stringify(sarif, null, 2);
|
|
13423
13423
|
}
|
|
13424
13424
|
//#endregion
|
|
13425
|
+
//#region src/reporter/codequality.ts
|
|
13426
|
+
/**
|
|
13427
|
+
* GitLab Code Quality reporter (product-gap-remediation master plan P3a,
|
|
13428
|
+
* plan 1788853205786 — flag 2, decision 2: GitLab is the in-plan second
|
|
13429
|
+
* platform).
|
|
13430
|
+
*
|
|
13431
|
+
* Emits the Code Quality report schema GitLab's `codequality` artifact
|
|
13432
|
+
* type consumes (docs.gitlab.com/ee/ci/testing/code_quality.html) so
|
|
13433
|
+
* findings render as MR widgets and diff annotations inside existing
|
|
13434
|
+
* governance — the same distribution logic as SARIF for GitHub, never a
|
|
13435
|
+
* second truth: this is a deterministic projection of the same
|
|
13436
|
+
* ScanResult the JSON contract carries.
|
|
13437
|
+
*
|
|
13438
|
+
* Usage: mjolnir --format codequality > gl-code-quality-report.json
|
|
13439
|
+
*
|
|
13440
|
+
* Severity map (documented in docs/GITLAB-CI.md):
|
|
13441
|
+
* error → major (categorical defects — the gate's core)
|
|
13442
|
+
* warning → minor
|
|
13443
|
+
* info → info
|
|
13444
|
+
*
|
|
13445
|
+
* Fingerprint stability: GitLab deduplicates MR findings across runs by
|
|
13446
|
+
* `fingerprint` — an unstable fingerprint would resurrect closed
|
|
13447
|
+
* findings on every push (the exact PR-noise the MR flow exists to
|
|
13448
|
+
* avoid). The fingerprint is sha256 over ruleId + repo-relative path +
|
|
13449
|
+
* line + column + message — the same finding shape the --why command
|
|
13450
|
+
* addresses, so a moved line legitimately re-reports and an unchanged
|
|
13451
|
+
* finding stays silent.
|
|
13452
|
+
*/
|
|
13453
|
+
function codeQualitySeverity(severity) {
|
|
13454
|
+
if (severity === "error") return "major";
|
|
13455
|
+
if (severity === "warning") return "minor";
|
|
13456
|
+
return "info";
|
|
13457
|
+
}
|
|
13458
|
+
/**
|
|
13459
|
+
* sha256 hex over the identifying tuple. Exported for the contract test:
|
|
13460
|
+
* the fingerprint MUST be stable across runs for an unchanged finding
|
|
13461
|
+
* (GitLab dedup contract) and MUST change when the finding's identity
|
|
13462
|
+
* changes (otherwise an MR could hide a moved-and-still-broken line).
|
|
13463
|
+
*/
|
|
13464
|
+
function codeQualityFingerprint(f) {
|
|
13465
|
+
return createHash("sha256").update(`${f.ruleId}\u0000${f.file}\u0000${f.line}\u0000${f.column}\u0000${f.message}`).digest("hex");
|
|
13466
|
+
}
|
|
13467
|
+
function renderCodeQuality(result) {
|
|
13468
|
+
const issues = result.findings.map((f) => ({
|
|
13469
|
+
description: f.message,
|
|
13470
|
+
check_name: f.ruleId,
|
|
13471
|
+
fingerprint: codeQualityFingerprint(f),
|
|
13472
|
+
severity: codeQualitySeverity(f.severity),
|
|
13473
|
+
location: {
|
|
13474
|
+
path: f.file.replaceAll("\\", "/"),
|
|
13475
|
+
lines: { begin: f.line }
|
|
13476
|
+
}
|
|
13477
|
+
}));
|
|
13478
|
+
return JSON.stringify(issues, null, 2) + "\n";
|
|
13479
|
+
}
|
|
13480
|
+
//#endregion
|
|
13425
13481
|
//#region src/reporter/mermaid.ts
|
|
13426
13482
|
function sanitizeId(raw) {
|
|
13427
13483
|
return raw.replace(/[^a-z0-9]/gi, "_");
|
|
@@ -16384,6 +16440,10 @@ const HELP_FLAGS = [
|
|
|
16384
16440
|
flag: "--format sarif",
|
|
16385
16441
|
summary: "SARIF 2.1 for GitHub Code Scanning"
|
|
16386
16442
|
},
|
|
16443
|
+
{
|
|
16444
|
+
flag: "--format codequality",
|
|
16445
|
+
summary: "GitLab Code Quality report (MR widget artifact)"
|
|
16446
|
+
},
|
|
16387
16447
|
{
|
|
16388
16448
|
flag: "--format mermaid",
|
|
16389
16449
|
summary: "test-architecture diagram"
|
|
@@ -18848,7 +18908,7 @@ const { runScan, buildUniversalRules, fallbackWorkspace, pathMatchesGlob, isVali
|
|
|
18848
18908
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
18849
18909
|
* `tests/version-consistency.spec.ts` locally.
|
|
18850
18910
|
*/
|
|
18851
|
-
const CLI_VERSION = "0.5.
|
|
18911
|
+
const CLI_VERSION = "0.5.35";
|
|
18852
18912
|
function parseArgs(argv, onError) {
|
|
18853
18913
|
const args = {
|
|
18854
18914
|
target: ".",
|
|
@@ -18871,6 +18931,7 @@ function parseArgs(argv, onError) {
|
|
|
18871
18931
|
const fmt = argv[++i];
|
|
18872
18932
|
if (fmt === "sarif") args.format = "sarif";
|
|
18873
18933
|
else if (fmt === "mermaid") args.format = "mermaid";
|
|
18934
|
+
else if (fmt === "codequality") args.format = "codequality";
|
|
18874
18935
|
else if (fmt === "json") {
|
|
18875
18936
|
args.format = "json";
|
|
18876
18937
|
args.json = true;
|
|
@@ -19255,6 +19316,7 @@ async function runScanCommand(argv, io = {
|
|
|
19255
19316
|
}
|
|
19256
19317
|
if (args.format === "sarif") io.out(renderSarif(result, pathToFileURL(target).href));
|
|
19257
19318
|
else if (args.format === "mermaid") io.out(renderMermaid(result));
|
|
19319
|
+
else if (args.format === "codequality") io.out(renderCodeQuality(result));
|
|
19258
19320
|
else if (args.json) io.out(JSON.stringify({
|
|
19259
19321
|
...result,
|
|
19260
19322
|
contract: buildMachineContract(result)
|
package/dist/mcp/stdio.mjs
CHANGED
|
@@ -13349,7 +13349,7 @@ function renderSarif(result, repoRootUri) {
|
|
|
13349
13349
|
tool: { driver: {
|
|
13350
13350
|
name: "Mjölnir",
|
|
13351
13351
|
informationUri: "https://github.com/Sergey-Bar/Mjolnir",
|
|
13352
|
-
version: "0.5.
|
|
13352
|
+
version: "0.5.35",
|
|
13353
13353
|
rules: [...rules.values()].map((r) => {
|
|
13354
13354
|
const meta = RULES.find((x) => x.id === r.id);
|
|
13355
13355
|
return {
|
|
@@ -13421,6 +13421,62 @@ function renderSarif(result, repoRootUri) {
|
|
|
13421
13421
|
return JSON.stringify(sarif, null, 2);
|
|
13422
13422
|
}
|
|
13423
13423
|
//#endregion
|
|
13424
|
+
//#region src/reporter/codequality.ts
|
|
13425
|
+
/**
|
|
13426
|
+
* GitLab Code Quality reporter (product-gap-remediation master plan P3a,
|
|
13427
|
+
* plan 1788853205786 — flag 2, decision 2: GitLab is the in-plan second
|
|
13428
|
+
* platform).
|
|
13429
|
+
*
|
|
13430
|
+
* Emits the Code Quality report schema GitLab's `codequality` artifact
|
|
13431
|
+
* type consumes (docs.gitlab.com/ee/ci/testing/code_quality.html) so
|
|
13432
|
+
* findings render as MR widgets and diff annotations inside existing
|
|
13433
|
+
* governance — the same distribution logic as SARIF for GitHub, never a
|
|
13434
|
+
* second truth: this is a deterministic projection of the same
|
|
13435
|
+
* ScanResult the JSON contract carries.
|
|
13436
|
+
*
|
|
13437
|
+
* Usage: mjolnir --format codequality > gl-code-quality-report.json
|
|
13438
|
+
*
|
|
13439
|
+
* Severity map (documented in docs/GITLAB-CI.md):
|
|
13440
|
+
* error → major (categorical defects — the gate's core)
|
|
13441
|
+
* warning → minor
|
|
13442
|
+
* info → info
|
|
13443
|
+
*
|
|
13444
|
+
* Fingerprint stability: GitLab deduplicates MR findings across runs by
|
|
13445
|
+
* `fingerprint` — an unstable fingerprint would resurrect closed
|
|
13446
|
+
* findings on every push (the exact PR-noise the MR flow exists to
|
|
13447
|
+
* avoid). The fingerprint is sha256 over ruleId + repo-relative path +
|
|
13448
|
+
* line + column + message — the same finding shape the --why command
|
|
13449
|
+
* addresses, so a moved line legitimately re-reports and an unchanged
|
|
13450
|
+
* finding stays silent.
|
|
13451
|
+
*/
|
|
13452
|
+
function codeQualitySeverity(severity) {
|
|
13453
|
+
if (severity === "error") return "major";
|
|
13454
|
+
if (severity === "warning") return "minor";
|
|
13455
|
+
return "info";
|
|
13456
|
+
}
|
|
13457
|
+
/**
|
|
13458
|
+
* sha256 hex over the identifying tuple. Exported for the contract test:
|
|
13459
|
+
* the fingerprint MUST be stable across runs for an unchanged finding
|
|
13460
|
+
* (GitLab dedup contract) and MUST change when the finding's identity
|
|
13461
|
+
* changes (otherwise an MR could hide a moved-and-still-broken line).
|
|
13462
|
+
*/
|
|
13463
|
+
function codeQualityFingerprint(f) {
|
|
13464
|
+
return createHash("sha256").update(`${f.ruleId}\u0000${f.file}\u0000${f.line}\u0000${f.column}\u0000${f.message}`).digest("hex");
|
|
13465
|
+
}
|
|
13466
|
+
function renderCodeQuality(result) {
|
|
13467
|
+
const issues = result.findings.map((f) => ({
|
|
13468
|
+
description: f.message,
|
|
13469
|
+
check_name: f.ruleId,
|
|
13470
|
+
fingerprint: codeQualityFingerprint(f),
|
|
13471
|
+
severity: codeQualitySeverity(f.severity),
|
|
13472
|
+
location: {
|
|
13473
|
+
path: f.file.replaceAll("\\", "/"),
|
|
13474
|
+
lines: { begin: f.line }
|
|
13475
|
+
}
|
|
13476
|
+
}));
|
|
13477
|
+
return JSON.stringify(issues, null, 2) + "\n";
|
|
13478
|
+
}
|
|
13479
|
+
//#endregion
|
|
13424
13480
|
//#region src/reporter/mermaid.ts
|
|
13425
13481
|
function sanitizeId(raw) {
|
|
13426
13482
|
return raw.replace(/[^a-z0-9]/gi, "_");
|
|
@@ -15575,6 +15631,10 @@ const HELP_FLAGS = [
|
|
|
15575
15631
|
flag: "--format sarif",
|
|
15576
15632
|
summary: "SARIF 2.1 for GitHub Code Scanning"
|
|
15577
15633
|
},
|
|
15634
|
+
{
|
|
15635
|
+
flag: "--format codequality",
|
|
15636
|
+
summary: "GitLab Code Quality report (MR widget artifact)"
|
|
15637
|
+
},
|
|
15578
15638
|
{
|
|
15579
15639
|
flag: "--format mermaid",
|
|
15580
15640
|
summary: "test-architecture diagram"
|
|
@@ -18484,7 +18544,7 @@ const { runScan, buildUniversalRules, fallbackWorkspace, pathMatchesGlob, isVali
|
|
|
18484
18544
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
18485
18545
|
* `tests/version-consistency.spec.ts` locally.
|
|
18486
18546
|
*/
|
|
18487
|
-
const CLI_VERSION = "0.5.
|
|
18547
|
+
const CLI_VERSION = "0.5.35";
|
|
18488
18548
|
function parseArgs(argv, onError) {
|
|
18489
18549
|
const args = {
|
|
18490
18550
|
target: ".",
|
|
@@ -18507,6 +18567,7 @@ function parseArgs(argv, onError) {
|
|
|
18507
18567
|
const fmt = argv[++i];
|
|
18508
18568
|
if (fmt === "sarif") args.format = "sarif";
|
|
18509
18569
|
else if (fmt === "mermaid") args.format = "mermaid";
|
|
18570
|
+
else if (fmt === "codequality") args.format = "codequality";
|
|
18510
18571
|
else if (fmt === "json") {
|
|
18511
18572
|
args.format = "json";
|
|
18512
18573
|
args.json = true;
|
|
@@ -18891,6 +18952,7 @@ async function runScanCommand(argv, io = {
|
|
|
18891
18952
|
}
|
|
18892
18953
|
if (args.format === "sarif") io.out(renderSarif(result, pathToFileURL(target).href));
|
|
18893
18954
|
else if (args.format === "mermaid") io.out(renderMermaid(result));
|
|
18955
|
+
else if (args.format === "codequality") io.out(renderCodeQuality(result));
|
|
18894
18956
|
else if (args.json) io.out(JSON.stringify({
|
|
18895
18957
|
...result,
|
|
18896
18958
|
contract: buildMachineContract(result)
|
package/package.json
CHANGED