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.
- package/CHANGELOG.md +36 -0
- package/README.ar.md +4 -4
- package/README.bn.md +4 -4
- package/README.br.md +4 -4
- package/README.bs.md +4 -4
- package/README.da.md +4 -4
- package/README.de.md +4 -4
- package/README.es.md +4 -4
- package/README.fr.md +4 -4
- package/README.gr.md +4 -4
- package/README.he.md +4 -4
- package/README.it.md +4 -4
- package/README.ja.md +4 -4
- package/README.ko.md +4 -4
- package/README.md +5 -5
- package/README.no.md +4 -4
- package/README.pl.md +4 -4
- package/README.ru.md +4 -4
- package/README.th.md +4 -4
- package/README.tr.md +4 -4
- package/README.uk.md +4 -4
- package/README.vi.md +4 -4
- package/README.zh.md +4 -4
- package/README.zht.md +4 -4
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +353 -76
- package/dist/mcp/stdio.mjs +353 -76
- package/package.json +8 -2
package/dist/mcp/stdio.mjs
CHANGED
|
@@ -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:
|
|
10156
|
-
*
|
|
10157
|
-
*
|
|
10158
|
-
*
|
|
10159
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
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
|
-
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
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.
|
|
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("
|
|
13160
|
-
lines.push(" class NOTESTS
|
|
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("
|
|
13197
|
-
lines.push("
|
|
13198
|
-
lines.push("
|
|
13199
|
-
lines.push("
|
|
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
|
-
*
|
|
15678
|
-
*
|
|
15679
|
-
*
|
|
15680
|
-
*
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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",
|