mjolnir-qa 0.5.0 → 0.5.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 +1065 -0
- package/README.ar.md +667 -0
- package/README.bn.md +678 -0
- package/README.br.md +706 -0
- package/README.bs.md +690 -0
- package/README.da.md +698 -0
- package/README.de.md +711 -0
- package/README.es.md +709 -0
- package/README.fr.md +714 -0
- package/README.gr.md +705 -0
- package/README.he.md +664 -0
- package/README.it.md +709 -0
- package/README.ja.md +692 -0
- package/README.ko.md +681 -0
- package/README.md +369 -233
- package/README.no.md +696 -0
- package/README.pl.md +699 -0
- package/README.ru.md +701 -0
- package/README.th.md +672 -0
- package/README.tr.md +699 -0
- package/README.uk.md +693 -0
- package/README.vi.md +680 -0
- package/README.zh.md +652 -0
- package/README.zht.md +652 -0
- package/dist/cli.d.mts +497 -23
- package/dist/cli.mjs +8302 -2774
- package/package.json +23 -8
package/dist/cli.d.mts
CHANGED
|
@@ -34,6 +34,52 @@ type EvidenceLevel = (typeof EVIDENCE_ORDER)[number];
|
|
|
34
34
|
type QaImpact = "BLOCKS-RELEASE" | "FLAKY-RISK" | "FALSE-GREEN" | "HYGIENE";
|
|
35
35
|
/** Rule namespaces are frozen public API (§18.4). IDs are never reused. */
|
|
36
36
|
type RuleCategory = "QA-TEST" | "QA-TQUAL" | "QA-PW" | "QA-CI";
|
|
37
|
+
/**
|
|
38
|
+
* Trust levels (Verification Trust Evolution Plan §16): the OVERALL
|
|
39
|
+
* trust a consumer can place in one finding, combining the static
|
|
40
|
+
* evidence ladder (E0–E2) with RUNTIME corroboration from a real run
|
|
41
|
+
* report. Exposed honestly, never overclaimed:
|
|
42
|
+
* L0 — observation only (E0, no runtime evidence).
|
|
43
|
+
* L1 — heuristic static evidence (E1), no runtime evidence.
|
|
44
|
+
* L2 — deterministic static evidence (E2), no runtime evidence.
|
|
45
|
+
* L3 — RUNTIME: the file containing this finding appeared in a real
|
|
46
|
+
* run report (tests in that file executed).
|
|
47
|
+
* L4 — RUNTIME: the specific test containing this finding was
|
|
48
|
+
* identified in the report and executed (its outcome is known).
|
|
49
|
+
* L5 — RUNTIME: the run verdict directly corroborates the DEFECT
|
|
50
|
+
* class (e.g. a flake-risk finding whose test actually flaked,
|
|
51
|
+
* retried, or timed out in the report).
|
|
52
|
+
* INVARIANT (structurally enforced): L3–L5 require runtime
|
|
53
|
+
* corroboration — a static-only finding can never claim L4/L5.
|
|
54
|
+
*/
|
|
55
|
+
declare const TRUST_ORDER: readonly ["L0", "L1", "L2", "L3", "L4", "L5"];
|
|
56
|
+
type TrustLevel = (typeof TRUST_ORDER)[number];
|
|
57
|
+
/**
|
|
58
|
+
* Runtime corroboration for one finding (plan §16): what a real run
|
|
59
|
+
* report says about the code this finding points at. Additive within
|
|
60
|
+
* schemaVersion 1; absent means "no runtime evidence" — never
|
|
61
|
+
* fabricated.
|
|
62
|
+
*/
|
|
63
|
+
interface RuntimeCorroboration {
|
|
64
|
+
/** Granularity of what the runtime report could vouch for. */
|
|
65
|
+
level: "file" | "test" | "defect";
|
|
66
|
+
/** Report format the evidence came from. */
|
|
67
|
+
source: "playwright-json" | "junit-xml";
|
|
68
|
+
/** Number of tests executed in the finding's file (any level). */
|
|
69
|
+
testsExecuted: number;
|
|
70
|
+
/**
|
|
71
|
+
* The containing test's verdict, when the finding line falls inside a
|
|
72
|
+
* test the report identifies (level "test"/"defect").
|
|
73
|
+
*/
|
|
74
|
+
matchedTest?: {
|
|
75
|
+
title: string;
|
|
76
|
+
finalStatus: string;
|
|
77
|
+
attempts: number;
|
|
78
|
+
passedOnRetry: boolean;
|
|
79
|
+
everFailed: boolean;
|
|
80
|
+
skipped: boolean;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
37
83
|
interface Finding {
|
|
38
84
|
ruleId: string;
|
|
39
85
|
category: RuleCategory;
|
|
@@ -57,6 +103,19 @@ interface Finding {
|
|
|
57
103
|
measuredFpRate?: number;
|
|
58
104
|
/** Classified (TP+FP) verdicts behind `measuredFpRate`. */
|
|
59
105
|
measuredFpN?: number;
|
|
106
|
+
/**
|
|
107
|
+
* Runtime corroboration from a real run report (plan §16), stamped
|
|
108
|
+
* when a report was available and matched this finding's file/test.
|
|
109
|
+
* Absent means "no runtime evidence" — the static evidence ladder
|
|
110
|
+
* (E0–E2) is all the consumer has. Additive within schemaVersion 1.
|
|
111
|
+
*/
|
|
112
|
+
runtimeCorroboration?: RuntimeCorroboration;
|
|
113
|
+
/**
|
|
114
|
+
* Overall trust level (plan §16, see TRUST_ORDER). Derived
|
|
115
|
+
* deterministically from evidenceLevel + runtimeCorroboration;
|
|
116
|
+
* stamped with the corroboration pass. Additive within schemaVersion 1.
|
|
117
|
+
*/
|
|
118
|
+
trustLevel?: TrustLevel;
|
|
60
119
|
/** Repo-relative path with forward slashes, regardless of OS. */
|
|
61
120
|
file: string;
|
|
62
121
|
/** 1-based. */
|
|
@@ -101,14 +160,326 @@ interface ScanResult {
|
|
|
101
160
|
rawDeductions?: number;
|
|
102
161
|
/** Number of findings suppressed by active config entries (suppression transparency). */
|
|
103
162
|
suppressionCount?: number;
|
|
163
|
+
/**
|
|
164
|
+
* Third-party plugin code that executed during this scan (audit S-8).
|
|
165
|
+
* Plugins run with full Node privileges by documented design — anyone
|
|
166
|
+
* reading a report must be able to tell whether they ran. Absent
|
|
167
|
+
* when no plugins are configured.
|
|
168
|
+
*/
|
|
169
|
+
plugins?: Array<{
|
|
170
|
+
name: string;
|
|
171
|
+
rules: number;
|
|
172
|
+
}>;
|
|
173
|
+
/**
|
|
174
|
+
* Agentic Trust Profile (plan §17): per-scan provenance metadata —
|
|
175
|
+
* share of test files carrying detected generative markers and the
|
|
176
|
+
* findings split across those surfaces. PROVENANCE IS NOT TRUST: the
|
|
177
|
+
* profile never changes scoring, evidence levels, or tier behavior
|
|
178
|
+
* (§17.4 — the same evidence standard applies regardless of author).
|
|
179
|
+
* Additive within schemaVersion 1; present on every completed scan.
|
|
180
|
+
*/
|
|
181
|
+
agenticProfile?: {
|
|
182
|
+
testFiles: number;
|
|
183
|
+
generatedMarkedFiles: number;
|
|
184
|
+
codegenLikeFiles: number;
|
|
185
|
+
/** generatedMarkedFiles / testFiles (0..1). */
|
|
186
|
+
shareMarkedGenerated: number;
|
|
187
|
+
findingsInGeneratedFiles: number;
|
|
188
|
+
findingsInUnmarkedFiles: number;
|
|
189
|
+
note: string;
|
|
190
|
+
};
|
|
104
191
|
analysisStatus: {
|
|
105
192
|
discovery: AnalysisStatus;
|
|
106
193
|
rules: AnalysisStatus;
|
|
107
194
|
skippedFiles: number;
|
|
108
195
|
durationMs: number;
|
|
196
|
+
/**
|
|
197
|
+
* Named reasons the scan stopped early (audit H-8): "deadline",
|
|
198
|
+
* "file-cap:<adapter>", "rule-loop-deadline". Present only when
|
|
199
|
+
* truncation actually happened — absence means the scan is whole.
|
|
200
|
+
*/
|
|
201
|
+
truncationReasons?: string[];
|
|
202
|
+
/**
|
|
203
|
+
* Rule executions that threw and were swallowed by crash isolation
|
|
204
|
+
* (audit R-9). 0 means no rule silently failed; absence means the
|
|
205
|
+
* producer predates the counter.
|
|
206
|
+
*/
|
|
207
|
+
rulesCrashed?: number;
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Local incremental cache report (Beta-to-Stable plan, M5.2). Present
|
|
211
|
+
* only when the scan ran with `--cache`; additive within
|
|
212
|
+
* schemaVersion 1. The cache is content-addressed and local-only
|
|
213
|
+
* (plan A-2) — it never leaves the machine and never touches the
|
|
214
|
+
* network.
|
|
215
|
+
*/
|
|
216
|
+
cache?: {
|
|
217
|
+
/** Files whose rule verdicts were reused from the cache. */
|
|
218
|
+
hits: number;
|
|
219
|
+
/** Files analyzed fresh this run (cache misses). */
|
|
220
|
+
misses: number;
|
|
221
|
+
/** Absolute path of the cache file — auditable, gitignored. */
|
|
222
|
+
file: string;
|
|
109
223
|
};
|
|
110
224
|
}
|
|
111
225
|
//#endregion
|
|
226
|
+
//#region src/discovery/workspace.d.ts
|
|
227
|
+
/**
|
|
228
|
+
* Repository discovery (Sprint-Plan W1-03).
|
|
229
|
+
* Finds project root, parses package.json, detects npm/yarn/pnpm workspaces.
|
|
230
|
+
* Monorepo depth beyond workspaces is a documented launch cut (§29.1).
|
|
231
|
+
*/
|
|
232
|
+
interface Workspace {
|
|
233
|
+
/** Absolute path of the workspace/project root. */
|
|
234
|
+
root: string;
|
|
235
|
+
name: string;
|
|
236
|
+
packageJson: Record<string, unknown>;
|
|
237
|
+
/** Glob patterns from the root package.json workspaces field. */
|
|
238
|
+
workspaceGlobs: string[];
|
|
239
|
+
}
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region src/engine/tier-policy.d.ts
|
|
242
|
+
type Tier = "core" | "extended" | "quarantine";
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/rules/rule.d.ts
|
|
245
|
+
/**
|
|
246
|
+
* How the rule's primary detection decision is made (Verification Trust
|
|
247
|
+
* Evolution Plan §09.6/§12.1 — the enforced D6 enum, replacing free text):
|
|
248
|
+
* - "LEXICAL": pattern matching over source text (regex over `codeText`,
|
|
249
|
+
* masked text, YAML/manifest text, suite-wide absence sweeps).
|
|
250
|
+
* - "AST": structural analysis of a parsed syntax tree (ts-morph node
|
|
251
|
+
* walks, tree-sitter queries) is the core decision.
|
|
252
|
+
* - "SEMANTIC": name/type/symbol or call-graph reasoning beyond
|
|
253
|
+
* single-file syntax (reserved — no rule ships this yet).
|
|
254
|
+
* - "FRAMEWORK": framework configuration/manifest semantics drive the
|
|
255
|
+
* decision (CI workflow job/step structure, test-command gating).
|
|
256
|
+
* - "RUNTIME": execution evidence drives the decision (reserved —
|
|
257
|
+
* Phase 6).
|
|
258
|
+
*/
|
|
259
|
+
type DetectionStrategy = "LEXICAL" | "AST" | "SEMANTIC" | "FRAMEWORK" | "RUNTIME";
|
|
260
|
+
interface RuleMeta {
|
|
261
|
+
/** Frozen public API — never reused (§18.4). */
|
|
262
|
+
id: string;
|
|
263
|
+
category: RuleCategory;
|
|
264
|
+
title: string;
|
|
265
|
+
severity: Severity;
|
|
266
|
+
confidence: Confidence;
|
|
267
|
+
findingType: FindingType;
|
|
268
|
+
/** QA-native impact framing (#21): what this means for the QA engineer. */
|
|
269
|
+
qaImpact: QaImpact;
|
|
270
|
+
/**
|
|
271
|
+
* Honesty Core: explicit evidence level. When omitted, findings derive
|
|
272
|
+
* it from findingType+confidence (deriveEvidenceLevel). Only set this
|
|
273
|
+
* when the rule's evidence is genuinely stronger/weaker than the
|
|
274
|
+
* default derivation implies.
|
|
275
|
+
*/
|
|
276
|
+
evidenceLevel?: EvidenceLevel;
|
|
277
|
+
/** Rule IDs that can fire on the same root cause (dedup pass, R6). */
|
|
278
|
+
overlapWith?: string[];
|
|
279
|
+
/** Languages this rule applies to, e.g. ["typescript", "python"]. */
|
|
280
|
+
languages?: string[];
|
|
281
|
+
/** Frameworks the rule is meaningful for, e.g. ["jest", "vitest", "playwright"]. */
|
|
282
|
+
frameworks?: string[];
|
|
283
|
+
/**
|
|
284
|
+
* Declared false-positive risk of the rule as shipped. Part of the
|
|
285
|
+
* north-star contract: a rule that cannot honestly classify its FP risk
|
|
286
|
+
* should not be enforced.
|
|
287
|
+
*/
|
|
288
|
+
falsePositiveRisk?: "low" | "medium" | "high";
|
|
289
|
+
/** Whether `mjolnir fix` (or a future autofix) can safely repair it. */
|
|
290
|
+
autofix?: boolean;
|
|
291
|
+
/**
|
|
292
|
+
* How detection works, as an enforced enum (plan §09.6/§12.1 — D6
|
|
293
|
+
* closed). Free-text declarations were migrated to the enum in
|
|
294
|
+
* Phase 2; the registry ratchet (tests/rules.registry.spec.ts) makes
|
|
295
|
+
* omission or a bad value a CI failure, so new rules must declare it.
|
|
296
|
+
*/
|
|
297
|
+
detectionStrategy?: DetectionStrategy;
|
|
298
|
+
/**
|
|
299
|
+
* Verbatim legacy detection-strategy description preserved from the
|
|
300
|
+
* pre-enum free-text era (D6 migration). Optional; carries the nuance
|
|
301
|
+
* the enum alone cannot ("regex pattern + inside-string oracle", …).
|
|
302
|
+
* Rendered by the rule docs pages alongside the enum.
|
|
303
|
+
*/
|
|
304
|
+
detectionNotes?: string;
|
|
305
|
+
/** First released version (semver). Immutable once set. */
|
|
306
|
+
introduced?: string;
|
|
307
|
+
/**
|
|
308
|
+
* Tier assignment (Phase 4 — Tempering Plan; measurement-dependent
|
|
309
|
+
* default per Verification Trust Evolution Plan §11.2 Step 2).
|
|
310
|
+
* - "core": ships in the default report (≤10% measured FP rate)
|
|
311
|
+
* - "extended": included by default, lower confidence (≤30% FP)
|
|
312
|
+
* - "quarantine": opt-in only via --strict (>30% FP or unmeasured)
|
|
313
|
+
* When omitted, the tier resolves measurement-dependently via
|
|
314
|
+
* `effectiveTier` (src/rules/measurement.ts): core for rules with a
|
|
315
|
+
* valid corpus measurement, extended (displayed PROVISIONAL)
|
|
316
|
+
* otherwise — an unmeasured rule can never default into core.
|
|
317
|
+
*/
|
|
318
|
+
tier?: "core" | "extended" | "quarantine";
|
|
319
|
+
/**
|
|
320
|
+
* Detector implementation revision (Verification Trust Evolution Plan
|
|
321
|
+
* §07). Increment on ANY detection-logic change — pattern, scoping,
|
|
322
|
+
* AST adoption, rung change. A `MEASURED_FP` entry recorded against a
|
|
323
|
+
* different revision is stale: the measurement is invalidated,
|
|
324
|
+
* displayed as PROVISIONAL, and the rule cannot sit in effective core
|
|
325
|
+
* until re-measured (registry ratchet, §20.3). Default when omitted: 1
|
|
326
|
+
* (the current first-generation detectors).
|
|
327
|
+
*/
|
|
328
|
+
detectorRevision?: number;
|
|
329
|
+
/**
|
|
330
|
+
* This finding proves the reported pass does not cover what it claims.
|
|
331
|
+
*
|
|
332
|
+
* `.only` makes the runner skip every other test; a masked CI gate means a
|
|
333
|
+
* failure was ignored. Either way the suite's green is not evidence, and no
|
|
334
|
+
* amount of density normalization should be able to average that away — a
|
|
335
|
+
* two-test repo with `.only` is as compromised as a two-thousand-test one.
|
|
336
|
+
*
|
|
337
|
+
* Findings marked here cap the score into the UNWORTHY band regardless of
|
|
338
|
+
* exposure. Reserved for mechanisms where the bypass is unambiguous, not for
|
|
339
|
+
* findings that merely weaken a single test.
|
|
340
|
+
*/
|
|
341
|
+
suiteInvalidating?: boolean;
|
|
342
|
+
}
|
|
343
|
+
interface SourceFileContext {
|
|
344
|
+
/** Repo-relative path, forward slashes. */
|
|
345
|
+
path: string;
|
|
346
|
+
text: string;
|
|
347
|
+
/**
|
|
348
|
+
* Parsed AST provided by the engine — ts-morph SourceFile for
|
|
349
|
+
* TypeScript files, tree-sitter Tree for Java/C# (Phase 0.5 parse
|
|
350
|
+
* stage), workflow DOM for GitHub Actions. Typed as unknown here to
|
|
351
|
+
* keep the core rule contract decoupled; each language's helper
|
|
352
|
+
* narrows it (getTsSourceFile / getTreeSitterTree).
|
|
353
|
+
*/
|
|
354
|
+
ast?: unknown;
|
|
355
|
+
/**
|
|
356
|
+
* Code-only text view: string literals and comments blanked to spaces,
|
|
357
|
+
* newlines preserved so line/column indices stay exact (Phase 1 FP
|
|
358
|
+
* firewall). Rules that must never fire on prose inside strings or
|
|
359
|
+
* comments use this instead of `text`. Falls back to `text` when
|
|
360
|
+
* unavailable.
|
|
361
|
+
*/
|
|
362
|
+
codeText?: string;
|
|
363
|
+
}
|
|
364
|
+
type RuleFn = (ctx: SourceFileContext) => Omit<Finding, "ruleId" | "category">[];
|
|
365
|
+
/**
|
|
366
|
+
* Optional L2 structural-analysis hook (Verification Trust Evolution
|
|
367
|
+
* Plan §13.2). When the engine provides a parsed AST on the context
|
|
368
|
+
* (ts-morph SourceFile for TypeScript, tree-sitter Tree for Java/C#),
|
|
369
|
+
* the hook produces the findings; its regex path is the MANDATORY
|
|
370
|
+
* fallback, never optional — `undefined` return (or no `ctx.ast`)
|
|
371
|
+
* means "no AST — run the regex path", so fixture harnesses, grammar
|
|
372
|
+
* load failures, and degraded scans all keep working (ts-ast fallback
|
|
373
|
+
* discipline, QA-PW-002 pattern). This seam is what lets a rule
|
|
374
|
+
* declare `detectionStrategy: "AST"` honestly: the structural path is
|
|
375
|
+
* the decision when a tree exists, and the regex path is documented
|
|
376
|
+
* degraded detection, not a second source of truth.
|
|
377
|
+
*/
|
|
378
|
+
type AstQueryHook = (ctx: SourceFileContext) => Omit<Finding, "ruleId" | "category">[] | undefined;
|
|
379
|
+
type AppliesTo = "test-files" | "ci-workflows" | "python" | "java" | "csharp" | "all";
|
|
380
|
+
interface QADoctorRule extends RuleMeta {
|
|
381
|
+
/** Which file kinds this rule applies to. */
|
|
382
|
+
appliesTo: AppliesTo;
|
|
383
|
+
/**
|
|
384
|
+
* Config-hygiene rules: the engine only feeds these rules config
|
|
385
|
+
* files (and never feeds them test files), and never feeds test-file
|
|
386
|
+
* rules a config. Set on rules whose detection gates on a config
|
|
387
|
+
* filename (QA-PW-121/122/141/143/144). Without this flag the
|
|
388
|
+
* generic test rules would fire nonsense on configs (e.g. QA-TEST-003
|
|
389
|
+
* "no assertions" on every playwright.config.ts).
|
|
390
|
+
*/
|
|
391
|
+
configRule?: boolean;
|
|
392
|
+
/**
|
|
393
|
+
* Config filename patterns (regex SOURCE strings) this config rule
|
|
394
|
+
* gates on (plan §15.2): the adapter matches these against the file's
|
|
395
|
+
* basename, replacing the hard-coded playwright.config.* regex that
|
|
396
|
+
* used to live in the adapter AND duplicated inside each config rule.
|
|
397
|
+
* The internal regex gate in `run` stays as belt-and-suspenders for
|
|
398
|
+
* direct harness invocation.
|
|
399
|
+
*/
|
|
400
|
+
configFiles?: string[];
|
|
401
|
+
/**
|
|
402
|
+
* Framework opt-in (plan §15.1, defect D7): when declared, the rule
|
|
403
|
+
* runs on a file only if the file's own framework tags (its
|
|
404
|
+
* imports/usings) intersect it. Files without tags are always
|
|
405
|
+
* analyzed (open-when-unknown) — the dimension narrows, it never
|
|
406
|
+
* silently drops evidence. Mirror of UniversalRule.frameworks,
|
|
407
|
+
* threaded through asUniversal.
|
|
408
|
+
*/
|
|
409
|
+
frameworksOverride?: string[];
|
|
410
|
+
/**
|
|
411
|
+
* L2 structural-analysis path (§13.2): runs when the engine supplies
|
|
412
|
+
* a parsed AST for the file. MUST be paired with a regex fallback in
|
|
413
|
+
* `run` (mandatory fallback discipline) — see AstQueryHook.
|
|
414
|
+
*/
|
|
415
|
+
astQuery?: AstQueryHook;
|
|
416
|
+
run: RuleFn;
|
|
417
|
+
}
|
|
418
|
+
//#endregion
|
|
419
|
+
//#region src/engine/adapter.d.ts
|
|
420
|
+
/** Semantic operations a parsed file exposes to rules. */
|
|
421
|
+
interface ParsedFile {
|
|
422
|
+
path: string;
|
|
423
|
+
text: string;
|
|
424
|
+
/** Adapter-specific AST; typed loosely until tree-sitter unifies it. */
|
|
425
|
+
ast?: unknown;
|
|
426
|
+
/**
|
|
427
|
+
* Code-only text view: string literals and comments blanked to spaces,
|
|
428
|
+
* newlines preserved so line/column indices stay exact. Regex rules
|
|
429
|
+
* that must never fire on prose inside strings or comments use this
|
|
430
|
+
* instead of `text`. Computed lazily per adapter.
|
|
431
|
+
*/
|
|
432
|
+
codeText?: string;
|
|
433
|
+
/**
|
|
434
|
+
* Per-file framework tags (Verification Trust Evolution Plan §15.1,
|
|
435
|
+
* defect D7): derived from the file's OWN imports/usings/imports-lines
|
|
436
|
+
* by the adapter ("playwright", "jest", "cypress", "junit", "testng",
|
|
437
|
+
* "selenium", "nunit", "xunit", "mstest", "pytest", …). EMPTY/absent
|
|
438
|
+
* means "no per-file evidence" — framework filtering is then OPEN (a
|
|
439
|
+
* rule declaring `frameworks` still runs), never a silent skip.
|
|
440
|
+
*/
|
|
441
|
+
frameworkTags?: readonly string[];
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* A rule that declares which adapters it supports. Backward compatible:
|
|
445
|
+
* legacy 'test-files' maps to ['typescript'], 'ci-workflows' to
|
|
446
|
+
* ['github-actions'].
|
|
447
|
+
*/
|
|
448
|
+
interface UniversalRule {
|
|
449
|
+
id: string;
|
|
450
|
+
category: string;
|
|
451
|
+
appliesTo: readonly string[];
|
|
452
|
+
/**
|
|
453
|
+
* Config-hygiene rule (see QADoctorRule.configRule): the adapter runs
|
|
454
|
+
* these ONLY on the config files named in `configFiles`, and runs
|
|
455
|
+
* every other rule only on test files. Keeps config rules measurable
|
|
456
|
+
* in real scans without letting generic test rules fire nonsense on
|
|
457
|
+
* configs.
|
|
458
|
+
*/
|
|
459
|
+
configOnly?: boolean;
|
|
460
|
+
/**
|
|
461
|
+
* Config filename patterns (regex sources) this config rule gates on
|
|
462
|
+
* (plan §15.2 — replaces the hard-coded playwright.config.* regex
|
|
463
|
+
* that used to live in the TS adapter AND duplicated inside each
|
|
464
|
+
* config rule). Empty/absent + configOnly=true falls back to the
|
|
465
|
+
* adapter's built-in config list.
|
|
466
|
+
*/
|
|
467
|
+
configFiles?: readonly string[];
|
|
468
|
+
/**
|
|
469
|
+
* Framework opt-in (plan §15.1, defect D7): when declared, the rule
|
|
470
|
+
* runs on a file only if the file's own `frameworkTags` intersect it.
|
|
471
|
+
* Files without tags are always analyzed (open-when-unknown).
|
|
472
|
+
*/
|
|
473
|
+
frameworks?: readonly string[];
|
|
474
|
+
/**
|
|
475
|
+
* Detector implementation revision (§07), threaded through asUniversal
|
|
476
|
+
* so the M5.2 cache digest can fold it in; the stale-measurement
|
|
477
|
+
* machinery reads it from the registry, the cache from this field.
|
|
478
|
+
*/
|
|
479
|
+
detectorRevision?: number;
|
|
480
|
+
run(file: ParsedFile): Array<Omit<Finding, "ruleId" | "category">>;
|
|
481
|
+
}
|
|
482
|
+
//#endregion
|
|
112
483
|
//#region src/cli.d.ts
|
|
113
484
|
/**
|
|
114
485
|
* Tool version for `mjolnir --version`.
|
|
@@ -120,7 +491,17 @@ interface ScanResult {
|
|
|
120
491
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
121
492
|
* `tests/version-consistency.spec.ts` locally.
|
|
122
493
|
*/
|
|
123
|
-
declare const CLI_VERSION = "0.5.
|
|
494
|
+
declare const CLI_VERSION = "0.5.3";
|
|
495
|
+
declare function buildUniversalRules(root: string, strict?: boolean): Promise<{
|
|
496
|
+
rules: UniversalRule[];
|
|
497
|
+
pluginErrors: string[];
|
|
498
|
+
tierByRuleId: Map<string, Tier>;
|
|
499
|
+
pluginMeta: Array<{
|
|
500
|
+
name: string;
|
|
501
|
+
rules: number;
|
|
502
|
+
}>;
|
|
503
|
+
externalRules: QADoctorRule[];
|
|
504
|
+
}>;
|
|
124
505
|
interface CliArgs {
|
|
125
506
|
target: string;
|
|
126
507
|
json: boolean;
|
|
@@ -136,17 +517,81 @@ interface CliArgs {
|
|
|
136
517
|
tone?: "blunt";
|
|
137
518
|
/** --strict: include quarantine-tier rules in the scan (Phase 4). */
|
|
138
519
|
strict?: boolean;
|
|
520
|
+
/** --base <ref>: base ref for --scope changed (audit H-10). */
|
|
521
|
+
base?: string;
|
|
522
|
+
/** --debug: print errors swallowed by crash isolation (audit R-9). */
|
|
523
|
+
debug?: boolean;
|
|
524
|
+
/** --record-milestones: let a scan write .mjolnir/stats.json (audit R-1). */
|
|
525
|
+
recordMilestones?: boolean;
|
|
526
|
+
/**
|
|
527
|
+
* --cache: reuse per-file rule verdicts from the local content-addressed
|
|
528
|
+
* cache (M5.2). Post-loop processing always re-runs; the cache only
|
|
529
|
+
* short-circuits the read+parse+rule loop for byte-identical files
|
|
530
|
+
* under an unchanged rule set. Local-only, never leaves the machine.
|
|
531
|
+
*/
|
|
532
|
+
cache?: boolean;
|
|
533
|
+
/**
|
|
534
|
+
* --no-progress: never render the live scan-progress line, even on an
|
|
535
|
+
* interactive TTY (plan M3, additive flag). Progress is stderr-only
|
|
536
|
+
* and auto-disabled in CI/machine formats; this flag is the manual off.
|
|
537
|
+
*/
|
|
538
|
+
noProgress?: boolean;
|
|
139
539
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
540
|
+
/** A usage-error detail: the offending token, when one exists. */
|
|
541
|
+
interface UsageErrorDetail {
|
|
542
|
+
/** The unknown flag or rejected value (e.g. `--nope`, `loud`). */
|
|
543
|
+
token?: string | undefined;
|
|
544
|
+
/** The flag whose value was rejected (`--tone` for `--tone loud`). */
|
|
545
|
+
flag?: string | undefined;
|
|
546
|
+
}
|
|
547
|
+
declare function parseArgs(argv: string[], onError?: (detail: UsageErrorDetail) => void): CliArgs | null;
|
|
548
|
+
/** Hand-rolled Levenshtein distance (plan M2: no new dependencies). */
|
|
549
|
+
declare function levenshtein(a: string, b: string): number;
|
|
550
|
+
/** Nearest known flags within distance ≤ 2, nearest first. */
|
|
551
|
+
declare function nearestFlags(flag: string, max?: number): string[];
|
|
143
552
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* "tests/foo.spec.ts" — exact path
|
|
148
|
-
* Forward slashes only (findings always use normalized paths).
|
|
553
|
+
* Friendly usage error (plan M2, exit 10 preserved): nearest-flag
|
|
554
|
+
* suggestion, the valid neighbors, and the exact help command. Printed
|
|
555
|
+
* to stderr; findings/usage stay on their documented streams.
|
|
149
556
|
*/
|
|
557
|
+
declare function usageErrorMessage(detail: UsageErrorDetail): string;
|
|
558
|
+
interface ScanHooks {
|
|
559
|
+
/** Invoked when a rule throws on a file (audit R-9). */
|
|
560
|
+
onRuleCrash?: (ruleId: string, file: string, error: unknown) => void;
|
|
561
|
+
/** Invoked for non-fatal config warnings (bug-audit M4). */
|
|
562
|
+
onConfigWarning?: (message: string) => void;
|
|
563
|
+
/**
|
|
564
|
+
* Live-progress feed (plan M3, additive). Fired from the per-file
|
|
565
|
+
* parse+rules loop and the phase boundaries. Render-on-event only —
|
|
566
|
+
* the scan never waits on a timer, and output contracts are
|
|
567
|
+
* unchanged when the hook is absent.
|
|
568
|
+
*/
|
|
569
|
+
onProgress?: (e: {
|
|
570
|
+
phase: "discover" | "parse" | "rules" | "score";
|
|
571
|
+
done?: number | undefined;
|
|
572
|
+
total?: number | undefined;
|
|
573
|
+
detail?: string | undefined;
|
|
574
|
+
}) => void;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Workspace fallback for targets with no discoverable project root
|
|
578
|
+
* (package.json-less repos, Python/Java/C# trees). Exported pure so the
|
|
579
|
+
* root-path degenerate case (`C:\` → basename "") is testable without
|
|
580
|
+
* scanning a filesystem root.
|
|
581
|
+
*/
|
|
582
|
+
declare function fallbackWorkspace(targetAbs: string): Workspace;
|
|
583
|
+
/**
|
|
584
|
+
* Testable default scan path core. `hooks` lets callers observe
|
|
585
|
+
* normally-invisible events (swallowed rule crashes) without changing
|
|
586
|
+
* the ScanResult contract beyond the rulesCrashed counter.
|
|
587
|
+
*
|
|
588
|
+
* Async since the Verification Trust Evolution Plan Phase 0.5 (§10): the
|
|
589
|
+
* per-file loop awaits the adapter parse stage (WASM grammar load is
|
|
590
|
+
* inherently async); `runRules` and every rule stay synchronous and
|
|
591
|
+
* consume `ParsedFile.ast`. Callers await the returned promise.
|
|
592
|
+
*/
|
|
593
|
+
declare function runScan(args: CliArgs, hooks?: ScanHooks): Promise<ScanResult>;
|
|
594
|
+
type Output = (...parts: unknown[]) => void;
|
|
150
595
|
declare function pathMatchesGlob(path: string, glob: string): boolean;
|
|
151
596
|
/** Testable `ci install` handler. Returns the process exit code. */
|
|
152
597
|
declare function runCiInstall(argv: string[], io?: {
|
|
@@ -156,6 +601,7 @@ declare function runCiInstall(argv: string[], io?: {
|
|
|
156
601
|
/** Testable `suppressions` handler. */
|
|
157
602
|
declare function runSuppressions(io?: {
|
|
158
603
|
out: Output;
|
|
604
|
+
err?: Output;
|
|
159
605
|
}): number;
|
|
160
606
|
/** Testable `forensics` handler. */
|
|
161
607
|
declare function runForensicsCommand(argv: string[], io?: {
|
|
@@ -165,7 +611,8 @@ declare function runForensicsCommand(argv: string[], io?: {
|
|
|
165
611
|
/** Testable `doctor:playwright` handler. */
|
|
166
612
|
declare function runDoctorPlaywright(argv: string[], io?: {
|
|
167
613
|
out: Output;
|
|
168
|
-
|
|
614
|
+
err?: Output;
|
|
615
|
+
}): Promise<number>;
|
|
169
616
|
/** Testable `doctor` handler — self-audit of Mjölnir's own rule base. */
|
|
170
617
|
declare function runDoctorCommand(argv: string[], io?: {
|
|
171
618
|
out: Output;
|
|
@@ -175,7 +622,7 @@ declare function runDoctorCommand(argv: string[], io?: {
|
|
|
175
622
|
declare function runRulesCommand(argv: string[], io?: {
|
|
176
623
|
out: Output;
|
|
177
624
|
err: Output;
|
|
178
|
-
}): number
|
|
625
|
+
}): Promise<number>;
|
|
179
626
|
/**
|
|
180
627
|
* Testable `explain <RULE-ID>` handler (Plan.md Sprint 1.3,
|
|
181
628
|
* Master-Stabilization-Plan Sprint 5 Task 19). Metadata always renders
|
|
@@ -188,11 +635,16 @@ declare function runExplainCommand(argv: string[], io?: {
|
|
|
188
635
|
out: Output;
|
|
189
636
|
err: Output;
|
|
190
637
|
}): number;
|
|
191
|
-
/** Testable default scan path. */
|
|
192
638
|
declare function runScanCommand(argv: string[], io?: {
|
|
193
639
|
out: Output;
|
|
194
640
|
err: Output;
|
|
195
|
-
}): number
|
|
641
|
+
}): Promise<number>;
|
|
642
|
+
/**
|
|
643
|
+
* Exit-code decision for a finished scan under the given gate level
|
|
644
|
+
* (audit H-7): the previously-dead config.gate field now selects which
|
|
645
|
+
* severities block. Advisory (E0) findings never gate at any level.
|
|
646
|
+
*/
|
|
647
|
+
declare function exitForFindings(findings: readonly Finding[], gate: "advisory" | "error" | "warning"): number;
|
|
196
648
|
/** Testable `triage` handler (Tier 5 #22). */
|
|
197
649
|
declare function runTriageCommand(argv: string[], io?: {
|
|
198
650
|
out: Output;
|
|
@@ -202,17 +654,17 @@ declare function runTriageCommand(argv: string[], io?: {
|
|
|
202
654
|
declare function runBadgeCommand(argv: string[], io?: {
|
|
203
655
|
out: Output;
|
|
204
656
|
err: Output;
|
|
205
|
-
}): number
|
|
657
|
+
}): Promise<number>;
|
|
206
658
|
/** Testable `debt` handler (Tier 5 #27). */
|
|
207
659
|
declare function runDebtCommand(argv: string[], io?: {
|
|
208
660
|
out: Output;
|
|
209
661
|
err: Output;
|
|
210
|
-
}): number
|
|
662
|
+
}): Promise<number>;
|
|
211
663
|
/** Testable `fix` handler (Tier 1 #3) — safe auto-fix with proof. */
|
|
212
664
|
declare function runFixCommand(argv: string[], io?: {
|
|
213
665
|
out: Output;
|
|
214
666
|
err: Output;
|
|
215
|
-
}): number
|
|
667
|
+
}): Promise<number>;
|
|
216
668
|
/** Testable `create-rule` handler (Tier 6 #34). */
|
|
217
669
|
declare function runCreateRuleCommand(argv: string[], io?: {
|
|
218
670
|
out: Output;
|
|
@@ -222,22 +674,22 @@ declare function runCreateRuleCommand(argv: string[], io?: {
|
|
|
222
674
|
declare function runImpactCommand(argv: string[], io?: {
|
|
223
675
|
out: Output;
|
|
224
676
|
err: Output;
|
|
225
|
-
}): number
|
|
677
|
+
}): Promise<number>;
|
|
226
678
|
/** Testable `baseline` handler (Sprint 6 Task 24). */
|
|
227
679
|
declare function runBaselineCommand(argv: string[], io?: {
|
|
228
680
|
out: Output;
|
|
229
681
|
err: Output;
|
|
230
|
-
}): number
|
|
682
|
+
}): Promise<number>;
|
|
231
683
|
/** Testable `diff` handler (Sprint 6 Task 24) — new/worsened debt only. */
|
|
232
684
|
declare function runDiffCommand(argv: string[], io?: {
|
|
233
685
|
out: Output;
|
|
234
686
|
err: Output;
|
|
235
|
-
}): number
|
|
687
|
+
}): Promise<number>;
|
|
236
688
|
/** Testable `pr-comment` handler (Sprint 6 Task 25). */
|
|
237
689
|
declare function runPrCommentCommand(argv: string[], io?: {
|
|
238
690
|
out: Output;
|
|
239
691
|
err: Output;
|
|
240
|
-
}): number
|
|
692
|
+
}): Promise<number>;
|
|
241
693
|
/** Testable `stats` handler (Sprint 6 Task 26). */
|
|
242
694
|
declare function runStatsCommand(argv: string[], io?: {
|
|
243
695
|
out: Output;
|
|
@@ -247,7 +699,7 @@ declare function runStatsCommand(argv: string[], io?: {
|
|
|
247
699
|
declare function runHandoverCommand(argv: string[], io?: {
|
|
248
700
|
out: Output;
|
|
249
701
|
err: Output;
|
|
250
|
-
}): number
|
|
702
|
+
}): Promise<number>;
|
|
251
703
|
/** Testable `init` handler (Tier 2 #10). */
|
|
252
704
|
declare function runInitCommand(argv: string[], io?: {
|
|
253
705
|
out: Output;
|
|
@@ -258,7 +710,29 @@ declare function runPwReportCommand(argv: string[], io?: {
|
|
|
258
710
|
out: Output;
|
|
259
711
|
err: Output;
|
|
260
712
|
}): number;
|
|
261
|
-
declare function main(argv?: string[]
|
|
713
|
+
declare function main(argv?: string[], io?: {
|
|
714
|
+
out: Output;
|
|
715
|
+
err: Output;
|
|
716
|
+
}): Promise<number>;
|
|
717
|
+
/**
|
|
718
|
+
* `mjolnir help` / `mjolnir help <verb>` (plan M2). `--help`/`-h` and
|
|
719
|
+
* `<verb> --help` route here too. Exit 0 — help answers a question.
|
|
720
|
+
* Two-word verbs (`ci install`) are resolved first via the join of the
|
|
721
|
+
* leading non-flag tokens, then the single-word form.
|
|
722
|
+
*/
|
|
723
|
+
declare function runHelpCommand(argv: string[], io?: {
|
|
724
|
+
out: Output;
|
|
725
|
+
err: Output;
|
|
726
|
+
}): number;
|
|
727
|
+
/**
|
|
728
|
+
* Friendly exit-20 path (plan M2): the crash says it's Mjölnir's bug,
|
|
729
|
+
* not the user's repo, carries the underlying message for a report, and
|
|
730
|
+
* prints the stack ONLY when `debug` is set (uniform across
|
|
731
|
+
* subcommands — they don't parse scan flags). Tests pin
|
|
732
|
+
* /internal error/i. Exported so the --debug stack arm is directly
|
|
733
|
+
* spec-coverable (spawning a real crash under --debug would be flaky).
|
|
734
|
+
*/
|
|
735
|
+
declare function internalErrorMessage(err: unknown, emit: (s: string) => void, debug: boolean): void;
|
|
262
736
|
declare function isEntryPoint(): boolean;
|
|
263
737
|
//#endregion
|
|
264
|
-
export { CLI_VERSION, Output, isEntryPoint, main, parseArgs, pathMatchesGlob, runBadgeCommand, runBaselineCommand, runCiInstall, runCreateRuleCommand, runDebtCommand, runDiffCommand, runDoctorCommand, runDoctorPlaywright, runExplainCommand, runFixCommand, runForensicsCommand, runHandoverCommand, runImpactCommand, runInitCommand, runPrCommentCommand, runPwReportCommand, runRulesCommand, runScan, runScanCommand, runStatsCommand, runSuppressions, runTriageCommand };
|
|
738
|
+
export { CLI_VERSION, Output, ScanHooks, UsageErrorDetail, buildUniversalRules, exitForFindings, fallbackWorkspace, internalErrorMessage, isEntryPoint, levenshtein, main, nearestFlags, parseArgs, pathMatchesGlob, runBadgeCommand, runBaselineCommand, runCiInstall, runCreateRuleCommand, runDebtCommand, runDiffCommand, runDoctorCommand, runDoctorPlaywright, runExplainCommand, runFixCommand, runForensicsCommand, runHandoverCommand, runHelpCommand, runImpactCommand, runInitCommand, runPrCommentCommand, runPwReportCommand, runRulesCommand, runScan, runScanCommand, runStatsCommand, runSuppressions, runTriageCommand, usageErrorMessage };
|