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 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.34";
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.34",
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.34";
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)
@@ -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.34",
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.34";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mjolnir-qa",
3
- "version": "0.5.34",
3
+ "version": "0.5.35",
4
4
  "description": "Mjölnir — the Verification Trust Engine for QA. Audits test suites and CI pipelines, reports a worthiness score and prioritized findings.",
5
5
  "type": "module",
6
6
  "engines": {