mjolnir-qa 4.0.0 → 5.0.0

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/dist/cli.d.mts CHANGED
@@ -1,3 +1,77 @@
1
+ //#region src/v6/test-doubles.d.ts
2
+ /** How serious the pattern is. Closed enum, so a renderer cannot invent a
3
+ * fourth severity at a call site. */
4
+ declare const DOUBLE_RISKS: readonly ["NO_ASSERTION", "DOUBLE_ONLY", "DOUBLE_MIXED", "DOUBLE_SUBJECT"];
5
+ type DoubleRisk = (typeof DOUBLE_RISKS)[number];
6
+ interface DoubleFinding {
7
+ risk: DoubleRisk;
8
+ /** How many assertions there are in total. */
9
+ assertions: number;
10
+ /** How many are about a double. */
11
+ doubleAssertions: number;
12
+ /** How many carry real evidence about the code under test. */
13
+ substantiatingAssertions: number;
14
+ /** 0..1 — the share of assertions that prove nothing. */
15
+ hollowRatio: number;
16
+ /** The one-line statement a surface can render verbatim. */
17
+ statement: string;
18
+ }
19
+ /**
20
+ * A roll-up across a suite. A suite is hollow when *no* test in it
21
+ * substantiates anything — which is a different and stronger statement
22
+ * than "some tests are hollow", and the one a user needs at the top of a
23
+ * report.
24
+ */
25
+ declare function assessSuite(findings: readonly DoubleFinding[]): {
26
+ tests: number;
27
+ hollow: number;
28
+ substantiating: number;
29
+ hollowRatio: number;
30
+ verdict: "SUBSTANTIATED" | "PARTIALLY_HOLLOW" | "HOLLOW";
31
+ };
32
+ //#endregion
33
+ //#region src/v6/tool-coverage.d.ts
34
+ /** What the engine can do with a detected tool. Closed enum, so a
35
+ * renderer cannot invent a fifth state. */
36
+ declare const TOOL_COVERAGE_STATES: readonly ["UNDERSTOOD", "DECLARED", "OUT_OF_SCOPE", "IGNORED"];
37
+ type ToolCoverageState = (typeof TOOL_COVERAGE_STATES)[number];
38
+ interface ToolCoverageEntry {
39
+ name: string;
40
+ state: ToolCoverageState;
41
+ /** The census category, when one applies. */
42
+ category: string | null;
43
+ /** The census entry id, when the engine has one. */
44
+ censusId: string | null;
45
+ /** What the cost of not handling it is, in one sentence. */
46
+ consequence: string;
47
+ }
48
+ interface BlindSpot {
49
+ id: string;
50
+ /** What the engine cannot see. */
51
+ blindTo: string;
52
+ /** Why it matters, in one sentence. */
53
+ consequence: string;
54
+ }
55
+ interface ToolCoverage {
56
+ /** Every QA tool the probe saw, with what the engine does about it. */
57
+ tools: readonly ToolCoverageEntry[];
58
+ counts: Readonly<Record<ToolCoverageState, number>>;
59
+ /** Tools the engine sees and cannot act on. */
60
+ unhandled: readonly ToolCoverageEntry[];
61
+ /**
62
+ * What the engine cannot see **through**, whether or not the tool is
63
+ * known. This is the part a user cannot infer, and the part that makes
64
+ * a green run mean something.
65
+ */
66
+ blindSpots: readonly BlindSpot[];
67
+ /** The double-analysis roll-up, when the caller supplied test data. */
68
+ suite: ReturnType<typeof assessSuite> | null;
69
+ /** One line a surface may render verbatim. */
70
+ statement: string;
71
+ /** Never a claim of completeness. */
72
+ complete: false;
73
+ }
74
+ //#endregion
1
75
  //#region src/types.d.ts
2
76
  /**
3
77
  * Mjolnir — canonical types (JSON contract v1, schemaVersion 1).
@@ -383,12 +457,42 @@ interface ScanResult {
383
457
  durationMs: number;
384
458
  /**
385
459
  * Named reasons the scan stopped early (audit H-8): "deadline",
386
- * "file-cap:<adapter>", "rule-loop-deadline". Present only when
387
- * truncation actually happened — absence means the scan is whole.
460
+ * "file-cap:<adapter>", "rule-loop-deadline", "ast-budget-fallback".
461
+ * Present only when truncation actually happened — absence means the
462
+ * scan is whole.
463
+ *
464
+ * Open `string[]`, NOT a closed union: a producer may add a member
465
+ * without a schema bump. The cost of that choice is that a consumer
466
+ * asserting the EXACT set breaks on every new reason, so assertions
467
+ * here are containment assertions, never `toEqual([...])`.
468
+ *
469
+ * "ast-budget-fallback" specifically means the scan ran out of time
470
+ * before a file could be analyzed through its adapter's AST stage, and
471
+ * that file was analyzed by regex instead. It is a capability loss, not
472
+ * a speed trade, and the count of affected files is in `reasons` as
473
+ * "ast-budget-fallback-files:<n>".
388
474
  */
389
475
  truncationReasons?: string[];
390
476
  /** Canonical completion reasons, including scope and parser degradation. */
391
477
  reasons?: string[];
478
+ /**
479
+ * Per-reason counts of capability lost inside a `catch` that returned a
480
+ * clean default — the degradation ledger (`src/engine/degradation-ledger.ts`).
481
+ *
482
+ * Distinct from `truncationReasons`, which means the scan STOPPED, and
483
+ * from `reasons`, which is a flat string set. This one is structured and
484
+ * reason-coded so "the AST mask was unavailable for 3 files" and "3 files
485
+ * were skipped" cannot be confused. Each entry is ALSO surfaced in
486
+ * `reasons` as `degraded:<reason>:<count>`.
487
+ *
488
+ * Present only when at least one degradation happened; absence means the
489
+ * scan lost no capability to a swallowed error, OR the producer predates
490
+ * the ledger. A non-empty value always forces `partial: true`.
491
+ */
492
+ degradations?: Array<{
493
+ reason: string;
494
+ count: number;
495
+ }>;
392
496
  /**
393
497
  * Rule executions that threw and were swallowed by crash isolation
394
498
  * (audit R-9). 0 means no rule silently failed; absence means the
@@ -429,6 +533,18 @@ interface ScanResult {
429
533
  /** The named scope reasons, present only when PARTIAL. */
430
534
  reasons?: string[];
431
535
  };
536
+ /**
537
+ * Tool coverage (v6): which QA tooling this scan actually saw, what the
538
+ * engine does about it, and what it is blind to.
539
+ *
540
+ * Present when the scan ran the ecosystem probe. Additive within
541
+ * schemaVersion 1; absent when the producer predates this field.
542
+ *
543
+ * complete is always false. A scan that could not see through a test
544
+ * double must never render as a scan with no blind spots, and that is
545
+ * the one claim a verification tool cannot make.
546
+ */
547
+ toolCoverage?: ToolCoverage;
432
548
  /**
433
549
  * Run Identity (R4c): the deterministic anchor binding verdict ←
434
550
  * evidence ← execution ← scope ← source ← rule(rev). Present when the
@@ -757,6 +873,53 @@ declare class DependencyGraph {
757
873
  has(path: string): boolean;
758
874
  }
759
875
  //#endregion
876
+ //#region src/engine/degradation-ledger.d.ts
877
+ /**
878
+ * One ledger for every silent capability loss.
879
+ *
880
+ * The defect class this exists to kill is not a deprecation warning. It is a
881
+ * `catch` in a DETECTION path that swallows an error and returns a clean
882
+ * default, so the scan still reports `analysisComplete` while quietly running
883
+ * with less capability than the reader was promised. Fifteen such sites were
884
+ * verified in this tree; the AST-layer ones are the worst, because the layer
885
+ * they degrade is the one that keeps prose comments and sample strings from
886
+ * becoming false-positive findings.
887
+ *
888
+ * Two shapes, one rule. The precedent already in the tree is
889
+ * `parserRetryDegradationCount` (`src/engine/tree-sitter-ast.ts`): a counted,
890
+ * readable number rather than a swallowed exception. This generalizes it —
891
+ * every site records a REASON from the closed set below, and the scan result
892
+ * carries the per-reason counts, so "the parser failed" becomes
893
+ * "the AST mask was unavailable for 3 files".
894
+ *
895
+ * The ledger is APPEND-ONLY and process-scoped, which has one consequence
896
+ * worth stating because it is the security-relevant direction: `beginWindow`
897
+ * returns a MARKER, not an ownership claim. Two concurrent `runScan` calls in
898
+ * one process (there is a test that does exactly this,
899
+ * `tests/stress/concurrent-same-process.spec.ts`) each read the records
900
+ * appended after their own marker, so a degradation one scan records may be
901
+ * counted by the other. That can only ever OVER-count — a scan reporting a
902
+ * degradation that happened to a sibling is more cautious than the truth, and
903
+ * never less — so the failure direction is the honest one. The alternative,
904
+ * a sink threaded through every rule context, is a diff across every call site
905
+ * in the tree to fix a problem that cannot make a scan look better than it is.
906
+ */
907
+ /**
908
+ * Every reason a detection path may lose capability.
909
+ *
910
+ * Closed on purpose: a `catch` site that wants a new reason has to add it
911
+ * here, which is the moment a reviewer sees the claim. `tests/contract/
912
+ * no-uncounted-degradation.spec.ts` reads this list and fails on any `catch`
913
+ * in a detection path that neither rethrows, records one of these, nor
914
+ * appears in its allowlist with a stated reason.
915
+ */
916
+ declare const DEGRADATION_REASONS: readonly ["ast-parse-failed", "ast-mask-unavailable", "ast-range-scan-failed", "ci-workflow-listing-unreadable", "workspace-manifest-unreadable", "java-build-listing-unreadable", "rules-tree-listing-unreadable", "trend-record-unparseable", "explain-fixture-unreadable", "explain-workflow-parse-failed", "explain-rule-crash", "rule-doc-rule-crash", "rule-doc-fixture-unreadable", "rule-doc-workflow-parse-failed"];
917
+ type DegradationReason = (typeof DEGRADATION_REASONS)[number];
918
+ interface DegradationCount {
919
+ readonly reason: DegradationReason;
920
+ readonly count: number;
921
+ }
922
+ //#endregion
760
923
  //#region src/discovery/workspace.d.ts
761
924
  /**
762
925
  * Repository discovery (Sprint-Plan W1-03).
@@ -1483,6 +1646,12 @@ interface FileAnalysisResult {
1483
1646
  parseFallbacks: number;
1484
1647
  scanned: number;
1485
1648
  analyzed: number;
1649
+ /**
1650
+ * Files that HAD an AST stage and lost it to the scan deadline, so they
1651
+ * were analyzed by regex alone. Zero for every adapter that declares no
1652
+ * `parseAst` — that is a capability envelope, not a downgrade.
1653
+ */
1654
+ astFallbackFiles: number;
1486
1655
  }
1487
1656
  declare function runFileAnalysisPhase(findings: Finding[], testFiles: string[], workspace: Workspace, activeRules: UniversalRule[], hooks: ScanHooks, cache: ScanCache, rulesDigest: string, deadline: number, truncationReasons: Set<string>, declarationsByFile: Map<string, number>, fileProvenance: Array<{
1488
1657
  path: string;
@@ -1530,6 +1699,14 @@ interface AssembleScanResultInput {
1530
1699
  scopeUnrecognized: number;
1531
1700
  parseFailed: number;
1532
1701
  parseFallbacks?: number;
1702
+ /** Files that lost their AST stage to the scan deadline. See completion.ts. */
1703
+ astFallbackFiles?: number;
1704
+ /**
1705
+ * Reason-coded capability losses recorded during this scan's window. See
1706
+ * `src/engine/degradation-ledger.ts` for why the window is an index rather
1707
+ * than an ownership claim.
1708
+ */
1709
+ degradations?: readonly DegradationCount[];
1533
1710
  scanned: number;
1534
1711
  analyzed?: number;
1535
1712
  testFiles: string[];
@@ -1585,7 +1762,7 @@ declare function runScan$1(args: CliArgs, hooks?: ScanHooks): Promise<ScanResult
1585
1762
  * scripts/sync-sarif-version.cjs and guarded by the version-consistency
1586
1763
  * spec. cli.ts re-exports this as CLI_VERSION.
1587
1764
  */
1588
- declare const ENGINE_VERSION = "4.0.0";
1765
+ declare const ENGINE_VERSION = "5.0.0";
1589
1766
  //#endregion
1590
1767
  //#region src/cli-io.d.ts
1591
1768
  /**
@@ -1897,7 +2074,7 @@ export declare const MAX_DURATION_MS = 3600000;
1897
2074
  export declare function parseArgs(argv: string[], onError?: (detail: UsageErrorDetail) => void): CliArgs | null;
1898
2075
  /** Hand-rolled Levenshtein distance (plan M2: no new dependencies). */
1899
2076
  export declare function levenshtein(a: string, b: string): number;
1900
- /** Nearest known flags within distance ≤ 2, nearest first. */
2077
+ /** Nearest known flags within distance ≤ 2, nearest first. */
1901
2078
  export declare function nearestFlags(flag: string, max?: number): string[];
1902
2079
  /**
1903
2080
  * Friendly usage error (plan M2, exit 10 preserved): nearest-flag