mjolnir-qa 0.5.16 → 0.5.18

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,18 @@ 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.18] — 2026-09-07
13
+
14
+ ### Changes since 0.5.17
15
+
16
+ - README: add npm downloads badge
17
+
18
+ ## [0.5.17] — 2026-09-07
19
+
20
+ ### Changes since 0.5.16
21
+
22
+ - Fixture integrity gate: Layer A structural typecheck, doctor fixture-integrity, Python tier (F1, D9, G3) (#53)
23
+
12
24
  ## [0.5.16] — 2026-09-07
13
25
 
14
26
  ### Changes since 0.5.15
package/README.md CHANGED
@@ -8,6 +8,7 @@
8
8
  pipelines, reports a worthiness score, and shows exactly where trust breaks.
9
9
 
10
10
  [![npm](https://img.shields.io/npm/v/mjolnir-qa.svg?style=flat-square&color=C9A227&labelColor=0B0F17)](https://www.npmjs.com/package/mjolnir-qa)
11
+ [![downloads](https://img.shields.io/npm/dm/mjolnir-qa.svg?style=flat-square&color=C9A227&labelColor=0B0F17)](https://www.npmjs.com/package/mjolnir-qa)
11
12
  [![ci](https://img.shields.io/github/actions/workflow/status/Sergey-Bar/Mjolnir/ci.yml?branch=main&style=flat-square&label=ci&labelColor=0B0F17)](https://github.com/Sergey-Bar/Mjolnir/actions/workflows/ci.yml)
12
13
  [![license](https://img.shields.io/badge/license-MIT-C9A227.svg?style=flat-square&labelColor=0B0F17)](LICENSE)
13
14
  [![node](https://img.shields.io/badge/node-%E2%89%A5%2022.18-2E8C7F.svg?style=flat-square&labelColor=0B0F17)](https://nodejs.org)
package/dist/cli.d.mts CHANGED
@@ -699,7 +699,7 @@ declare const runScan: typeof runScan$1, buildUniversalRules: typeof buildUniver
699
699
  * `scripts/sync-sarif-version.cjs` on release and guarded by
700
700
  * `tests/version-consistency.spec.ts` locally.
701
701
  */
702
- declare const CLI_VERSION = "0.5.16";
702
+ declare const CLI_VERSION = "0.5.18";
703
703
  /** A usage-error detail: the offending token, when one exists. */
704
704
  interface UsageErrorDetail {
705
705
  /** The unknown flag or rejected value (e.g. `--nope`, `loud`). */
package/dist/cli.mjs CHANGED
@@ -13264,7 +13264,7 @@ function renderSarif(result, repoRootUri) {
13264
13264
  tool: { driver: {
13265
13265
  name: "Mjölnir",
13266
13266
  informationUri: "https://github.com/Sergey-Bar/Mjolnir",
13267
- version: "0.5.16",
13267
+ version: "0.5.18",
13268
13268
  rules: [...rules.values()].map((r) => {
13269
13269
  const meta = RULES.find((x) => x.id === r.id);
13270
13270
  return {
@@ -17826,6 +17826,63 @@ function checkQuarantineEnforcement(rules = RULES) {
17826
17826
  details
17827
17827
  };
17828
17828
  }
17829
+ /**
17830
+ * Check 8 (certification-audit Phase 2.5, plan G3 Layer A support):
17831
+ * fixture-integrity — deterministic structural census of the fixture
17832
+ * trees. Complements the fixture firewall (which proves must-fire /
17833
+ * must-not-fire PRESENCE per rule) with: orphaned fixture dirs (no
17834
+ * registered rule), empty dirs, and the Layer A typecheck allowlist
17835
+ * census. Blocking: an orphaned dir or an empty fixture dir means the
17836
+ * structural layer has silently drifted.
17837
+ */
17838
+ function checkFixtureIntegrity(fixturesRoot, rules = RULES) {
17839
+ const details = [];
17840
+ let ok = true;
17841
+ const registeredIds = new Set(rules.map((r) => r.id));
17842
+ const fixtureRoots = [fixturesRoot, join(fixturesRoot, "..", "corpus", "positive-fixtures")];
17843
+ let fixtureFiles = 0;
17844
+ let fixtureDirs = 0;
17845
+ for (const root of fixtureRoots) {
17846
+ if (!existsSync(root)) continue;
17847
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
17848
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
17849
+ fixtureDirs++;
17850
+ const ruleId = entry.name;
17851
+ if (!registeredIds.has(ruleId)) {
17852
+ ok = false;
17853
+ details.push(`orphaned fixture dir (no registered rule): ${ruleId}`);
17854
+ continue;
17855
+ }
17856
+ const sub = join(root, ruleId);
17857
+ for (const child of readdirSync(sub, { withFileTypes: true })) if (child.isDirectory()) {
17858
+ const files = nonHiddenFiles(join(sub, child.name));
17859
+ fixtureFiles += files.length;
17860
+ if (files.length === 0) {
17861
+ ok = false;
17862
+ details.push(`empty fixture dir: ${ruleId}/${child.name}`);
17863
+ }
17864
+ } else if (!child.name.startsWith(".")) fixtureFiles++;
17865
+ }
17866
+ }
17867
+ const allowlistPath = join(fixturesRoot, "typecheck-allowlist.json");
17868
+ if (existsSync(allowlistPath)) try {
17869
+ const parsed = JSON.parse(readFileSync(allowlistPath, "utf8"));
17870
+ if (Array.isArray(parsed.entries)) details.push(`Layer A typecheck allowlist: ${parsed.entries.length} justified entr(ies)`);
17871
+ else {
17872
+ ok = false;
17873
+ details.push("typecheck-allowlist.json entries is not an array");
17874
+ }
17875
+ } catch {
17876
+ ok = false;
17877
+ details.push("typecheck-allowlist.json is unreadable/malformed");
17878
+ }
17879
+ details.unshift(`fixture trees: ${fixtureDirs} rule dirs, ${fixtureFiles} fixture files — orphaned dirs and empty dirs are blocking`);
17880
+ return {
17881
+ name: "fixture-integrity",
17882
+ ok,
17883
+ details
17884
+ };
17885
+ }
17829
17886
  function runDoctorSelfAudit(fixturesRoot) {
17830
17887
  const verdictsDir = join(fixturesRoot, "..", "corpus", "verdicts");
17831
17888
  const checks = [
@@ -17835,7 +17892,8 @@ function runDoctorSelfAudit(fixturesRoot) {
17835
17892
  checkEvidenceHonesty(),
17836
17893
  checkTierEnforcement(verdictsDir),
17837
17894
  checkAntiCreep(),
17838
- checkQuarantineEnforcement()
17895
+ checkQuarantineEnforcement(),
17896
+ checkFixtureIntegrity(fixturesRoot)
17839
17897
  ];
17840
17898
  return {
17841
17899
  checks,
@@ -18074,7 +18132,7 @@ const { runScan, buildUniversalRules, fallbackWorkspace, pathMatchesGlob, isVali
18074
18132
  * `scripts/sync-sarif-version.cjs` on release and guarded by
18075
18133
  * `tests/version-consistency.spec.ts` locally.
18076
18134
  */
18077
- const CLI_VERSION = "0.5.16";
18135
+ const CLI_VERSION = "0.5.18";
18078
18136
  function parseArgs(argv, onError) {
18079
18137
  const args = {
18080
18138
  target: ".",
@@ -13263,7 +13263,7 @@ function renderSarif(result, repoRootUri) {
13263
13263
  tool: { driver: {
13264
13264
  name: "Mjölnir",
13265
13265
  informationUri: "https://github.com/Sergey-Bar/Mjolnir",
13266
- version: "0.5.16",
13266
+ version: "0.5.18",
13267
13267
  rules: [...rules.values()].map((r) => {
13268
13268
  const meta = RULES.find((x) => x.id === r.id);
13269
13269
  return {
@@ -17308,6 +17308,63 @@ function checkQuarantineEnforcement(rules = RULES) {
17308
17308
  details
17309
17309
  };
17310
17310
  }
17311
+ /**
17312
+ * Check 8 (certification-audit Phase 2.5, plan G3 Layer A support):
17313
+ * fixture-integrity — deterministic structural census of the fixture
17314
+ * trees. Complements the fixture firewall (which proves must-fire /
17315
+ * must-not-fire PRESENCE per rule) with: orphaned fixture dirs (no
17316
+ * registered rule), empty dirs, and the Layer A typecheck allowlist
17317
+ * census. Blocking: an orphaned dir or an empty fixture dir means the
17318
+ * structural layer has silently drifted.
17319
+ */
17320
+ function checkFixtureIntegrity(fixturesRoot, rules = RULES) {
17321
+ const details = [];
17322
+ let ok = true;
17323
+ const registeredIds = new Set(rules.map((r) => r.id));
17324
+ const fixtureRoots = [fixturesRoot, join(fixturesRoot, "..", "corpus", "positive-fixtures")];
17325
+ let fixtureFiles = 0;
17326
+ let fixtureDirs = 0;
17327
+ for (const root of fixtureRoots) {
17328
+ if (!existsSync(root)) continue;
17329
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
17330
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
17331
+ fixtureDirs++;
17332
+ const ruleId = entry.name;
17333
+ if (!registeredIds.has(ruleId)) {
17334
+ ok = false;
17335
+ details.push(`orphaned fixture dir (no registered rule): ${ruleId}`);
17336
+ continue;
17337
+ }
17338
+ const sub = join(root, ruleId);
17339
+ for (const child of readdirSync(sub, { withFileTypes: true })) if (child.isDirectory()) {
17340
+ const files = nonHiddenFiles(join(sub, child.name));
17341
+ fixtureFiles += files.length;
17342
+ if (files.length === 0) {
17343
+ ok = false;
17344
+ details.push(`empty fixture dir: ${ruleId}/${child.name}`);
17345
+ }
17346
+ } else if (!child.name.startsWith(".")) fixtureFiles++;
17347
+ }
17348
+ }
17349
+ const allowlistPath = join(fixturesRoot, "typecheck-allowlist.json");
17350
+ if (existsSync(allowlistPath)) try {
17351
+ const parsed = JSON.parse(readFileSync(allowlistPath, "utf8"));
17352
+ if (Array.isArray(parsed.entries)) details.push(`Layer A typecheck allowlist: ${parsed.entries.length} justified entr(ies)`);
17353
+ else {
17354
+ ok = false;
17355
+ details.push("typecheck-allowlist.json entries is not an array");
17356
+ }
17357
+ } catch {
17358
+ ok = false;
17359
+ details.push("typecheck-allowlist.json is unreadable/malformed");
17360
+ }
17361
+ details.unshift(`fixture trees: ${fixtureDirs} rule dirs, ${fixtureFiles} fixture files — orphaned dirs and empty dirs are blocking`);
17362
+ return {
17363
+ name: "fixture-integrity",
17364
+ ok,
17365
+ details
17366
+ };
17367
+ }
17311
17368
  function runDoctorSelfAudit(fixturesRoot) {
17312
17369
  const verdictsDir = join(fixturesRoot, "..", "corpus", "verdicts");
17313
17370
  const checks = [
@@ -17317,7 +17374,8 @@ function runDoctorSelfAudit(fixturesRoot) {
17317
17374
  checkEvidenceHonesty(),
17318
17375
  checkTierEnforcement(verdictsDir),
17319
17376
  checkAntiCreep(),
17320
- checkQuarantineEnforcement()
17377
+ checkQuarantineEnforcement(),
17378
+ checkFixtureIntegrity(fixturesRoot)
17321
17379
  ];
17322
17380
  return {
17323
17381
  checks,
@@ -17710,7 +17768,7 @@ const { runScan, buildUniversalRules, fallbackWorkspace, pathMatchesGlob, isVali
17710
17768
  * `scripts/sync-sarif-version.cjs` on release and guarded by
17711
17769
  * `tests/version-consistency.spec.ts` locally.
17712
17770
  */
17713
- const CLI_VERSION = "0.5.16";
17771
+ const CLI_VERSION = "0.5.18";
17714
17772
  function parseArgs(argv, onError) {
17715
17773
  const args = {
17716
17774
  target: ".",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mjolnir-qa",
3
- "version": "0.5.16",
3
+ "version": "0.5.18",
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": {
@@ -28,7 +28,7 @@
28
28
  "test:watch": "vitest",
29
29
  "lint": "eslint . --max-warnings=0 && prettier --check .",
30
30
  "format": "prettier --write .",
31
- "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
31
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json && tsx scripts/typecheck-fixtures.ts",
32
32
  "golden:update": "tsx tests/golden/gen.ts",
33
33
  "corpus:regression": "tsx tests/corpus/audit.ts",
34
34
  "corpus:regression:update": "tsx tests/corpus/audit.ts --update",