mjolnir-qa 1.0.2 → 1.0.3

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.
@@ -10076,6 +10076,237 @@ function createScanCache(root) {
10076
10076
  };
10077
10077
  }
10078
10078
  //#endregion
10079
+ //#region src/brand/tokens.ts
10080
+ /**
10081
+ * The single source of brand truth.
10082
+ *
10083
+ * Every colour, typeface and motion constant Mjölnir shows a human —
10084
+ * terminal, README SVGs, demo video, website, docs, badges — resolves to
10085
+ * a value in this file. Nothing else may define one.
10086
+ *
10087
+ * WHY THIS EXISTS. Before it, the palette existed in six independent
10088
+ * copies: `site/.vitepress/theme/styles/vars.css`, `NORSE` in
10089
+ * `src/reporter/theme.ts`, `scripts/readme-svg.ts`,
10090
+ * `scripts/video/terminal-page.ts`, `scripts/generate-readme-architecture.ts`
10091
+ * and the table in `assets/brand/README.md`. Exactly one pair of those
10092
+ * was guarded (site-doctor Check 8, doc ↔ vars.css). The unguarded edges
10093
+ * are where the shipped surfaces drifted apart: the terminal and the site
10094
+ * disagreed on six semantic roles, the architecture diagram invented its
10095
+ * own neutral ramp, and the README badges still carried a palette retired
10096
+ * two releases earlier. `scripts/brand-doctor.mjs` now checks every edge
10097
+ * against this file.
10098
+ *
10099
+ * PURITY. Pure data. No I/O, no rendering, no environment access, no
10100
+ * imports, no logic. Consumers convert (hex → ANSI triplet, hex → CSS)
10101
+ * themselves. Same reason `score-state.ts` is pure: it makes the whole
10102
+ * thing golden-testable and safe to ship inside the npm package, where
10103
+ * it costs a few hundred bytes and replaces values the package already
10104
+ * carried anyway.
10105
+ *
10106
+ * DERIVATION. The palette is the one derived from the logo in PR #20
10107
+ * (brushed steel and forge gold under an aurora, over midnight iron).
10108
+ * Where the terminal disagreed with it, the terminal converges — see
10109
+ * `PENDING_TERMINAL` below. Full rationale: `assets/brand/README.md`.
10110
+ *
10111
+ * ACCESSIBILITY. Every foreground token in `BRAND` meets WCAG AA
10112
+ * (≥ 4.5:1) against every surface token it is allowed to sit on. That is
10113
+ * not a claim, it is `brand-doctor` rule 8, which computes the ratios.
10114
+ * The weakest legal pairing is `steelDim` on `ink800` at 5.00:1.
10115
+ */
10116
+ /**
10117
+ * The two brand hues plus the neutral they sit on.
10118
+ *
10119
+ * GOLD IS SCARCE. It means forged / certified / earned / decisive — the
10120
+ * primary mark, the FORGED state, one call to action. It is not a paint
10121
+ * bucket: gold as default text, default border or default heading is a
10122
+ * brand-doctor finding, not a style choice.
10123
+ *
10124
+ * AURORA is verification energy — the secondary, and the hue that marks
10125
+ * the runtime half of the trust ladder.
10126
+ */
10127
+ const BRAND = {
10128
+ gold: "#C19A34",
10129
+ goldBright: "#E6BD57",
10130
+ goldHot: "#F4DC9C",
10131
+ /** Pressed / deepest gold — the only step dark enough to carry white. */
10132
+ goldDeep: "#A5811C",
10133
+ aurora: "#37ABBD",
10134
+ auroraBright: "#45C1D4",
10135
+ auroraCyan: "#5CBDE0",
10136
+ steel: "#C8CBCF",
10137
+ steelDim: "#8B939D"
10138
+ };
10139
+ /**
10140
+ * Midnight iron. One ramp, four steps, darkest first.
10141
+ *
10142
+ * `terminal` and `terminalBar` share one tone deliberately: the window's
10143
+ * only seam is a hairline ring and an inset shadow, never a second fill.
10144
+ * `chromeDot` is the three window dots — see the note on
10145
+ * `PENDING_TERMINAL.chromeDots` for why they are no longer red/amber/green.
10146
+ */
10147
+ const SURFACE = {
10148
+ ink950: "#0A1119",
10149
+ ink900: "#0C1420",
10150
+ ink850: "#111A29",
10151
+ ink800: "#18243A",
10152
+ /** Raised panel (cards, elevated surfaces). */
10153
+ panel: "#141F33",
10154
+ /** Soft fill (inline code, quiet chips). */
10155
+ soft: "#1A2740",
10156
+ /** Terminal body — the deepest tone, so a terminal reads as recessed. */
10157
+ terminal: "#0A1119",
10158
+ /** Terminal title bar — the same tone; the seam is shadow, not colour. */
10159
+ terminalBar: "#0A1119",
10160
+ /** The three window dots. One neutral, not a traffic light. */
10161
+ chromeDot: "#18243A"
10162
+ };
10163
+ const TEXT = {
10164
+ primary: "#EAEEF5",
10165
+ secondary: "#ABB6C6",
10166
+ muted: "#8B939D",
10167
+ /** Ink for text set ON gold (buttons, the FORGED chip). 7.17:1 on `gold`. */
10168
+ onGold: "#0A1119"
10169
+ };
10170
+ /**
10171
+ * Non-score status. `ok` is the one green in the system and it is NOT a
10172
+ * score colour — it survives only for contexts with no worthiness
10173
+ * meaning ("autofix applied", "analysis complete"). A green score would
10174
+ * say "your software is fine", which is the exact claim this product
10175
+ * refuses to make.
10176
+ */
10177
+ const STATUS = {
10178
+ ok: "#4FB477",
10179
+ info: "#5CC4E0",
10180
+ warning: "#E6BD57",
10181
+ error: "#EC6B66"
10182
+ };
10183
+ /**
10184
+ * The four ScoreState bands plus the unmeasured state. Band thresholds
10185
+ * and runes live in `src/reporter/score-state.ts`, which stays free of
10186
+ * colour — it emits a palette KEY and each surface resolves it here.
10187
+ *
10188
+ * `unmeasured` is steel-dim on purpose. UNKNOWN is a legitimate answer,
10189
+ * not a failure: colouring it red would make "we did not measure this"
10190
+ * look like "this is broken", which is precisely the dishonesty the
10191
+ * north-star law exists to prevent.
10192
+ */
10193
+ const SCORE = {
10194
+ critical: "#EC6B66",
10195
+ warning: "#E6BD57",
10196
+ trusted: "#5CC4E0",
10197
+ forged: "#F4DC9C",
10198
+ unmeasured: "#8B939D"
10199
+ };
10200
+ /**
10201
+ * E0 → E1 → E2 is a certainty ramp, and it is deliberately HUE-FREE.
10202
+ *
10203
+ * Evidence level says how sure we are, not whether the news is good. A
10204
+ * deterministic proof (E2) is a defect we are certain about — painting
10205
+ * it gold or green would read as an achievement. So certainty is carried
10206
+ * by brightness alone, and the *shape* does the real work:
10207
+ *
10208
+ * E0 open ring observation, no weight
10209
+ * E1 half-filled pattern evidence, half weight
10210
+ * E2 sealed deterministic proof, full weight
10211
+ *
10212
+ * Colour never carries this alone (R11): the geometry is the signal and
10213
+ * survives `--ascii`, `NO_COLOR` and monochrome print.
10214
+ */
10215
+ const EVIDENCE = {
10216
+ e0: "#8B939D",
10217
+ e1: "#ABB6C6",
10218
+ e2: "#EAEEF5"
10219
+ };
10220
+ /**
10221
+ * L0–L5, and the most important boundary in the product.
10222
+ *
10223
+ * L0–L2 are STATIC: the neutral steel ramp, brightening to the static
10224
+ * ceiling at L2. L3–L5 require a real run, and the hue changes to aurora
10225
+ * exactly there. The boundary is a hue break, not a gradient step,
10226
+ * because it is a change of kind and not of degree — a static-only
10227
+ * finding can never climb past L2, however confident it is.
10228
+ *
10229
+ * Every surface that draws the ladder must draw that break.
10230
+ */
10231
+ const TRUST = {
10232
+ l0: "#8B939D",
10233
+ l1: "#ABB6C6",
10234
+ l2: "#C8CBCF",
10235
+ l3: "#37ABBD",
10236
+ l4: "#45C1D4",
10237
+ l5: "#5CC4E0"
10238
+ };
10239
+ const TINT = {
10240
+ gold: {
10241
+ fill: "#F6EBCC",
10242
+ stroke: "#7A5F16",
10243
+ text: "#4A3A0E"
10244
+ },
10245
+ aurora: {
10246
+ fill: "#D9F0F4",
10247
+ stroke: "#1F6F7C",
10248
+ text: "#10353C"
10249
+ },
10250
+ error: {
10251
+ fill: "#FADEDD",
10252
+ stroke: "#A83A35",
10253
+ text: "#4E1B19"
10254
+ },
10255
+ /** The unmeasured / unknown state. Neutral, never the error tint. */
10256
+ neutral: {
10257
+ fill: "#E4E7EB",
10258
+ stroke: "#5C646E",
10259
+ text: "#262B31"
10260
+ },
10261
+ ok: {
10262
+ fill: "#DCF0E4",
10263
+ stroke: "#276B45",
10264
+ text: "#163A26"
10265
+ }
10266
+ };
10267
+ /**
10268
+ * The score bands as the badge Mjölnir itself generates renders them.
10269
+ *
10270
+ * These are DEEPER than the score tokens on purpose, and it is not a
10271
+ * style preference: shields.io sets the message text in white and gives
10272
+ * you no say in it. `score.forged` (#F4DC9C) under white text measures
10273
+ * 1.35:1 — an unreadable badge, shipped to look on-brand. The brand's
10274
+ * own deep steps put every band between 4.9 and 6.3:1.
10275
+ *
10276
+ * What they replace was worse than off-brand, it was wrong:
10277
+ *
10278
+ * band was rendered as white-on now white-on
10279
+ * ────────────────────────────────────────────────────────────────────────────
10280
+ * 0-49 `red` #dd4343 4.24 #A83A35 6.32
10281
+ * 50-79 `yellow` #d8b800 1.95 #7A5F16 6.04
10282
+ * 80-99 `important` #ea7233 ORANGE 3.02 #1F6F7C 5.80
10283
+ * 100 `success` #44bb00 GREEN 2.51 #8A6D1E 4.90
10284
+ * unmeasured `lightgrey` #939393 3.07 #5C646E 5.99
10285
+ *
10286
+ * Two of those were defects, not preferences. `success` is green, and
10287
+ * green is not a score colour here — a 100 badge said "your software is
10288
+ * fine", which is the one claim this product refuses to make. And
10289
+ * `important` is ORANGE, not the blue-family colour the code's own
10290
+ * comment claimed for eight releases: every WORTHY badge ever rendered
10291
+ * showed the trusted band in a warning colour. Nobody had resolved a
10292
+ * shields name to a value and looked.
10293
+ *
10294
+ * Values are hex without `#`, the form shields.io's endpoint takes.
10295
+ * `A83A35`, `7A5F16`, `1F6F7C` and `5C646E` are the `TINT` strokes —
10296
+ * the same deep steps the mermaid diagrams use, for the same reason.
10297
+ * `8A6D1E` is the gold the brand document already named as the light
10298
+ * FORGED gradient's start.
10299
+ */
10300
+ const BADGE_BAND = {
10301
+ critical: "A83A35",
10302
+ warning: "7A5F16",
10303
+ trusted: "1F6F7C",
10304
+ forged: "8A6D1E",
10305
+ unmeasured: "5C646E"
10306
+ };
10307
+ SURFACE.ink950, SURFACE.ink900, SURFACE.ink850, SURFACE.ink800, BRAND.steel, BRAND.steelDim, BRAND.gold, BRAND.goldBright, BRAND.goldHot, BRAND.aurora, BRAND.auroraBright, BRAND.auroraCyan;
10308
+ SCORE.trusted, SCORE.forged, SCORE.warning, SCORE.critical, STATUS.info, TEXT.onGold, STATUS.ok, EVIDENCE.e0, EVIDENCE.e1, EVIDENCE.e2, TRUST.l0, TRUST.l1, TRUST.l2, TRUST.l3, TRUST.l4, TRUST.l5;
10309
+ //#endregion
10079
10310
  //#region src/reporter/score-state.ts
10080
10311
  const HEADLINES = {
10081
10312
  critical: "The hammer is cracked — {n} findings break its edge.",
@@ -10152,11 +10383,11 @@ function headlineFor(state, findings) {
10152
10383
  * Respects NO_COLOR and non-TTY via `palette(isTTY)` — every renderer
10153
10384
  * receives a palette and never touches process.env directly.
10154
10385
  *
10155
- * Palette: a cold northern set — frost-steel, aurora teal, Yggdrasil
10156
- * green — with amber for warnings (Mjölnir's lightning) and a
10157
- * rune-red for errors. No magenta/pink. Emitted as 24-bit truecolor
10158
- * SGR (`38;2;r;g;b`), which every modern terminal renders and which
10159
- * `shouldColorize` already gates behind TTY + !NO_COLOR.
10386
+ * Palette: resolved from `src/brand/tokens.ts`, the single source of
10387
+ * brand truth — this file defines no colour of its own. Emitted as
10388
+ * 24-bit truecolor SGR (`38;2;r;g;b`), which every modern terminal
10389
+ * renders and which `shouldColorize` already gates behind
10390
+ * TTY + !NO_COLOR.
10160
10391
  *
10161
10392
  * Symbols always accompany color (color-blind safe, R11).
10162
10393
  *
@@ -10169,53 +10400,42 @@ function headlineFor(state, findings) {
10169
10400
  * flag so output degrades to plain characters on cmd.exe/legacy
10170
10401
  * consoles that mangle box-drawing glyphs and emoji.
10171
10402
  */
10172
- /** Norse-forge palette, 24-bit truecolor. */
10403
+ /**
10404
+ * `"#RRGGBB"` → the `[r, g, b]` triplet the SGR truecolor emitter needs.
10405
+ * Lives here rather than in `src/brand/tokens.ts`, which is pure data:
10406
+ * each surface converts the canonical hex into its own colour space.
10407
+ */
10408
+ function fromHex(hex) {
10409
+ const n = Number.parseInt(hex.slice(1), 16);
10410
+ return [
10411
+ n >> 16 & 255,
10412
+ n >> 8 & 255,
10413
+ n & 255
10414
+ ];
10415
+ }
10416
+ /**
10417
+ * The terminal palette, 24-bit truecolor, resolved from
10418
+ * `src/brand/tokens.ts` — the single source of brand truth. Nothing in
10419
+ * this file may name a hex value of its own, and `brand-doctor` rule 2
10420
+ * fails if it tries.
10421
+ *
10422
+ * Every role is now the canonical token. Six of them used to be the
10423
+ * terminal's own: a frost-steel blue for headers, a teal for info, an
10424
+ * amber for warnings, a rune-red for errors, a bone white for bold and a
10425
+ * weathered stone for dim — a second palette for one product. The
10426
+ * rune-red also failed WCAG AA at 4.36:1 on this terminal's own
10427
+ * background; `STATUS.error` on the canonical ground is 6.20:1.
10428
+ */
10173
10429
  const NORSE = {
10174
- ok: [
10175
- 79,
10176
- 180,
10177
- 119
10178
- ],
10179
- info: [
10180
- 63,
10181
- 176,
10182
- 160
10183
- ],
10184
- accent: [
10185
- 138,
10186
- 180,
10187
- 216
10188
- ],
10189
- warning: [
10190
- 224,
10191
- 165,
10192
- 38
10193
- ],
10194
- error: [
10195
- 208,
10196
- 69,
10197
- 59
10198
- ],
10199
- trusted: [
10200
- 92,
10201
- 196,
10202
- 224
10203
- ],
10204
- forged: [
10205
- 244,
10206
- 220,
10207
- 156
10208
- ],
10209
- bold: [
10210
- 237,
10211
- 230,
10212
- 214
10213
- ],
10214
- dim: [
10215
- 124,
10216
- 133,
10217
- 144
10218
- ]
10430
+ ok: fromHex(STATUS.ok),
10431
+ info: fromHex(BRAND.aurora),
10432
+ accent: fromHex(BRAND.steel),
10433
+ warning: fromHex(STATUS.warning),
10434
+ error: fromHex(STATUS.error),
10435
+ trusted: fromHex(SCORE.trusted),
10436
+ forged: fromHex(SCORE.forged),
10437
+ bold: fromHex(TEXT.primary),
10438
+ dim: fromHex(TEXT.muted)
10219
10439
  };
10220
10440
  const on = {
10221
10441
  ok: rgb(NORSE.ok),
@@ -12857,16 +13077,47 @@ function countBySeverity(result) {
12857
13077
  }
12858
13078
  return counts;
12859
13079
  }
13080
+ EVIDENCE.e0, EVIDENCE.e1, EVIDENCE.e2;
13081
+ const RUNG_MEANINGS = [
13082
+ "observation only",
13083
+ "heuristic static",
13084
+ "deterministic static",
13085
+ "the finding's file executed",
13086
+ "the finding's test executed",
13087
+ "the run verdict corroborates"
13088
+ ];
13089
+ const RUNG_COLORS = [
13090
+ TRUST.l0,
13091
+ TRUST.l1,
13092
+ TRUST.l2,
13093
+ TRUST.l3,
13094
+ TRUST.l4,
13095
+ TRUST.l5
13096
+ ];
13097
+ const TRUST_RUNGS = RUNG_MEANINGS.map((meaning, i) => ({
13098
+ level: `L${i}`,
13099
+ meaning,
13100
+ runtime: i >= 3,
13101
+ color: RUNG_COLORS[i]
13102
+ }));
12860
13103
  //#endregion
12861
13104
  //#region src/reporter/trust-report.ts
12862
- const TRUST_LABELS = {
12863
- L0: "L0 · observation only",
12864
- L1: "L1 · heuristic static",
12865
- L2: "L2 · deterministic static",
12866
- L3: "L3 · file executed",
12867
- L4: "L4 · test executed",
12868
- L5: "L5 · run corroborates defect"
12869
- };
13105
+ /**
13106
+ * The rung labels, built from `src/brand/symbols.ts` rather than typed
13107
+ * again here.
13108
+ *
13109
+ * They had drifted the moment there were two copies: this file said
13110
+ * "file executed" and "run corroborates defect" where the symbol module,
13111
+ * the architecture diagram and the website's ladder all said "the
13112
+ * finding's file executed" and "the run verdict corroborates". Small
13113
+ * enough that nobody would notice, and exactly the kind of divergence
13114
+ * that makes a reader wonder whether two surfaces mean the same thing.
13115
+ *
13116
+ * The runtime marker is not decoration either: L3 and above cannot be
13117
+ * reached without a real run report, and the label says so wherever the
13118
+ * ladder is not drawn to show it.
13119
+ */
13120
+ const TRUST_LABELS = Object.fromEntries(TRUST_RUNGS.map((r) => [r.level, `${r.level} · ${r.meaning}${r.runtime ? " · runtime" : ""}`]));
12870
13121
  function pct$1(v) {
12871
13122
  return `${Math.round(v * 100)}%`;
12872
13123
  }
@@ -13002,7 +13253,7 @@ function renderSarif(result, repoRootUri) {
13002
13253
  tool: { driver: {
13003
13254
  name: "Mjölnir",
13004
13255
  informationUri: "https://github.com/Sergey-Bar/Mjolnir",
13005
- version: "1.0.2",
13256
+ version: "1.0.3",
13006
13257
  rules: [...rules.values()].map((r) => {
13007
13258
  const meta = RULES.find((x) => x.id === r.id);
13008
13259
  return {
@@ -13131,6 +13382,22 @@ function renderCodeQuality(result) {
13131
13382
  }
13132
13383
  //#endregion
13133
13384
  //#region src/reporter/mermaid.ts
13385
+ /**
13386
+ * `--format mermaid` — test-architecture diagram (Sprint 9 Task 38,
13387
+ * Master-Stabilization-Plan.md). Ranked first among the delight
13388
+ * features because it is genuinely useful to QA leads presenting scan
13389
+ * results to stakeholders, not merely decorative — a flowchart of
13390
+ * detected frameworks → rule categories → severity buckets, so gaps
13391
+ * are visible at a glance in a format that pastes directly into a
13392
+ * GitHub/GitLab markdown comment or a slide (Mermaid renders natively
13393
+ * in both).
13394
+ *
13395
+ * Score-neutral (Sprint 9's own DoD line): this is a pure alternate
13396
+ * rendering of the exact same ScanResult every other format uses — it
13397
+ * changes no scoring, no exit code, no JSON contract field. Output is
13398
+ * fully deterministic: every collection is sorted before rendering, so
13399
+ * the same ScanResult always produces byte-identical Mermaid source.
13400
+ */
13134
13401
  function sanitizeId(raw) {
13135
13402
  return raw.replace(/[^a-z0-9]/gi, "_");
13136
13403
  }
@@ -13138,6 +13405,14 @@ function sanitizeId(raw) {
13138
13405
  function escapeLabel(text) {
13139
13406
  return text.replaceAll("\"", """).replaceAll("\n", " ");
13140
13407
  }
13408
+ /**
13409
+ * One Mermaid `classDef` line from one brand tint. Every colour this
13410
+ * renderer emits comes from `src/brand/tokens.ts`; it names none of its
13411
+ * own, and `brand-doctor` rule 6 fails if it starts to.
13412
+ */
13413
+ function classDef(name, tint) {
13414
+ return ` classDef ${name} fill:${tint.fill},stroke:${tint.stroke},color:${tint.text};`;
13415
+ }
13141
13416
  function dimensionStyleClass(dim) {
13142
13417
  if (dim.score >= 80) return "healthy";
13143
13418
  if (dim.score >= 50) return "warn";
@@ -13156,8 +13431,8 @@ function renderMermaid(result) {
13156
13431
  if (result.score === null) {
13157
13432
  lines.push(` ${rootId} --> NOTESTS["No test files detected"]`);
13158
13433
  lines.push("");
13159
- lines.push(" classDef critical fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d;");
13160
- lines.push(" class NOTESTS critical;");
13434
+ lines.push(classDef("unknown", TINT.neutral));
13435
+ lines.push(" class NOTESTS unknown;");
13161
13436
  return lines.join("\n");
13162
13437
  }
13163
13438
  const frameworks = [...result.frameworks].sort((a, b) => a.localeCompare(b));
@@ -13193,10 +13468,11 @@ function renderMermaid(result) {
13193
13468
  }
13194
13469
  }
13195
13470
  lines.push("");
13196
- lines.push(" classDef healthy fill:#dcfce7,stroke:#15803d,color:#14532d;");
13197
- lines.push(" classDef warn fill:#fef9c3,stroke:#a16207,color:#713f12;");
13198
- lines.push(" classDef critical fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d;");
13199
- lines.push(" classDef info fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a;");
13471
+ lines.push(classDef("healthy", TINT.ok));
13472
+ lines.push(classDef("warn", TINT.gold));
13473
+ lines.push(classDef("critical", TINT.error));
13474
+ lines.push(classDef("info", TINT.aurora));
13475
+ lines.push(classDef("unknown", TINT.neutral));
13200
13476
  for (const { id, cls } of styleAssignments) lines.push(` class ${id} ${cls};`);
13201
13477
  return lines.join("\n");
13202
13478
  }
@@ -15674,17 +15950,19 @@ function sweepStaleTempFiles(dir) {
15674
15950
  * retarget fixes the historical threshold drift (the badge used
15675
15951
  * ≥90/≥75/≥50 with four bands while the reporter used ≥80/≥50).
15676
15952
  *
15677
- * Shields.io has no cyan or white-gold, so the mapping is documented
15678
- * here: trusted → `important` (blue-family, closest to aurora-cyan),
15679
- * forged → `success` (the strongest positive signal shields offers).
15680
- * The badge is a peripheral surface; ScoreState remains the truth.
15953
+ * They are brand values now, from `BADGE_BAND`, not shields.io's named
15954
+ * colors. That mapping was documented as "trusted → `important`
15955
+ * (blue-family, closest to aurora-cyan)" and was simply untrue:
15956
+ * `important` resolves to #ea7233, which is orange. Every WORTHY badge
15957
+ * rendered the trusted band in a warning colour, and `success` — the
15958
+ * 100 state — rendered green, which is not a score colour here.
15959
+ *
15960
+ * ScoreState remains the truth; the badge is still a peripheral
15961
+ * surface. But peripheral is not the same as unchecked.
15681
15962
  */
15682
15963
  function colorFor(score) {
15683
15964
  const band = deriveScoreState(score).band;
15684
- if (band === "unmeasured") return "lightgrey";
15685
- if (band === "forged") return "success";
15686
- if (band === "trusted") return "important";
15687
- return band === "warning" ? "yellow" : "red";
15965
+ return BADGE_BAND[band];
15688
15966
  }
15689
15967
  /** Build the shields.io endpoint payload from a scan result. */
15690
15968
  function buildBadge(result, commit) {
@@ -15695,7 +15973,6 @@ function buildBadge(result, commit) {
15695
15973
  label: "MJÖLNIR",
15696
15974
  message: score === null ? "no tests found" : score === 100 && errors === 0 ? "100/100 · forged" : `${score}/100 · ${errors} error${errors === 1 ? "" : "s"}`,
15697
15975
  color: colorFor(score),
15698
- namedLogo: "vitest",
15699
15976
  ...commit !== void 0 ? { commit } : {}
15700
15977
  };
15701
15978
  }
@@ -19009,7 +19286,7 @@ const { runScan, buildUniversalRules, fallbackWorkspace, pathMatchesGlob, isVali
19009
19286
  * `scripts/sync-sarif-version.cjs` on release and guarded by
19010
19287
  * `tests/version-consistency.spec.ts` locally.
19011
19288
  */
19012
- const CLI_VERSION = "1.0.2";
19289
+ const CLI_VERSION = "1.0.3";
19013
19290
  function parseArgs(argv, onError) {
19014
19291
  const args = {
19015
19292
  target: ".",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mjolnir-qa",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
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": {
@@ -59,7 +59,13 @@
59
59
  "prepare": "husky",
60
60
  "prepublishOnly": "npm run build",
61
61
  "docs:flow": "tsx scripts/generate-readme-flow.ts",
62
- "docs:architecture": "tsx scripts/generate-readme-architecture.ts"
62
+ "docs:architecture": "tsx scripts/generate-readme-architecture.ts",
63
+ "brand:tokens": "tsx scripts/generate-brand-tokens.ts",
64
+ "brand:doctor": "node scripts/brand-doctor.mjs",
65
+ "brand:doctor:selftest": "node scripts/brand-doctor-selftest.mjs",
66
+ "brand:fonts": "tsx scripts/vendor-fonts.ts",
67
+ "brand:fonts:check": "tsx scripts/vendor-fonts.ts --check",
68
+ "brand:marks:update": "node scripts/update-mark-hashes.mjs"
63
69
  },
64
70
  "keywords": [
65
71
  "qa",