mjolnir-qa 6.0.0 → 6.0.1
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 +384 -1
- package/README.md +314 -29
- package/dist/cli.d.mts +1 -477
- package/dist/cli.mjs +664 -554
- package/dist/mcp/stdio.mjs +254 -366
- package/package.json +24 -34
- package/dist/rolldown-runtime-8H4AJuhK.mjs +0 -14
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
3
2
|
import { createRequire } from "node:module";
|
|
4
|
-
import { accessSync, chmodSync, closeSync, constants, copyFileSync, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
3
|
+
import { accessSync, appendFileSync, chmodSync, closeSync, constants, copyFileSync, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
5
4
|
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
5
|
import process$1 from "node:process";
|
|
7
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -17,7 +16,18 @@ import { createInterface } from "node:readline";
|
|
|
17
16
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
18
17
|
import { isDeepStrictEqual } from "node:util";
|
|
19
18
|
import { tmpdir } from "node:os";
|
|
20
|
-
|
|
19
|
+
// \0rolldown/runtime.js
|
|
20
|
+
var __defProp = Object.defineProperty;
|
|
21
|
+
var __exportAll = (all, no_symbols) => {
|
|
22
|
+
let target = {};
|
|
23
|
+
for (var name in all) __defProp(target, name, {
|
|
24
|
+
get: all[name],
|
|
25
|
+
enumerable: true
|
|
26
|
+
});
|
|
27
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
28
|
+
return target;
|
|
29
|
+
};
|
|
30
|
+
// src/types.ts
|
|
21
31
|
/** Severity ladder. Order matters for sorting and gating. */
|
|
22
32
|
const SEVERITY_ORDER$1 = [
|
|
23
33
|
"error",
|
|
@@ -146,8 +156,7 @@ const DEDUCTIONS = {
|
|
|
146
156
|
warning: 3,
|
|
147
157
|
info: 1
|
|
148
158
|
};
|
|
149
|
-
|
|
150
|
-
//#region src/claim-evidence.ts
|
|
159
|
+
// src/claim-evidence.ts
|
|
151
160
|
/**
|
|
152
161
|
* The one place a rendered surface decides whether it may claim "clean".
|
|
153
162
|
*
|
|
@@ -204,8 +213,7 @@ function scanExitCode(input) {
|
|
|
204
213
|
if (input.gate === "advisory") return 0;
|
|
205
214
|
return input.findings.some((f) => isAtGate(f, input.gate, input.isAdvisory)) ? 1 : 0;
|
|
206
215
|
}
|
|
207
|
-
|
|
208
|
-
//#region src/engine/completion.ts
|
|
216
|
+
// src/engine/completion.ts
|
|
209
217
|
function deriveCompletion(input) {
|
|
210
218
|
const truncationReasons = [...new Set(input.truncationReasons)].sort();
|
|
211
219
|
const degradations = [...input.degradations ?? []].sort((a, b) => a.reason < b.reason ? -1 : a.reason > b.reason ? 1 : 0);
|
|
@@ -247,8 +255,7 @@ function deriveCompletion(input) {
|
|
|
247
255
|
}
|
|
248
256
|
};
|
|
249
257
|
}
|
|
250
|
-
|
|
251
|
-
//#region src/engine/degradation-ledger.ts
|
|
258
|
+
// src/engine/degradation-ledger.ts
|
|
252
259
|
/**
|
|
253
260
|
* Append-only. Records are never edited and never removed by a recording
|
|
254
261
|
* site; a reader takes a window, and the window is defined by index, so
|
|
@@ -284,8 +291,7 @@ function summarizeDegradations(window) {
|
|
|
284
291
|
count
|
|
285
292
|
})).sort((a, b) => a.reason < b.reason ? -1 : a.reason > b.reason ? 1 : 0);
|
|
286
293
|
}
|
|
287
|
-
|
|
288
|
-
//#region src/engine/runtime-corroboration.ts
|
|
294
|
+
// src/engine/runtime-corroboration.ts
|
|
289
295
|
/**
|
|
290
296
|
* Stamp runtime corroboration + trust levels onto findings (mutates in
|
|
291
297
|
* place, the same contract as stampEvidenceLevels). Returns the number
|
|
@@ -367,8 +373,7 @@ function deriveTrustLevel(finding, corroboration) {
|
|
|
367
373
|
if (corroboration.level === "test") return "L4";
|
|
368
374
|
return "L3";
|
|
369
375
|
}
|
|
370
|
-
|
|
371
|
-
//#region src/rules/measured-fp.generated.ts
|
|
376
|
+
// src/rules/measured-fp.generated.ts
|
|
372
377
|
const MEASURED_FP = {
|
|
373
378
|
"QA-CI-001": {
|
|
374
379
|
fpRate: .105,
|
|
@@ -1429,8 +1434,7 @@ const MEASURED_FP_RAW = {
|
|
|
1429
1434
|
ciHigh: .4343
|
|
1430
1435
|
}
|
|
1431
1436
|
};
|
|
1432
|
-
|
|
1433
|
-
//#region src/engine/trust-summary.ts
|
|
1437
|
+
// src/engine/trust-summary.ts
|
|
1434
1438
|
/**
|
|
1435
1439
|
* Trust level of the summary — the BEST trust level any finding in the
|
|
1436
1440
|
* scan reached. Static-only findings settle at L2 (deriveTrustLevel's
|
|
@@ -1554,8 +1558,7 @@ function buildTrustSummary(result, declarationsByFile) {
|
|
|
1554
1558
|
ceilingReasons: reasons
|
|
1555
1559
|
};
|
|
1556
1560
|
}
|
|
1557
|
-
|
|
1558
|
-
//#region src/governance/run-mode.ts
|
|
1561
|
+
// src/governance/run-mode.ts
|
|
1559
1562
|
/**
|
|
1560
1563
|
* Which mode a run is in, and how the other one is reached.
|
|
1561
1564
|
*
|
|
@@ -1606,8 +1609,7 @@ function resolveRunMode(input) {
|
|
|
1606
1609
|
if (declared === "local-only" || declared === "hosted") return declared;
|
|
1607
1610
|
throw new Error(`${HOSTED_MODE_FLAG}=${String(declared)} is not a run mode. Expected "hosted" or "local-only". Falling back to a default here would let a mistyped flag look like a deliberate choice.`);
|
|
1608
1611
|
}
|
|
1609
|
-
|
|
1610
|
-
//#region src/engine/run-identity.ts
|
|
1612
|
+
// src/engine/run-identity.ts
|
|
1611
1613
|
/**
|
|
1612
1614
|
* Run Identity (product-gap master plan §7, plan 1789009691197 R4c) —
|
|
1613
1615
|
* the deterministic binding that anchors every trust claim to its
|
|
@@ -1736,8 +1738,7 @@ function buildEvidenceGraph(parts) {
|
|
|
1736
1738
|
} } : {}
|
|
1737
1739
|
};
|
|
1738
1740
|
}
|
|
1739
|
-
|
|
1740
|
-
//#region src/engine/file-executor.ts
|
|
1741
|
+
// src/engine/file-executor.ts
|
|
1741
1742
|
function cacheHitOutcome(job) {
|
|
1742
1743
|
return {
|
|
1743
1744
|
path: job.path,
|
|
@@ -1828,8 +1829,7 @@ function createExecutor(name, concurrency, runOne) {
|
|
|
1828
1829
|
execute: runOne
|
|
1829
1830
|
};
|
|
1830
1831
|
}
|
|
1831
|
-
|
|
1832
|
-
//#region src/scope/git-resolve.ts
|
|
1832
|
+
// src/scope/git-resolve.ts
|
|
1833
1833
|
/**
|
|
1834
1834
|
* Absolute-path git resolution (audit S1).
|
|
1835
1835
|
*
|
|
@@ -1904,8 +1904,7 @@ function runGit(root, args) {
|
|
|
1904
1904
|
return null;
|
|
1905
1905
|
}
|
|
1906
1906
|
}
|
|
1907
|
-
|
|
1908
|
-
//#region src/lib/safe-json.ts
|
|
1907
|
+
// src/lib/safe-json.ts
|
|
1909
1908
|
/**
|
|
1910
1909
|
* Reusable safe-JSON parse-and-validate helper.
|
|
1911
1910
|
*
|
|
@@ -1942,8 +1941,7 @@ function isRecord(v) {
|
|
|
1942
1941
|
return false;
|
|
1943
1942
|
}
|
|
1944
1943
|
}
|
|
1945
|
-
|
|
1946
|
-
//#region src/engine/candidate-binding.ts
|
|
1944
|
+
// src/engine/candidate-binding.ts
|
|
1947
1945
|
/**
|
|
1948
1946
|
* Candidate binding (plan V5-010).
|
|
1949
1947
|
*
|
|
@@ -2076,18 +2074,17 @@ function readCandidateBinding(root, manifestPath = CANDIDATE_MANIFEST_PATH) {
|
|
|
2076
2074
|
return null;
|
|
2077
2075
|
}
|
|
2078
2076
|
}
|
|
2079
|
-
|
|
2080
|
-
//#region src/engine/version.ts
|
|
2077
|
+
// src/engine/version.ts
|
|
2081
2078
|
/**
|
|
2082
2079
|
* Engine version, as a leaf module: `runIdentity` (R4c) needs the engine
|
|
2083
2080
|
* version inside scan-pipeline WITHOUT importing cli.ts (a cycle — cli
|
|
2084
2081
|
* imports the pipeline). The literal follows the same discipline as
|
|
2085
2082
|
* CLI_VERSION and SARIF's driver.version: kept in sync on release by
|
|
2086
|
-
*
|
|
2087
|
-
*
|
|
2083
|
+
* `npm run version:surface:sync` (this file is an identity surface) and
|
|
2084
|
+
* guarded by the version-consistency
|
|
2088
2085
|
* spec. cli.ts re-exports this as CLI_VERSION.
|
|
2089
2086
|
*/
|
|
2090
|
-
const ENGINE_VERSION = "6.0.
|
|
2087
|
+
const ENGINE_VERSION = "6.0.1";
|
|
2091
2088
|
/**
|
|
2092
2089
|
* Build identity — WHICH BUILD IS RUNNING, as distinct from WHICH RELEASE IS
|
|
2093
2090
|
* PUBLISHED.
|
|
@@ -2148,16 +2145,7 @@ function resolveBuildId() {
|
|
|
2148
2145
|
};
|
|
2149
2146
|
return buildIdFrom(runGit);
|
|
2150
2147
|
}
|
|
2151
|
-
|
|
2152
|
-
//#region src/engine/contract-versions.ts
|
|
2153
|
-
/**
|
|
2154
|
-
* Versioned contract registry (VERSION-001).
|
|
2155
|
-
*
|
|
2156
|
-
* Single source of truth for every version constant the engine exposes
|
|
2157
|
-
* to consumers (run identity, evidence graph, machine contract).
|
|
2158
|
-
* Constants are `as const` — literal types flow into the identity
|
|
2159
|
-
* surface without widening.
|
|
2160
|
-
*/
|
|
2148
|
+
// src/engine/contract-versions.ts
|
|
2161
2149
|
const TRUST_MODEL_VERSION = "1.0.0";
|
|
2162
2150
|
/**
|
|
2163
2151
|
* 2.0.0 (ADR 0014): the core tier carries an evidence FLOOR. A core rule's
|
|
@@ -2169,8 +2157,7 @@ const TRUST_MODEL_VERSION = "1.0.0";
|
|
|
2169
2157
|
*/
|
|
2170
2158
|
const SCORING_MODEL_VERSION = "2.0.0";
|
|
2171
2159
|
const FRAMEWORK_SUPPORT_MATRIX_VERSION = "1.0.0";
|
|
2172
|
-
|
|
2173
|
-
//#region src/discovery/evidence-discovery.ts
|
|
2160
|
+
// src/discovery/evidence-discovery.ts
|
|
2174
2161
|
/**
|
|
2175
2162
|
* Zero-config evidence discovery (Mega MVP Master Plan v3.1 §26 WI-11).
|
|
2176
2163
|
*
|
|
@@ -2262,8 +2249,7 @@ function discoverEvidenceCandidates(scanRoot) {
|
|
|
2262
2249
|
}
|
|
2263
2250
|
return out.sort((a, b) => a.depth - b.depth || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
2264
2251
|
}
|
|
2265
|
-
|
|
2266
|
-
//#region src/discovery/workspace.ts
|
|
2252
|
+
// src/discovery/workspace.ts
|
|
2267
2253
|
/**
|
|
2268
2254
|
* Repository discovery (Sprint-Plan W1-03).
|
|
2269
2255
|
* Finds project root, parses package.json, detects npm/yarn/pnpm workspaces.
|
|
@@ -2310,8 +2296,7 @@ function basename$1(p) {
|
|
|
2310
2296
|
const parts = p.split(/[\\/]/);
|
|
2311
2297
|
return parts[parts.length - 1];
|
|
2312
2298
|
}
|
|
2313
|
-
|
|
2314
|
-
//#region src/lib/fs-bounded.ts
|
|
2299
|
+
// src/lib/fs-bounded.ts
|
|
2315
2300
|
function closeQuiet(fd) {
|
|
2316
2301
|
try {
|
|
2317
2302
|
closeSync(fd);
|
|
@@ -2363,8 +2348,7 @@ function readFileBounded(path, maxBytes) {
|
|
|
2363
2348
|
if (fd !== void 0) closeQuiet(fd);
|
|
2364
2349
|
}
|
|
2365
2350
|
}
|
|
2366
|
-
|
|
2367
|
-
//#region src/config/config-schema.ts
|
|
2351
|
+
// src/config/config-schema.ts
|
|
2368
2352
|
const VALID_GATES = /* @__PURE__ */ new Set([
|
|
2369
2353
|
"advisory",
|
|
2370
2354
|
"error",
|
|
@@ -2434,8 +2418,7 @@ function validateConfigSchema(config) {
|
|
|
2434
2418
|
errors
|
|
2435
2419
|
};
|
|
2436
2420
|
}
|
|
2437
|
-
|
|
2438
|
-
//#region src/config/config.ts
|
|
2421
|
+
// src/config/config.ts
|
|
2439
2422
|
/**
|
|
2440
2423
|
* Config file support (Sprint-Plan W7, Product-MVP §27 + GAP-F).
|
|
2441
2424
|
* Zero-config preserved: config presence never changes detection
|
|
@@ -2569,8 +2552,7 @@ function applySeverityOverrides(findings, cfg) {
|
|
|
2569
2552
|
if (override && SEVERITY_ORDER$1.includes(override)) f.severity = override;
|
|
2570
2553
|
}
|
|
2571
2554
|
}
|
|
2572
|
-
|
|
2573
|
-
//#region src/lib/compare.ts
|
|
2555
|
+
// src/lib/compare.ts
|
|
2574
2556
|
/**
|
|
2575
2557
|
* Order that does not depend on who is asking.
|
|
2576
2558
|
*
|
|
@@ -2616,8 +2598,7 @@ function compareCodePoints(a, b) {
|
|
|
2616
2598
|
function compareLocalized(locale) {
|
|
2617
2599
|
return (a, b) => a.localeCompare(b, locale);
|
|
2618
2600
|
}
|
|
2619
|
-
|
|
2620
|
-
//#region src/lib/glob.ts
|
|
2601
|
+
// src/lib/glob.ts
|
|
2621
2602
|
/**
|
|
2622
2603
|
* The shared segment compiler: a glob to a regex BODY, with no anchors.
|
|
2623
2604
|
*
|
|
@@ -2671,8 +2652,7 @@ function anyDepthGlobRegExp(glob) {
|
|
|
2671
2652
|
function pathMatchesGlob$1(path, glob) {
|
|
2672
2653
|
return anchoredGlobRegExp(glob).test(path.replaceAll("\\", "/"));
|
|
2673
2654
|
}
|
|
2674
|
-
|
|
2675
|
-
//#region src/discovery/ignores.ts
|
|
2655
|
+
// src/discovery/ignores.ts
|
|
2676
2656
|
/**
|
|
2677
2657
|
* Ignore resolution (Product-MVP.txt R1 — Tier-1 launch blocker).
|
|
2678
2658
|
*
|
|
@@ -2829,8 +2809,7 @@ function isLintFixtureDir(absoluteDir) {
|
|
|
2829
2809
|
return false;
|
|
2830
2810
|
}
|
|
2831
2811
|
}
|
|
2832
|
-
|
|
2833
|
-
//#region src/discovery/shared-walk.ts
|
|
2812
|
+
// src/discovery/shared-walk.ts
|
|
2834
2813
|
/**
|
|
2835
2814
|
* Shared directory walker (audit P-2).
|
|
2836
2815
|
*
|
|
@@ -2893,8 +2872,7 @@ function sharedWalk(options) {
|
|
|
2893
2872
|
};
|
|
2894
2873
|
walk(options.root);
|
|
2895
2874
|
}
|
|
2896
|
-
|
|
2897
|
-
//#region src/frameworks/framework-inventory.ts
|
|
2875
|
+
// src/frameworks/framework-inventory.ts
|
|
2898
2876
|
const FRAMEWORK_INVENTORY = [
|
|
2899
2877
|
{
|
|
2900
2878
|
frameworkId: "playwright",
|
|
@@ -3095,8 +3073,7 @@ const FRAMEWORK_INVENTORY = [
|
|
|
3095
3073
|
function getFrameworkById(frameworkId) {
|
|
3096
3074
|
return FRAMEWORK_INVENTORY.find((f) => f.frameworkId === frameworkId);
|
|
3097
3075
|
}
|
|
3098
|
-
|
|
3099
|
-
//#region src/discovery/frameworks.ts
|
|
3076
|
+
// src/discovery/frameworks.ts
|
|
3100
3077
|
/**
|
|
3101
3078
|
* Framework detection via config resolution (Sprint-Plan W3-01, R8).
|
|
3102
3079
|
*
|
|
@@ -3179,8 +3156,7 @@ function detectFrameworks(ws) {
|
|
|
3179
3156
|
unknown: false
|
|
3180
3157
|
};
|
|
3181
3158
|
}
|
|
3182
|
-
|
|
3183
|
-
//#region src/engine/ts-ast.ts
|
|
3159
|
+
// src/engine/ts-ast.ts
|
|
3184
3160
|
/**
|
|
3185
3161
|
* AST precision layer (Upgrade-Plan-v3 Phase 3).
|
|
3186
3162
|
*
|
|
@@ -3379,8 +3355,7 @@ function getCodeOnlyText(file) {
|
|
|
3379
3355
|
function resetTsMorphProject() {
|
|
3380
3356
|
looseProject = null;
|
|
3381
3357
|
}
|
|
3382
|
-
|
|
3383
|
-
//#region src/engine/code-text.ts
|
|
3358
|
+
// src/engine/code-text.ts
|
|
3384
3359
|
/**
|
|
3385
3360
|
* Code-only text masking (Phase 1 — Tempering Plan).
|
|
3386
3361
|
*
|
|
@@ -3740,8 +3715,7 @@ function getCodeTextForCounting(text, language) {
|
|
|
3740
3715
|
function blankRange(chars, start, end) {
|
|
3741
3716
|
for (let i = start; i < end && i < chars.length; i++) if (chars[i] !== "\n" && chars[i] !== "\r") chars[i] = " ";
|
|
3742
3717
|
}
|
|
3743
|
-
|
|
3744
|
-
//#region src/engine/adapter.ts
|
|
3718
|
+
// src/engine/adapter.ts
|
|
3745
3719
|
/**
|
|
3746
3720
|
* The §15.1 framework filter, shared by every language adapter: a rule
|
|
3747
3721
|
* that declares `frameworks` runs on a file only when the file carries
|
|
@@ -3755,8 +3729,7 @@ function frameworkFilterApplies(rule, file) {
|
|
|
3755
3729
|
if (tags === void 0 || tags.length === 0) return true;
|
|
3756
3730
|
return rule.frameworks.some((f) => tags.includes(f));
|
|
3757
3731
|
}
|
|
3758
|
-
|
|
3759
|
-
//#region src/engine/shared-run-rules.ts
|
|
3732
|
+
// src/engine/shared-run-rules.ts
|
|
3760
3733
|
/**
|
|
3761
3734
|
* Run ONE rule with crash isolation, and return what it produced.
|
|
3762
3735
|
*
|
|
@@ -3793,8 +3766,7 @@ function runRuleIsolated(rule, file, onCrash) {
|
|
|
3793
3766
|
return null;
|
|
3794
3767
|
}
|
|
3795
3768
|
}
|
|
3796
|
-
|
|
3797
|
-
//#region src/adapters/typescript.ts
|
|
3769
|
+
// src/adapters/typescript.ts
|
|
3798
3770
|
/**
|
|
3799
3771
|
* TypeScript/JavaScript adapter (R1).
|
|
3800
3772
|
* Wraps the proven regex-based engine behind the LanguageAdapter
|
|
@@ -4047,8 +4019,7 @@ function loadWorkspaceShim(root) {
|
|
|
4047
4019
|
return null;
|
|
4048
4020
|
}
|
|
4049
4021
|
}
|
|
4050
|
-
|
|
4051
|
-
//#region src/engine/tree-sitter-ast.ts
|
|
4022
|
+
// src/engine/tree-sitter-ast.ts
|
|
4052
4023
|
/**
|
|
4053
4024
|
* Tree-sitter WASM AST layer for Java and C# — Sprint 8 Task 36
|
|
4054
4025
|
* (Master-Stabilization-Plan.md).
|
|
@@ -4267,8 +4238,7 @@ async function releaseTreeSitterResources() {
|
|
|
4267
4238
|
} catch {}
|
|
4268
4239
|
}
|
|
4269
4240
|
}
|
|
4270
|
-
|
|
4271
|
-
//#region src/adapters/python.ts
|
|
4241
|
+
// src/adapters/python.ts
|
|
4272
4242
|
/**
|
|
4273
4243
|
* Python/pytest adapter (Upgrade-Plan-v2 R2).
|
|
4274
4244
|
*
|
|
@@ -4426,8 +4396,7 @@ const pythonAdapter = {
|
|
|
4426
4396
|
function readText$2(p) {
|
|
4427
4397
|
return readFileSync(p, "utf8");
|
|
4428
4398
|
}
|
|
4429
|
-
|
|
4430
|
-
//#region src/adapters/java.ts
|
|
4399
|
+
// src/adapters/java.ts
|
|
4431
4400
|
/**
|
|
4432
4401
|
* Java/JUnit adapter (Upgrade-Plan-v3 Phase 4).
|
|
4433
4402
|
*
|
|
@@ -4593,8 +4562,7 @@ function javaFileTags(file) {
|
|
|
4593
4562
|
function readText$1(path) {
|
|
4594
4563
|
return readFileSync(path, "utf8");
|
|
4595
4564
|
}
|
|
4596
|
-
|
|
4597
|
-
//#region src/adapters/csharp.ts
|
|
4565
|
+
// src/adapters/csharp.ts
|
|
4598
4566
|
/**
|
|
4599
4567
|
* C#/.NET adapter (Upgrade-Plan-v3 Phase 5).
|
|
4600
4568
|
*
|
|
@@ -4750,8 +4718,7 @@ function csharpFileTags(file) {
|
|
|
4750
4718
|
function readText(path) {
|
|
4751
4719
|
return readFileSync(path, "utf8");
|
|
4752
4720
|
}
|
|
4753
|
-
|
|
4754
|
-
//#region src/discovery/yaml-guards.ts
|
|
4721
|
+
// src/discovery/yaml-guards.ts
|
|
4755
4722
|
/**
|
|
4756
4723
|
* Shared hostile-YAML guards (product-gap master plan P3 common law —
|
|
4757
4724
|
* `azure-pipelines.yml` detection rides the same YAML machinery as the
|
|
@@ -4820,8 +4787,7 @@ function enforceDepthCap(value, max, depthMessage = GENERIC_LABELS.depthMessage)
|
|
|
4820
4787
|
});
|
|
4821
4788
|
}
|
|
4822
4789
|
}
|
|
4823
|
-
|
|
4824
|
-
//#region src/discovery/workflow-parser.ts
|
|
4790
|
+
// src/discovery/workflow-parser.ts
|
|
4825
4791
|
/**
|
|
4826
4792
|
* Safe GitHub Actions workflow parser (Phase 0.3, W4-03 debt).
|
|
4827
4793
|
*
|
|
@@ -4867,8 +4833,7 @@ function parseWorkflow(text) {
|
|
|
4867
4833
|
}
|
|
4868
4834
|
return { jobs };
|
|
4869
4835
|
}
|
|
4870
|
-
|
|
4871
|
-
//#region src/adapters/github-actions.ts
|
|
4836
|
+
// src/adapters/github-actions.ts
|
|
4872
4837
|
/**
|
|
4873
4838
|
* GitHub Actions adapter (R1).
|
|
4874
4839
|
* Owns workflow discovery and safe-YAML parsing; CI rules consume the
|
|
@@ -4953,8 +4918,7 @@ const githubActionsAdapter = {
|
|
|
4953
4918
|
};
|
|
4954
4919
|
var WorkflowParseSkipped = class extends Error {};
|
|
4955
4920
|
const LIMITS_MAX_BYTES$2 = 1048576;
|
|
4956
|
-
|
|
4957
|
-
//#region src/discovery/azure-pipeline-parser.ts
|
|
4921
|
+
// src/discovery/azure-pipeline-parser.ts
|
|
4958
4922
|
/**
|
|
4959
4923
|
* Safe Azure DevOps pipeline parser (product-gap master plan P3b).
|
|
4960
4924
|
*
|
|
@@ -5188,8 +5152,7 @@ function isAzurePipelineFixture(normalizedPath) {
|
|
|
5188
5152
|
const base = normalizedPath.replaceAll("\\", "/").split("/").pop() ?? "";
|
|
5189
5153
|
return AZURE_PIPELINE_FILENAMES_SET.has(base);
|
|
5190
5154
|
}
|
|
5191
|
-
|
|
5192
|
-
//#region src/adapters/azure-pipelines.ts
|
|
5155
|
+
// src/adapters/azure-pipelines.ts
|
|
5193
5156
|
/**
|
|
5194
5157
|
* Azure DevOps pipeline adapter (product-gap master plan P3b).
|
|
5195
5158
|
* Owns `azure-pipelines.yml` discovery (repo root, the platform's
|
|
@@ -5269,8 +5232,7 @@ const azurePipelinesAdapter = {
|
|
|
5269
5232
|
};
|
|
5270
5233
|
var AzureParseSkipped = class extends Error {};
|
|
5271
5234
|
const LIMITS_MAX_BYTES$1 = 1048576;
|
|
5272
|
-
|
|
5273
|
-
//#region src/adapters/jenkins.ts
|
|
5235
|
+
// src/adapters/jenkins.ts
|
|
5274
5236
|
/**
|
|
5275
5237
|
* Jenkins adapter (product-gap master plan P3c).
|
|
5276
5238
|
* The root `Jenkinsfile` is a TEXT-target file kind: declarative and
|
|
@@ -5344,8 +5306,7 @@ const jenkinsAdapter = {
|
|
|
5344
5306
|
}
|
|
5345
5307
|
};
|
|
5346
5308
|
const LIMITS_MAX_BYTES = 1048576;
|
|
5347
|
-
|
|
5348
|
-
//#region src/discovery/scan-adapters.ts
|
|
5309
|
+
// src/discovery/scan-adapters.ts
|
|
5349
5310
|
/**
|
|
5350
5311
|
* The canonical adapter registry (audits H-6/H-9).
|
|
5351
5312
|
*
|
|
@@ -5519,8 +5480,7 @@ const SEARCHED_FOR = [
|
|
|
5519
5480
|
globs: jenkinsAdapter.testFileGlobs
|
|
5520
5481
|
}
|
|
5521
5482
|
];
|
|
5522
|
-
|
|
5523
|
-
//#region src/scope/changed.ts
|
|
5483
|
+
// src/scope/changed.ts
|
|
5524
5484
|
/**
|
|
5525
5485
|
* Changed-scope engine (Sprint-Plan W6, Product-MVP §9 `--scope changed`).
|
|
5526
5486
|
*
|
|
@@ -5763,8 +5723,7 @@ function computeStagedFiles(root) {
|
|
|
5763
5723
|
if (raw === null) return null;
|
|
5764
5724
|
return raw.split("\0").filter((s) => s.length > 0);
|
|
5765
5725
|
}
|
|
5766
|
-
|
|
5767
|
-
//#region src/rules/rule.ts
|
|
5726
|
+
// src/rules/rule.ts
|
|
5768
5727
|
/**
|
|
5769
5728
|
* Invoke an `astQuery` hook with the mandatory-fallback contract: no
|
|
5770
5729
|
* hook or no AST on the context resolves to `undefined` (→ regex
|
|
@@ -5777,8 +5736,7 @@ function tryAstQuery(hook, ctx) {
|
|
|
5777
5736
|
function defineRule(rule) {
|
|
5778
5737
|
return rule;
|
|
5779
5738
|
}
|
|
5780
|
-
|
|
5781
|
-
//#region src/rules/shared/positions.ts
|
|
5739
|
+
// src/rules/shared/positions.ts
|
|
5782
5740
|
/**
|
|
5783
5741
|
* Shared position helpers (Phase 6 — Tempering Plan).
|
|
5784
5742
|
* One lineAt, one colAt, one matchBrace. The other 79 copies are dead.
|
|
@@ -5823,8 +5781,7 @@ function lineAt(text, index) {
|
|
|
5823
5781
|
function colAt(text, index) {
|
|
5824
5782
|
return index - (index <= 0 ? -1 : text.lastIndexOf("\n", index - 1));
|
|
5825
5783
|
}
|
|
5826
|
-
|
|
5827
|
-
//#region src/rules/test/qa-test-001-focused-test.ts
|
|
5784
|
+
// src/rules/test/qa-test-001-focused-test.ts
|
|
5828
5785
|
/**
|
|
5829
5786
|
* QA-TEST-001 — Focused test committed (`fit`/`fdescribe`/`.only`).
|
|
5830
5787
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5890,8 +5847,7 @@ const focusedTestCommitted = defineRule({
|
|
|
5890
5847
|
return findings;
|
|
5891
5848
|
}
|
|
5892
5849
|
});
|
|
5893
|
-
|
|
5894
|
-
//#region src/rules/test/qa-test-002-skipped-test.ts
|
|
5850
|
+
// src/rules/test/qa-test-002-skipped-test.ts
|
|
5895
5851
|
/**
|
|
5896
5852
|
* QA-TEST-002 — Skipped test (`it.skip`, `xit`, `test.skip`, `describe.skip`).
|
|
5897
5853
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -5947,8 +5903,7 @@ const skippedTest = defineRule({
|
|
|
5947
5903
|
return findings;
|
|
5948
5904
|
}
|
|
5949
5905
|
});
|
|
5950
|
-
|
|
5951
|
-
//#region src/rules/shared/masking.ts
|
|
5906
|
+
// src/rules/shared/masking.ts
|
|
5952
5907
|
/** True when the offset was blanked in codeText (inside a string or comment). */
|
|
5953
5908
|
function isMasked(ctx, index) {
|
|
5954
5909
|
const mask = ctx.codeText;
|
|
@@ -6009,8 +5964,7 @@ function isInsideEmbeddedCode(ctx, index) {
|
|
|
6009
5964
|
const hasCallSyntax = /\w\s*\(/.test(inner);
|
|
6010
5965
|
return hasNestedQuote && hasCallSyntax;
|
|
6011
5966
|
}
|
|
6012
|
-
|
|
6013
|
-
//#region src/rules/test/qa-test-003-no-assertions.ts
|
|
5967
|
+
// src/rules/test/qa-test-003-no-assertions.ts
|
|
6014
5968
|
/**
|
|
6015
5969
|
* QA-TEST-003 — Test with no assertions.
|
|
6016
5970
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -6088,8 +6042,7 @@ function matchBrace$2(text, open) {
|
|
|
6088
6042
|
}
|
|
6089
6043
|
return -1;
|
|
6090
6044
|
}
|
|
6091
|
-
|
|
6092
|
-
//#region src/rules/test/qa-test-004-hard-sleep.ts
|
|
6045
|
+
// src/rules/test/qa-test-004-hard-sleep.ts
|
|
6093
6046
|
/**
|
|
6094
6047
|
* QA-TEST-004 — Hard sleep (`sleep()`, `waitForTimeout()`, `setTimeout` as wait).
|
|
6095
6048
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -6154,8 +6107,7 @@ const hardSleep = defineRule({
|
|
|
6154
6107
|
return findings;
|
|
6155
6108
|
}
|
|
6156
6109
|
});
|
|
6157
|
-
|
|
6158
|
-
//#region src/rules/test/qa-test-006-retry-abuse.ts
|
|
6110
|
+
// src/rules/test/qa-test-006-retry-abuse.ts
|
|
6159
6111
|
/**
|
|
6160
6112
|
* QA-TEST-006 — Retry abuse in test config.
|
|
6161
6113
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -6221,8 +6173,7 @@ const retryAbuse = defineRule({
|
|
|
6221
6173
|
return findings;
|
|
6222
6174
|
}
|
|
6223
6175
|
});
|
|
6224
|
-
|
|
6225
|
-
//#region src/rules/test/qa-test-010-empty-body.ts
|
|
6176
|
+
// src/rules/test/qa-test-010-empty-body.ts
|
|
6226
6177
|
/**
|
|
6227
6178
|
* QA-TEST-010 — Empty test body.
|
|
6228
6179
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -6277,8 +6228,7 @@ const emptyTestBody = defineRule({
|
|
|
6277
6228
|
return findings;
|
|
6278
6229
|
}
|
|
6279
6230
|
});
|
|
6280
|
-
|
|
6281
|
-
//#region src/rules/quality/qa-tqual-002-tautological.ts
|
|
6231
|
+
// src/rules/quality/qa-tqual-002-tautological.ts
|
|
6282
6232
|
/**
|
|
6283
6233
|
* QA-TQUAL-002 — Tautological assertion.
|
|
6284
6234
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -6355,8 +6305,7 @@ const tautologicalAssertion = defineRule({
|
|
|
6355
6305
|
return findings;
|
|
6356
6306
|
}
|
|
6357
6307
|
});
|
|
6358
|
-
|
|
6359
|
-
//#region src/rules/quality/qa-tqual-009-promise-assertion.ts
|
|
6308
|
+
// src/rules/quality/qa-tqual-009-promise-assertion.ts
|
|
6360
6309
|
/**
|
|
6361
6310
|
* QA-TQUAL-009 — Unawaited assertion inside a promise chain.
|
|
6362
6311
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -6526,8 +6475,7 @@ function matchParen$2(text, open) {
|
|
|
6526
6475
|
}
|
|
6527
6476
|
return -1;
|
|
6528
6477
|
}
|
|
6529
|
-
|
|
6530
|
-
//#region src/rules/quality/qa-tqual-011-commented-out.ts
|
|
6478
|
+
// src/rules/quality/qa-tqual-011-commented-out.ts
|
|
6531
6479
|
/**
|
|
6532
6480
|
* QA-TQUAL-011 — Commented-out tests.
|
|
6533
6481
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -6640,8 +6588,7 @@ function commentRanges(ctx) {
|
|
|
6640
6588
|
}
|
|
6641
6589
|
return ranges;
|
|
6642
6590
|
}
|
|
6643
|
-
|
|
6644
|
-
//#region src/rules/playwright/qa-pw-002-unawaited-assertion.ts
|
|
6591
|
+
// src/rules/playwright/qa-pw-002-unawaited-assertion.ts
|
|
6645
6592
|
/**
|
|
6646
6593
|
* QA-PW-002 — Missing `await` on a Playwright locator assertion.
|
|
6647
6594
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -6786,8 +6733,7 @@ function runRegexFallback(text, path) {
|
|
|
6786
6733
|
});
|
|
6787
6734
|
return findings;
|
|
6788
6735
|
}
|
|
6789
|
-
|
|
6790
|
-
//#region src/rules/playwright/qa-pw-003-debug-artifacts.ts
|
|
6736
|
+
// src/rules/playwright/qa-pw-003-debug-artifacts.ts
|
|
6791
6737
|
/**
|
|
6792
6738
|
* QA-PW-003 — Committed `page.pause()` or `test.only()` in e2e specs.
|
|
6793
6739
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -6839,8 +6785,7 @@ const committedDebugArtifacts = defineRule({
|
|
|
6839
6785
|
return findings;
|
|
6840
6786
|
}
|
|
6841
6787
|
});
|
|
6842
|
-
|
|
6843
|
-
//#region src/rules/playwright/qa-pw-004-brittle-selectors.ts
|
|
6788
|
+
// src/rules/playwright/qa-pw-004-brittle-selectors.ts
|
|
6844
6789
|
/**
|
|
6845
6790
|
* QA-PW-004 — Brittle CSS/XPath selectors vs role-based locators.
|
|
6846
6791
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -6932,8 +6877,7 @@ function stripComments(ctx) {
|
|
|
6932
6877
|
} catch {}
|
|
6933
6878
|
return ctx.text.replace(/\/\/[^\n]*/g, (cm) => " ".repeat(cm.length));
|
|
6934
6879
|
}
|
|
6935
|
-
|
|
6936
|
-
//#region src/rules/ci/verification-gate.ts
|
|
6880
|
+
// src/rules/ci/verification-gate.ts
|
|
6937
6881
|
/**
|
|
6938
6882
|
* Shared "is this shell command a verification gate?" matcher for the QA-CI
|
|
6939
6883
|
* rules. A gate is a command whose failure MUST fail the job — test runners,
|
|
@@ -6973,8 +6917,7 @@ const VERIFICATION_GATE_RE = new RegExp([
|
|
|
6973
6917
|
function looksLikeVerificationGate(cmd) {
|
|
6974
6918
|
return VERIFICATION_GATE_RE.test(cmd);
|
|
6975
6919
|
}
|
|
6976
|
-
|
|
6977
|
-
//#region src/rules/ci/azure-gates.ts
|
|
6920
|
+
// src/rules/ci/azure-gates.ts
|
|
6978
6921
|
/** Discriminator: the Azure parser is the only one emitting `platform`. */
|
|
6979
6922
|
function isAzurePipelineDoc(ast) {
|
|
6980
6923
|
return typeof ast === "object" && ast !== null && ast.platform === "azure-pipelines";
|
|
@@ -7058,8 +7001,7 @@ function locateAzureJobKey(text, job, keyName) {
|
|
|
7058
7001
|
if (m) return lineAt(text, m.index);
|
|
7059
7002
|
return 1;
|
|
7060
7003
|
}
|
|
7061
|
-
|
|
7062
|
-
//#region src/rules/ci/qa-ci-001-continue-on-error.ts
|
|
7004
|
+
// src/rules/ci/qa-ci-001-continue-on-error.ts
|
|
7063
7005
|
/**
|
|
7064
7006
|
* QA-CI-001 — `continue-on-error: true` masks a failing job.
|
|
7065
7007
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7287,8 +7229,7 @@ function describeAzureStep(step) {
|
|
|
7287
7229
|
if (step.task) return step.task;
|
|
7288
7230
|
return "step";
|
|
7289
7231
|
}
|
|
7290
|
-
|
|
7291
|
-
//#region src/rules/ci/qa-ci-002-swallowed-exit.ts
|
|
7232
|
+
// src/rules/ci/qa-ci-002-swallowed-exit.ts
|
|
7292
7233
|
/**
|
|
7293
7234
|
* QA-CI-002 — `|| true` swallows a command's exit code.
|
|
7294
7235
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7343,8 +7284,7 @@ const swallowedExitCode = defineRule({
|
|
|
7343
7284
|
return findings;
|
|
7344
7285
|
}
|
|
7345
7286
|
});
|
|
7346
|
-
|
|
7347
|
-
//#region src/rules/ci/qa-ci-007-retry-masking.ts
|
|
7287
|
+
// src/rules/ci/qa-ci-007-retry-masking.ts
|
|
7348
7288
|
/**
|
|
7349
7289
|
* QA-CI-007 — Retry masking test failures.
|
|
7350
7290
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -7495,8 +7435,7 @@ function azureArm$1(ctx, doc, findings) {
|
|
|
7495
7435
|
});
|
|
7496
7436
|
}
|
|
7497
7437
|
}
|
|
7498
|
-
|
|
7499
|
-
//#region src/rules/ci/qa-ci-005-report-never-generated.ts
|
|
7438
|
+
// src/rules/ci/qa-ci-005-report-never-generated.ts
|
|
7500
7439
|
/**
|
|
7501
7440
|
* QA-CI-005 — Report consumed but never generated.
|
|
7502
7441
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7654,8 +7593,7 @@ function findJobConsumerLine(text, jobName, consumerRe) {
|
|
|
7654
7593
|
const anywhere = new RegExp(consumerRe.source, "i").exec(text);
|
|
7655
7594
|
return anywhere ? lineAt(text, anywhere.index) : 1;
|
|
7656
7595
|
}
|
|
7657
|
-
|
|
7658
|
-
//#region src/rules/ci/jenkins-gates.ts
|
|
7596
|
+
// src/rules/ci/jenkins-gates.ts
|
|
7659
7597
|
/**
|
|
7660
7598
|
* Jenkinsfile block scanner for the QA-CI Jenkins arms (product-gap
|
|
7661
7599
|
* master plan P3c). Jenkinsfiles are Groovy text — "no new language
|
|
@@ -7901,8 +7839,7 @@ function textIsVerificationGate(text) {
|
|
|
7901
7839
|
function lineOfOffset(text, offset) {
|
|
7902
7840
|
return lineAt(text, offset);
|
|
7903
7841
|
}
|
|
7904
|
-
|
|
7905
|
-
//#region src/rules/ci/qa-ci-008-always-success.ts
|
|
7842
|
+
// src/rules/ci/qa-ci-008-always-success.ts
|
|
7906
7843
|
/**
|
|
7907
7844
|
* QA-CI-008 — Always-success step at the end of a job.
|
|
7908
7845
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -8076,8 +8013,7 @@ function jenkinsArm$1(ctx, findings) {
|
|
|
8076
8013
|
});
|
|
8077
8014
|
}
|
|
8078
8015
|
}
|
|
8079
|
-
|
|
8080
|
-
//#region src/rules/ci/qa-ci-009-exit-code.ts
|
|
8016
|
+
// src/rules/ci/qa-ci-009-exit-code.ts
|
|
8081
8017
|
/**
|
|
8082
8018
|
* QA-CI-009 — Test command does not propagate exit code.
|
|
8083
8019
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -8212,8 +8148,7 @@ function jenkinsArm(ctx, findings) {
|
|
|
8212
8148
|
});
|
|
8213
8149
|
}
|
|
8214
8150
|
}
|
|
8215
|
-
|
|
8216
|
-
//#region src/rules/ci/qa-ci-010-non-blocking.ts
|
|
8151
|
+
// src/rules/ci/qa-ci-010-non-blocking.ts
|
|
8217
8152
|
/**
|
|
8218
8153
|
* QA-CI-010 — Required test job is non-blocking for branch protection.
|
|
8219
8154
|
* Severity: error · Confidence: medium · deterministic-defect
|
|
@@ -8296,8 +8231,7 @@ function findLine(text, re) {
|
|
|
8296
8231
|
function escapeRe(s) {
|
|
8297
8232
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8298
8233
|
}
|
|
8299
|
-
|
|
8300
|
-
//#region src/rules/ci/qa-ci-013-can-never-fail.ts
|
|
8234
|
+
// src/rules/ci/qa-ci-013-can-never-fail.ts
|
|
8301
8235
|
/**
|
|
8302
8236
|
* QA-CI-013 — Verification stage/job conditioned so it can never fail the
|
|
8303
8237
|
* pipeline (product-gap master plan P3b).
|
|
@@ -8401,8 +8335,7 @@ const canNeverFailGate = defineRule({
|
|
|
8401
8335
|
function jobHasGate(job) {
|
|
8402
8336
|
return (job.steps ?? []).some((s) => s !== void 0 && stepIsVerificationGate$1(s));
|
|
8403
8337
|
}
|
|
8404
|
-
|
|
8405
|
-
//#region src/rules/ci/qa-ci-014-swallowed-verification.ts
|
|
8338
|
+
// src/rules/ci/qa-ci-014-swallowed-verification.ts
|
|
8406
8339
|
/**
|
|
8407
8340
|
* QA-CI-014 — try/catch swallowing a verification-stage failure without
|
|
8408
8341
|
* rethrow or failure-marking (product-gap master plan P3c).
|
|
@@ -8459,8 +8392,7 @@ const swallowedVerificationFailure = defineRule({
|
|
|
8459
8392
|
return findings;
|
|
8460
8393
|
}
|
|
8461
8394
|
});
|
|
8462
|
-
|
|
8463
|
-
//#region src/rules/python/qa-py-003-no-assertions.ts
|
|
8395
|
+
// src/rules/python/qa-py-003-no-assertions.ts
|
|
8464
8396
|
/**
|
|
8465
8397
|
* QA-PY-003 — Test function with no assertions.
|
|
8466
8398
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -8581,8 +8513,7 @@ function extractBlock$1(text, afterColon) {
|
|
|
8581
8513
|
const lineEnd = rest.indexOf("\n", firstIdx);
|
|
8582
8514
|
return lineEnd === -1 ? rest : rest.slice(0, lineEnd);
|
|
8583
8515
|
}
|
|
8584
|
-
|
|
8585
|
-
//#region src/rules/python/qa-py-005-hard-sleep.ts
|
|
8516
|
+
// src/rules/python/qa-py-005-hard-sleep.ts
|
|
8586
8517
|
/**
|
|
8587
8518
|
* QA-PY-005 — time.sleep() in tests.
|
|
8588
8519
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -8629,8 +8560,7 @@ const pyHardSleep = defineRule({
|
|
|
8629
8560
|
return findings;
|
|
8630
8561
|
}
|
|
8631
8562
|
});
|
|
8632
|
-
|
|
8633
|
-
//#region src/rules/python/qa-py-002-skipped-test.ts
|
|
8563
|
+
// src/rules/python/qa-py-002-skipped-test.ts
|
|
8634
8564
|
/**
|
|
8635
8565
|
* QA-PY-002 — Skipped test (`@pytest.mark.skip`, `xfail` non-strict).
|
|
8636
8566
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -8690,8 +8620,7 @@ const pySkippedTest = defineRule({
|
|
|
8690
8620
|
return findings;
|
|
8691
8621
|
}
|
|
8692
8622
|
});
|
|
8693
|
-
|
|
8694
|
-
//#region src/rules/python/qa-py-012-tautological.ts
|
|
8623
|
+
// src/rules/python/qa-py-012-tautological.ts
|
|
8695
8624
|
/**
|
|
8696
8625
|
* QA-PY-012 — Tautological assertion (`assert True`, `assert x == x`).
|
|
8697
8626
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -8746,8 +8675,7 @@ const pyTautological = defineRule({
|
|
|
8746
8675
|
return findings;
|
|
8747
8676
|
}
|
|
8748
8677
|
});
|
|
8749
|
-
|
|
8750
|
-
//#region src/rules/python/qa-py-001-focused-test.ts
|
|
8678
|
+
// src/rules/python/qa-py-001-focused-test.ts
|
|
8751
8679
|
/**
|
|
8752
8680
|
* QA-PY-001 — Focused test committed.
|
|
8753
8681
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -8801,8 +8729,7 @@ const pyFocusedTest = defineRule({
|
|
|
8801
8729
|
return findings;
|
|
8802
8730
|
}
|
|
8803
8731
|
});
|
|
8804
|
-
|
|
8805
|
-
//#region src/engine/jv-cs-ast.ts
|
|
8732
|
+
// src/engine/jv-cs-ast.ts
|
|
8806
8733
|
/** Narrow the loose `ast` seam back to a tree-sitter Tree. */
|
|
8807
8734
|
function getTreeSitterTree(ast) {
|
|
8808
8735
|
if (!(ast instanceof Object) || !("rootNode" in ast)) return void 0;
|
|
@@ -9006,8 +8933,7 @@ function isHelperIdiom(name) {
|
|
|
9006
8933
|
}
|
|
9007
8934
|
return false;
|
|
9008
8935
|
}
|
|
9009
|
-
|
|
9010
|
-
//#region src/engine/python-ast.ts
|
|
8936
|
+
// src/engine/python-ast.ts
|
|
9011
8937
|
function getPythonTree(ast) {
|
|
9012
8938
|
return getTreeSitterTree(ast);
|
|
9013
8939
|
}
|
|
@@ -9092,8 +9018,7 @@ function pythonWithRaisesBlocks(tree) {
|
|
|
9092
9018
|
});
|
|
9093
9019
|
return out;
|
|
9094
9020
|
}
|
|
9095
|
-
|
|
9096
|
-
//#region src/rules/python/qa-py-007-raises-without-match.ts
|
|
9021
|
+
// src/rules/python/qa-py-007-raises-without-match.ts
|
|
9097
9022
|
/**
|
|
9098
9023
|
* QA-PY-007 — pytest.raises without match.
|
|
9099
9024
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9220,8 +9145,7 @@ function astArm(ctx, tree) {
|
|
|
9220
9145
|
}
|
|
9221
9146
|
return findings;
|
|
9222
9147
|
}
|
|
9223
|
-
|
|
9224
|
-
//#region src/rules/python/qa-py-009-commented-out.ts
|
|
9148
|
+
// src/rules/python/qa-py-009-commented-out.ts
|
|
9225
9149
|
/**
|
|
9226
9150
|
* QA-PY-009 — Commented-out tests.
|
|
9227
9151
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9268,8 +9192,7 @@ const pyCommentedOutTest = defineRule({
|
|
|
9268
9192
|
return findings;
|
|
9269
9193
|
}
|
|
9270
9194
|
});
|
|
9271
|
-
|
|
9272
|
-
//#region src/rules/python/qa-py-004-bare-truthiness.ts
|
|
9195
|
+
// src/rules/python/qa-py-004-bare-truthiness.ts
|
|
9273
9196
|
/**
|
|
9274
9197
|
* QA-PY-004 — Bare truthiness assert on complex object.
|
|
9275
9198
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9348,8 +9271,7 @@ const pyBareTruthinessAssert = defineRule({
|
|
|
9348
9271
|
return findings;
|
|
9349
9272
|
}
|
|
9350
9273
|
});
|
|
9351
|
-
|
|
9352
|
-
//#region src/rules/python/qa-py-011-mutable-fixture.ts
|
|
9274
|
+
// src/rules/python/qa-py-011-mutable-fixture.ts
|
|
9353
9275
|
/**
|
|
9354
9276
|
* QA-PY-011 — Mutable fixture shared across tests.
|
|
9355
9277
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9400,8 +9322,7 @@ const pyMutableFixture = defineRule({
|
|
|
9400
9322
|
return findings;
|
|
9401
9323
|
}
|
|
9402
9324
|
});
|
|
9403
|
-
|
|
9404
|
-
//#region src/rules/playwright/qa-pw-101-wait-for-timeout.ts
|
|
9325
|
+
// src/rules/playwright/qa-pw-101-wait-for-timeout.ts
|
|
9405
9326
|
/**
|
|
9406
9327
|
* QA-PW-101 — waitForTimeout() anywhere.
|
|
9407
9328
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -9448,8 +9369,7 @@ const pwWaitForTimeout = defineRule({
|
|
|
9448
9369
|
return findings;
|
|
9449
9370
|
}
|
|
9450
9371
|
});
|
|
9451
|
-
|
|
9452
|
-
//#region src/rules/playwright/qa-pw-102-wait-load.ts
|
|
9372
|
+
// src/rules/playwright/qa-pw-102-wait-load.ts
|
|
9453
9373
|
/**
|
|
9454
9374
|
* QA-PW-102 — waitForEvent('load') / waitForLoadState('load') instead of
|
|
9455
9375
|
* web-first assertions.
|
|
@@ -9503,8 +9423,7 @@ const pwWaitForLoadEvent = defineRule({
|
|
|
9503
9423
|
return findings;
|
|
9504
9424
|
}
|
|
9505
9425
|
});
|
|
9506
|
-
|
|
9507
|
-
//#region src/rules/playwright/qa-pw-113-deep-frames.ts
|
|
9426
|
+
// src/rules/playwright/qa-pw-113-deep-frames.ts
|
|
9508
9427
|
/**
|
|
9509
9428
|
* QA-PW-113 — frameLocator chains > 2 deep.
|
|
9510
9429
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9554,8 +9473,7 @@ const pwDeepFrameLocator = defineRule({
|
|
|
9554
9473
|
function count(fragment) {
|
|
9555
9474
|
return fragment.match(/frameLocator\s*\(/g).length;
|
|
9556
9475
|
}
|
|
9557
|
-
|
|
9558
|
-
//#region src/rules/playwright/qa-pw-117-serial.ts
|
|
9476
|
+
// src/rules/playwright/qa-pw-117-serial.ts
|
|
9559
9477
|
/**
|
|
9560
9478
|
* QA-PW-117 — test.describe.serial without justification comment.
|
|
9561
9479
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9609,8 +9527,7 @@ const pwSerialNoJustification = defineRule({
|
|
|
9609
9527
|
return findings;
|
|
9610
9528
|
}
|
|
9611
9529
|
});
|
|
9612
|
-
|
|
9613
|
-
//#region src/rules/playwright/qa-pw-121-config-retries.ts
|
|
9530
|
+
// src/rules/playwright/qa-pw-121-config-retries.ts
|
|
9614
9531
|
/**
|
|
9615
9532
|
* QA-PW-121 — playwright.config: retries >= 3 or workers = 0.
|
|
9616
9533
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9675,8 +9592,7 @@ const pwConfigRetryAbuse = defineRule({
|
|
|
9675
9592
|
return findings;
|
|
9676
9593
|
}
|
|
9677
9594
|
});
|
|
9678
|
-
|
|
9679
|
-
//#region src/rules/playwright/qa-pw-122-no-trace.ts
|
|
9595
|
+
// src/rules/playwright/qa-pw-122-no-trace.ts
|
|
9680
9596
|
/**
|
|
9681
9597
|
* QA-PW-122 — No trace capture on retry in playwright.config.
|
|
9682
9598
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9725,8 +9641,7 @@ const pwNoTraceOnRetry = defineRule({
|
|
|
9725
9641
|
return findings;
|
|
9726
9642
|
}
|
|
9727
9643
|
});
|
|
9728
|
-
|
|
9729
|
-
//#region src/rules/playwright/qa-pw-124-project-split.ts
|
|
9644
|
+
// src/rules/playwright/qa-pw-124-project-split.ts
|
|
9730
9645
|
/**
|
|
9731
9646
|
* QA-PW-124 — Projects defined but no smoke/regression split.
|
|
9732
9647
|
* Severity: info · Confidence: high · deterministic-defect
|
|
@@ -9788,8 +9703,7 @@ const pwNoProjectSplit = defineRule({
|
|
|
9788
9703
|
return findings;
|
|
9789
9704
|
}
|
|
9790
9705
|
});
|
|
9791
|
-
|
|
9792
|
-
//#region src/rules/playwright/qa-pw-104-trial-click.ts
|
|
9706
|
+
// src/rules/playwright/qa-pw-104-trial-click.ts
|
|
9793
9707
|
/**
|
|
9794
9708
|
* QA-PW-104 — `{ trial: true }` misuse — check-then-act race.
|
|
9795
9709
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9835,8 +9749,7 @@ const pwTrialMisuse = defineRule({
|
|
|
9835
9749
|
return findings;
|
|
9836
9750
|
}
|
|
9837
9751
|
});
|
|
9838
|
-
|
|
9839
|
-
//#region src/rules/playwright/qa-pw-116-storage-state.ts
|
|
9752
|
+
// src/rules/playwright/qa-pw-116-storage-state.ts
|
|
9840
9753
|
/**
|
|
9841
9754
|
* QA-PW-116 — storageState reuse without expiry strategy.
|
|
9842
9755
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9882,8 +9795,7 @@ const pwStorageStateNoExpiry = defineRule({
|
|
|
9882
9795
|
return findings;
|
|
9883
9796
|
}
|
|
9884
9797
|
});
|
|
9885
|
-
|
|
9886
|
-
//#region src/rules/playwright/qa-pw-125-global-setup.ts
|
|
9798
|
+
// src/rules/playwright/qa-pw-125-global-setup.ts
|
|
9887
9799
|
/**
|
|
9888
9800
|
* QA-PW-125 — globalSetup mutating shared external state.
|
|
9889
9801
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9941,8 +9853,7 @@ const pwGlobalSetupSharedState = defineRule({
|
|
|
9941
9853
|
return findings;
|
|
9942
9854
|
}
|
|
9943
9855
|
});
|
|
9944
|
-
|
|
9945
|
-
//#region src/rules/playwright/qa-pw-115-shared-page.ts
|
|
9856
|
+
// src/rules/playwright/qa-pw-115-shared-page.ts
|
|
9946
9857
|
/**
|
|
9947
9858
|
* QA-PW-115 — Shared `page` across parallel tests without isolation.
|
|
9948
9859
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9990,8 +9901,7 @@ const pwSharedPage = defineRule({
|
|
|
9990
9901
|
return findings;
|
|
9991
9902
|
}
|
|
9992
9903
|
});
|
|
9993
|
-
|
|
9994
|
-
//#region src/rules/playwright/qa-pw-140.ts
|
|
9904
|
+
// src/rules/playwright/qa-pw-140.ts
|
|
9995
9905
|
/**
|
|
9996
9906
|
* QA-PW-140 — Screenshot without maxDiffPixelRatio.
|
|
9997
9907
|
*
|
|
@@ -10065,8 +9975,7 @@ function matchParen(text, open) {
|
|
|
10065
9975
|
}
|
|
10066
9976
|
return -1;
|
|
10067
9977
|
}
|
|
10068
|
-
|
|
10069
|
-
//#region src/rules/playwright/qa-pw-123-hardcoded-url.ts
|
|
9978
|
+
// src/rules/playwright/qa-pw-123-hardcoded-url.ts
|
|
10070
9979
|
/**
|
|
10071
9980
|
* QA-PW-123 — Hardcoded baseURL in specs.
|
|
10072
9981
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -10114,8 +10023,7 @@ const hardcodedBaseUrl = defineRule({
|
|
|
10114
10023
|
return findings;
|
|
10115
10024
|
}
|
|
10116
10025
|
});
|
|
10117
|
-
|
|
10118
|
-
//#region src/rules/quality/qa-env-001-env-coupling.ts
|
|
10026
|
+
// src/rules/quality/qa-env-001-env-coupling.ts
|
|
10119
10027
|
/**
|
|
10120
10028
|
* QA-ENV-001 — "Works On My Machine" detector core (Tier 5 #24).
|
|
10121
10029
|
* Environment coupling in test files: fixed ports, OS paths,
|
|
@@ -10182,8 +10090,7 @@ const envCoupling = defineRule({
|
|
|
10182
10090
|
return findings;
|
|
10183
10091
|
}
|
|
10184
10092
|
});
|
|
10185
|
-
|
|
10186
|
-
//#region src/rules/playwright/qa-pw-141-retry-no-triage.ts
|
|
10093
|
+
// src/rules/playwright/qa-pw-141-retry-no-triage.ts
|
|
10187
10094
|
/**
|
|
10188
10095
|
* QA-PW-141 — playwright.config retries set high enough to mask flakes.
|
|
10189
10096
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -10240,8 +10147,7 @@ const pwRetryMaskingNoForensics = defineRule({
|
|
|
10240
10147
|
return findings;
|
|
10241
10148
|
}
|
|
10242
10149
|
});
|
|
10243
|
-
|
|
10244
|
-
//#region src/rules/playwright/qa-pw-142-blanket-route.ts
|
|
10150
|
+
// src/rules/playwright/qa-pw-142-blanket-route.ts
|
|
10245
10151
|
/**
|
|
10246
10152
|
* QA-PW-142 — page.route() blanket-mocking instead of scoped intercepts.
|
|
10247
10153
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -10296,8 +10202,7 @@ const pwBlanketRouteMock = defineRule({
|
|
|
10296
10202
|
return findings;
|
|
10297
10203
|
}
|
|
10298
10204
|
});
|
|
10299
|
-
|
|
10300
|
-
//#region src/rules/playwright/qa-pw-143-no-artifacts.ts
|
|
10205
|
+
// src/rules/playwright/qa-pw-143-no-artifacts.ts
|
|
10301
10206
|
/**
|
|
10302
10207
|
* QA-PW-143 — No screenshot/video artifact capture on failure.
|
|
10303
10208
|
* Severity: info · Confidence: high · deterministic-defect
|
|
@@ -10350,8 +10255,7 @@ const pwNoFailureArtifacts = defineRule({
|
|
|
10350
10255
|
return findings;
|
|
10351
10256
|
}
|
|
10352
10257
|
});
|
|
10353
|
-
|
|
10354
|
-
//#region src/rules/playwright/qa-pw-144-single-browser.ts
|
|
10258
|
+
// src/rules/playwright/qa-pw-144-single-browser.ts
|
|
10355
10259
|
/**
|
|
10356
10260
|
* QA-PW-144 — Single-browser project matrix (no cross-browser coverage).
|
|
10357
10261
|
* Severity: info · Confidence: high · deterministic-defect
|
|
@@ -10413,8 +10317,7 @@ const pwSingleBrowserMatrix = defineRule({
|
|
|
10413
10317
|
return findings;
|
|
10414
10318
|
}
|
|
10415
10319
|
});
|
|
10416
|
-
|
|
10417
|
-
//#region src/rules/playwright/qa-pw-146-css-locator.ts
|
|
10320
|
+
// src/rules/playwright/qa-pw-146-css-locator.ts
|
|
10418
10321
|
/**
|
|
10419
10322
|
* QA-PW-146 — CSS/XPath string selector where Playwright's locator
|
|
10420
10323
|
* standard expects a normalized locator (plan §17.3).
|
|
@@ -10493,8 +10396,7 @@ const pwLocatorNormalize = defineRule({
|
|
|
10493
10396
|
return findings;
|
|
10494
10397
|
}
|
|
10495
10398
|
});
|
|
10496
|
-
|
|
10497
|
-
//#region src/rules/playwright/qa-pw-147-codegen-artifact.ts
|
|
10399
|
+
// src/rules/playwright/qa-pw-147-codegen-artifact.ts
|
|
10498
10400
|
/**
|
|
10499
10401
|
* QA-PW-147 — Playwright codegen artifact left unedited (plan §17.1).
|
|
10500
10402
|
* Severity: info · Confidence: medium · observation
|
|
@@ -10553,8 +10455,7 @@ const pwCodegenArtifact = defineRule({
|
|
|
10553
10455
|
return findings;
|
|
10554
10456
|
}
|
|
10555
10457
|
});
|
|
10556
|
-
|
|
10557
|
-
//#region src/rules/python/qa-py-101-sync-async-mix.ts
|
|
10458
|
+
// src/rules/python/qa-py-101-sync-async-mix.ts
|
|
10558
10459
|
/**
|
|
10559
10460
|
* QA-PY-101 — Playwright-Python: sync API in async test (or vice versa).
|
|
10560
10461
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -10603,8 +10504,7 @@ const pyPwSyncAsyncMix = defineRule({
|
|
|
10603
10504
|
return findings;
|
|
10604
10505
|
}
|
|
10605
10506
|
});
|
|
10606
|
-
|
|
10607
|
-
//#region src/rules/python/qa-py-103-wait-for-timeout.ts
|
|
10507
|
+
// src/rules/python/qa-py-103-wait-for-timeout.ts
|
|
10608
10508
|
/**
|
|
10609
10509
|
* QA-PY-103 — Playwright-Python: wait_for_timeout() as synchronization.
|
|
10610
10510
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -10651,8 +10551,7 @@ const pyPwWaitForTimeout = defineRule({
|
|
|
10651
10551
|
return findings;
|
|
10652
10552
|
}
|
|
10653
10553
|
});
|
|
10654
|
-
|
|
10655
|
-
//#region src/rules/python/qa-py-105-pw-no-assertions.ts
|
|
10554
|
+
// src/rules/python/qa-py-105-pw-no-assertions.ts
|
|
10656
10555
|
/**
|
|
10657
10556
|
* QA-PY-105 — Playwright-Python: no assertion in Playwright test.
|
|
10658
10557
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -10732,8 +10631,7 @@ function extractBlock(text, afterColon) {
|
|
|
10732
10631
|
const lineEnd = rest.indexOf("\n", firstContent.index);
|
|
10733
10632
|
return lineEnd === -1 ? rest : rest.slice(0, lineEnd);
|
|
10734
10633
|
}
|
|
10735
|
-
|
|
10736
|
-
//#region src/rules/java/qa-jv-101-disabled-test.ts
|
|
10634
|
+
// src/rules/java/qa-jv-101-disabled-test.ts
|
|
10737
10635
|
/**
|
|
10738
10636
|
* QA-JV-101 — @Disabled test without justification.
|
|
10739
10637
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -10787,8 +10685,7 @@ const jvDisabledTest = defineRule({
|
|
|
10787
10685
|
return findings;
|
|
10788
10686
|
}
|
|
10789
10687
|
});
|
|
10790
|
-
|
|
10791
|
-
//#region src/rules/java/qa-jv-103-no-assertions.ts
|
|
10688
|
+
// src/rules/java/qa-jv-103-no-assertions.ts
|
|
10792
10689
|
/**
|
|
10793
10690
|
* QA-JV-103 — Test method without assertions.
|
|
10794
10691
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -10945,8 +10842,7 @@ function matchBrace$1(text, open) {
|
|
|
10945
10842
|
}
|
|
10946
10843
|
return -1;
|
|
10947
10844
|
}
|
|
10948
|
-
|
|
10949
|
-
//#region src/engine/qa-model.ts
|
|
10845
|
+
// src/engine/qa-model.ts
|
|
10950
10846
|
/**
|
|
10951
10847
|
* Extract the semantic model for one parsed file. Resolves undefined
|
|
10952
10848
|
* when the language has no extractor or the parse stage produced no
|
|
@@ -11489,8 +11385,7 @@ function extractPythonModel(file) {
|
|
|
11489
11385
|
nodes
|
|
11490
11386
|
};
|
|
11491
11387
|
}
|
|
11492
|
-
|
|
11493
|
-
//#region src/rules/java/qa-jv-105-wait-for-timeout.ts
|
|
11388
|
+
// src/rules/java/qa-jv-105-wait-for-timeout.ts
|
|
11494
11389
|
/**
|
|
11495
11390
|
* QA-JV-105 — waitForTimeout / hardcoded navigation waits.
|
|
11496
11391
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -11575,8 +11470,7 @@ const jvWaitForTimeout = defineRule({
|
|
|
11575
11470
|
return findings;
|
|
11576
11471
|
}
|
|
11577
11472
|
});
|
|
11578
|
-
|
|
11579
|
-
//#region src/rules/csharp/qa-cs-101-skipped-test.ts
|
|
11473
|
+
// src/rules/csharp/qa-cs-101-skipped-test.ts
|
|
11580
11474
|
/**
|
|
11581
11475
|
* QA-CS-101 — [Ignore]/[Skip] test without justification.
|
|
11582
11476
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -11640,8 +11534,7 @@ const csSkippedTest = defineRule({
|
|
|
11640
11534
|
return findings;
|
|
11641
11535
|
}
|
|
11642
11536
|
});
|
|
11643
|
-
|
|
11644
|
-
//#region src/rules/csharp/qa-cs-103-no-assertions.ts
|
|
11537
|
+
// src/rules/csharp/qa-cs-103-no-assertions.ts
|
|
11645
11538
|
/**
|
|
11646
11539
|
* QA-CS-103 — Test method without assertions.
|
|
11647
11540
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -11675,7 +11568,6 @@ const csSkippedTest = defineRule({
|
|
|
11675
11568
|
* Deep-diffing those requires symbol semantics Mjölnir deliberately does
|
|
11676
11569
|
* not promise for C# (plan §13.4).
|
|
11677
11570
|
*/
|
|
11678
|
-
/** Helper/verification idiom: verify/check/assert-prefixed names. */
|
|
11679
11571
|
const WHY$5 = "Without an assertion the test can only fail by throwing — it cannot detect behavioral regressions.";
|
|
11680
11572
|
const FIX$4 = "Add an assertion on the expected outcome, or remove the test.";
|
|
11681
11573
|
function makeFinding(path, position, name) {
|
|
@@ -11839,8 +11731,7 @@ function matchBrace(text, open) {
|
|
|
11839
11731
|
}
|
|
11840
11732
|
return -1;
|
|
11841
11733
|
}
|
|
11842
|
-
|
|
11843
|
-
//#region src/rules/csharp/qa-cs-105-wait-for-timeout.ts
|
|
11734
|
+
// src/rules/csharp/qa-cs-105-wait-for-timeout.ts
|
|
11844
11735
|
/**
|
|
11845
11736
|
* QA-CS-105 — Playwright-.NET: WaitForTimeoutAsync hard sleep.
|
|
11846
11737
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -11928,8 +11819,7 @@ const csWaitForTimeout = defineRule({
|
|
|
11928
11819
|
return findings;
|
|
11929
11820
|
}
|
|
11930
11821
|
});
|
|
11931
|
-
|
|
11932
|
-
//#region src/rules/cypress/qa-cyp-001-cy-wait.ts
|
|
11822
|
+
// src/rules/cypress/qa-cyp-001-cy-wait.ts
|
|
11933
11823
|
/**
|
|
11934
11824
|
* QA-CYP-001 — Fixed `cy.wait(n)` (hard-coded wait).
|
|
11935
11825
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -12008,8 +11898,7 @@ function isCypressFile(ctx) {
|
|
|
12008
11898
|
if (/\.cy\.[jt]sx?$/.test(ctx.path)) return true;
|
|
12009
11899
|
return /\bcy\s*\./.test(ctx.codeText ?? ctx.text);
|
|
12010
11900
|
}
|
|
12011
|
-
|
|
12012
|
-
//#region src/rules/cypress/qa-cyp-002-focused-test.ts
|
|
11901
|
+
// src/rules/cypress/qa-cyp-002-focused-test.ts
|
|
12013
11902
|
/**
|
|
12014
11903
|
* QA-CYP-002 — Focused test (`.only`).
|
|
12015
11904
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -12072,8 +11961,7 @@ const cypFocusedTest = defineRule({
|
|
|
12072
11961
|
return findings;
|
|
12073
11962
|
}
|
|
12074
11963
|
});
|
|
12075
|
-
|
|
12076
|
-
//#region src/rules/cypress/qa-cyp-003-config-security.ts
|
|
11964
|
+
// src/rules/cypress/qa-cyp-003-config-security.ts
|
|
12077
11965
|
/**
|
|
12078
11966
|
* QA-CYP-003 — Cypress config disables `chromeWebSecurity`.
|
|
12079
11967
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -12143,8 +12031,7 @@ const cypConfigSecurity = defineRule({
|
|
|
12143
12031
|
return findings;
|
|
12144
12032
|
}
|
|
12145
12033
|
});
|
|
12146
|
-
|
|
12147
|
-
//#region src/rules/selenium/qa-se-sleep-lookup.ts
|
|
12034
|
+
// src/rules/selenium/qa-se-sleep-lookup.ts
|
|
12148
12035
|
/**
|
|
12149
12036
|
* Selenium sleep-then-interact rules (Verification Trust Evolution Plan
|
|
12150
12037
|
* §15.3 — "Selenium implicit-wait abuse / missing WebDriverWait",
|
|
@@ -12295,8 +12182,7 @@ const sePythonSleepLookup = defineRule({
|
|
|
12295
12182
|
return sleepBeforeLookup(ctx, PY_SLEEP_RE, PY_LOOKUP_RE, "QA-SE-003");
|
|
12296
12183
|
}
|
|
12297
12184
|
});
|
|
12298
|
-
|
|
12299
|
-
//#region src/rules/shared/family.ts
|
|
12185
|
+
// src/rules/shared/family.ts
|
|
12300
12186
|
/**
|
|
12301
12187
|
* Cross-language pattern rule family generator (Phase 6 — Tempering Plan).
|
|
12302
12188
|
*
|
|
@@ -12362,8 +12248,7 @@ function definePatternFamily(opts) {
|
|
|
12362
12248
|
}
|
|
12363
12249
|
}));
|
|
12364
12250
|
}
|
|
12365
|
-
|
|
12366
|
-
//#region src/rules/families/hard-sleep.ts
|
|
12251
|
+
// src/rules/families/hard-sleep.ts
|
|
12367
12252
|
/**
|
|
12368
12253
|
* Hard-sleep family (Phase 6 — Tempering Plan).
|
|
12369
12254
|
* Java: Thread.sleep(), C#: Thread.Sleep() / Task.Delay()
|
|
@@ -12514,8 +12399,7 @@ const hardSleepFamily = definePatternFamily({
|
|
|
12514
12399
|
fix: "Use `await Assertions.Expect(locator).ToBeVisibleAsync()` or locator.WaitForAsync()."
|
|
12515
12400
|
}]
|
|
12516
12401
|
});
|
|
12517
|
-
|
|
12518
|
-
//#region src/rules/families/network-idle.ts
|
|
12402
|
+
// src/rules/families/network-idle.ts
|
|
12519
12403
|
/**
|
|
12520
12404
|
* Network-idle family (Phase 6 — Tempering Plan).
|
|
12521
12405
|
* All three language variants of waitForLoadState(networkidle).
|
|
@@ -12576,8 +12460,7 @@ const networkIdleFamily = definePatternFamily({
|
|
|
12576
12460
|
}
|
|
12577
12461
|
]
|
|
12578
12462
|
});
|
|
12579
|
-
|
|
12580
|
-
//#region src/rules/families/hardcoded-url.ts
|
|
12463
|
+
// src/rules/families/hardcoded-url.ts
|
|
12581
12464
|
/**
|
|
12582
12465
|
* Hardcoded-URL family (Phase 6 — Tempering Plan).
|
|
12583
12466
|
* Detects absolute URLs in navigation/request calls across languages.
|
|
@@ -12627,8 +12510,7 @@ const hardcodedUrlFamily = definePatternFamily({
|
|
|
12627
12510
|
fix: "Use `--base-url` / `base_url` fixture, or `os.environ[...]`."
|
|
12628
12511
|
}]
|
|
12629
12512
|
});
|
|
12630
|
-
|
|
12631
|
-
//#region src/rules/families/shared-page.ts
|
|
12513
|
+
// src/rules/families/shared-page.ts
|
|
12632
12514
|
/**
|
|
12633
12515
|
* Shared-page family (Phase 6 — Tempering Plan).
|
|
12634
12516
|
* Detects static/module-level Page/Browser instances shared across tests.
|
|
@@ -12689,8 +12571,7 @@ const sharedPageFamily = definePatternFamily({
|
|
|
12689
12571
|
}
|
|
12690
12572
|
]
|
|
12691
12573
|
});
|
|
12692
|
-
|
|
12693
|
-
//#region src/rules/families/brittle-selectors.ts
|
|
12574
|
+
// src/rules/families/brittle-selectors.ts
|
|
12694
12575
|
/**
|
|
12695
12576
|
* Brittle-selectors family (Phase 6 — Tempering Plan).
|
|
12696
12577
|
* Detects XPath/structural CSS selectors across Java, C#, Python.
|
|
@@ -12802,8 +12683,7 @@ const brittleSelectorsFamily = [
|
|
|
12802
12683
|
}
|
|
12803
12684
|
], "Prefer role-based locators (`get_by_role(...)`) or data-testid attributes.")
|
|
12804
12685
|
];
|
|
12805
|
-
|
|
12806
|
-
//#region src/rules/families/retry-masking.ts
|
|
12686
|
+
// src/rules/families/retry-masking.ts
|
|
12807
12687
|
/**
|
|
12808
12688
|
* Retry-masking family (Phase 6 — Tempering Plan).
|
|
12809
12689
|
* Detects retry annotations/attributes that mask flaky tests.
|
|
@@ -12923,8 +12803,7 @@ const retryMaskingFamily = [defineRule({
|
|
|
12923
12803
|
return findings;
|
|
12924
12804
|
}
|
|
12925
12805
|
})];
|
|
12926
|
-
|
|
12927
|
-
//#region src/rules/index.ts
|
|
12806
|
+
// src/rules/index.ts
|
|
12928
12807
|
const RULES = [
|
|
12929
12808
|
focusedTestCommitted,
|
|
12930
12809
|
skippedTest,
|
|
@@ -13046,8 +12925,7 @@ const RETIRED_RULE_IDS = [
|
|
|
13046
12925
|
"QA-JV-111",
|
|
13047
12926
|
"QA-CS-111"
|
|
13048
12927
|
];
|
|
13049
|
-
|
|
13050
|
-
//#region src/lib/wilson.ts
|
|
12928
|
+
// src/lib/wilson.ts
|
|
13051
12929
|
/**
|
|
13052
12930
|
* 95% Wilson score interval on a proportion (plan §20.2).
|
|
13053
12931
|
*
|
|
@@ -13170,8 +13048,7 @@ function isProvisional(rule) {
|
|
|
13170
13048
|
function isRetiredRule(ruleId) {
|
|
13171
13049
|
return RETIRED_RULE_IDS.includes(ruleId);
|
|
13172
13050
|
}
|
|
13173
|
-
|
|
13174
|
-
//#region src/scorer/scorer.ts
|
|
13051
|
+
// src/scorer/scorer.ts
|
|
13175
13052
|
/**
|
|
13176
13053
|
* Transparent scorer (Product-MVP §8).
|
|
13177
13054
|
* score = 100 − Σ(severity deductions), floor 0.
|
|
@@ -13323,9 +13200,9 @@ function countTestDeclarations(text, codeText) {
|
|
|
13323
13200
|
const source = codeText ?? text;
|
|
13324
13201
|
const patterns = [
|
|
13325
13202
|
/\b(?:it|test)(?:\.\w+)*\s*(?:\(|`)/g,
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
|
|
13203
|
+
/^[ \t]*(?:async[ \t]+)?def[ \t]+test_\w*/gm,
|
|
13204
|
+
/^[ \t]*@Test\b/gm,
|
|
13205
|
+
/^[ \t]*\[(?:Test|Fact|Theory|TestMethod)\b/gm
|
|
13329
13206
|
];
|
|
13330
13207
|
let count = 0;
|
|
13331
13208
|
for (const re of patterns) {
|
|
@@ -13334,8 +13211,7 @@ function countTestDeclarations(text, codeText) {
|
|
|
13334
13211
|
}
|
|
13335
13212
|
return count;
|
|
13336
13213
|
}
|
|
13337
|
-
|
|
13338
|
-
//#region src/engine/rule-runner.ts
|
|
13214
|
+
// src/engine/rule-runner.ts
|
|
13339
13215
|
/**
|
|
13340
13216
|
* Legacy appliesTo → adapter id mapping.
|
|
13341
13217
|
*
|
|
@@ -13374,8 +13250,7 @@ function asUniversal(rule) {
|
|
|
13374
13250
|
}
|
|
13375
13251
|
};
|
|
13376
13252
|
}
|
|
13377
|
-
|
|
13378
|
-
//#region src/engine/tier-policy.ts
|
|
13253
|
+
// src/engine/tier-policy.ts
|
|
13379
13254
|
/**
|
|
13380
13255
|
* Tier policy (audit H-1): the declared tier is authoritative over a
|
|
13381
13256
|
* rule's severity and evidence claims.
|
|
@@ -13515,8 +13390,7 @@ function enforceTierPolicy(findings, tierByRuleId) {
|
|
|
13515
13390
|
if (EVIDENCE_RANK$1[effective] < EVIDENCE_RANK$1[floor.evidenceLevel]) f.evidenceLevel = floor.evidenceLevel;
|
|
13516
13391
|
}
|
|
13517
13392
|
}
|
|
13518
|
-
|
|
13519
|
-
//#region src/engine/evidence-core.ts
|
|
13393
|
+
// src/engine/evidence-core.ts
|
|
13520
13394
|
/**
|
|
13521
13395
|
* The core's own version — stamped into every record's provenance so a
|
|
13522
13396
|
* consumer can tell which normalization contract produced a record.
|
|
@@ -13583,8 +13457,7 @@ function countEvidence(records) {
|
|
|
13583
13457
|
}
|
|
13584
13458
|
return counts;
|
|
13585
13459
|
}
|
|
13586
|
-
|
|
13587
|
-
//#region src/engine/provenance.ts
|
|
13460
|
+
// src/engine/provenance.ts
|
|
13588
13461
|
const GENERATED_HEADER_RE = /^\s*(?:\/\/|#|\/\*)\s*(?:auto[- ]?generated|generated by|do not edit)/i;
|
|
13589
13462
|
/**
|
|
13590
13463
|
* Codegen fingerprint: the recorder names its first test exactly
|
|
@@ -13625,8 +13498,7 @@ function computeAgenticProfile(files, findings) {
|
|
|
13625
13498
|
note: "Static provenance markers only (generated-file headers, Playwright codegen default titles). Absence of a marker does not prove human authorship; healed and MCP-edited files without markers are indistinguishable from hand-written ones. Provenance never changes scoring or evidence — authorship is metadata, not trust."
|
|
13626
13499
|
};
|
|
13627
13500
|
}
|
|
13628
|
-
|
|
13629
|
-
//#region src/engine/overlap-dedup.ts
|
|
13501
|
+
// src/engine/overlap-dedup.ts
|
|
13630
13502
|
const TIER_RANK = {
|
|
13631
13503
|
core: 0,
|
|
13632
13504
|
extended: 1,
|
|
@@ -13721,8 +13593,7 @@ function dropOrderBefore(a, b, metaByRuleId) {
|
|
|
13721
13593
|
if (ranksBefore(b, a, metaByRuleId)) return true;
|
|
13722
13594
|
return (a.column ?? Number.MAX_SAFE_INTEGER) < (b.column ?? Number.MAX_SAFE_INTEGER);
|
|
13723
13595
|
}
|
|
13724
|
-
|
|
13725
|
-
//#region src/engine/finding-identity.ts
|
|
13596
|
+
// src/engine/finding-identity.ts
|
|
13726
13597
|
/**
|
|
13727
13598
|
* Finding Identity (ENGINE-002): deterministic, stable fingerprints for
|
|
13728
13599
|
* deduplication, correlation, and lifecycle tracking across scans.
|
|
@@ -13761,8 +13632,7 @@ function codeQualityFingerprint(f) {
|
|
|
13761
13632
|
function findingId(f) {
|
|
13762
13633
|
return `${findingFingerprint(f)}\u0000${f.line}\u0000${f.column}`;
|
|
13763
13634
|
}
|
|
13764
|
-
|
|
13765
|
-
//#region src/engine/correlation-engine.ts
|
|
13635
|
+
// src/engine/correlation-engine.ts
|
|
13766
13636
|
const EVIDENCE_RANK = {
|
|
13767
13637
|
E0: 0,
|
|
13768
13638
|
E1: 1,
|
|
@@ -13892,8 +13762,7 @@ function correlateFindings(findings) {
|
|
|
13892
13762
|
});
|
|
13893
13763
|
return conclusions;
|
|
13894
13764
|
}
|
|
13895
|
-
|
|
13896
|
-
//#region src/engine/dependency-graph.ts
|
|
13765
|
+
// src/engine/dependency-graph.ts
|
|
13897
13766
|
/**
|
|
13898
13767
|
* Dependency Graph (ECO-005).
|
|
13899
13768
|
*
|
|
@@ -14066,8 +13935,7 @@ function getReachableFiles(fromFiles, graph) {
|
|
|
14066
13935
|
resolvedAny: unresolvedStarts.length < fromFiles.length
|
|
14067
13936
|
};
|
|
14068
13937
|
}
|
|
14069
|
-
|
|
14070
|
-
//#region src/engine/incremental-analysis.ts
|
|
13938
|
+
// src/engine/incremental-analysis.ts
|
|
14071
13939
|
/**
|
|
14072
13940
|
* Semantic inputs that affect the full scan beyond individual file content.
|
|
14073
13941
|
* If any of these changed, an incremental run is NOT safe — a full scan
|
|
@@ -14103,8 +13971,7 @@ function isIncrementalSafe(changedFiles) {
|
|
|
14103
13971
|
reasons
|
|
14104
13972
|
};
|
|
14105
13973
|
}
|
|
14106
|
-
|
|
14107
|
-
//#region src/brand/tokens.ts
|
|
13974
|
+
// src/brand/tokens.ts
|
|
14108
13975
|
/**
|
|
14109
13976
|
* The single source of brand truth.
|
|
14110
13977
|
*
|
|
@@ -14339,8 +14206,7 @@ RUNG_MEANINGS.map((meaning, i) => ({
|
|
|
14339
14206
|
runtime: i >= 3,
|
|
14340
14207
|
color: RUNG_COLORS[i]
|
|
14341
14208
|
}));
|
|
14342
|
-
|
|
14343
|
-
//#region src/reporter/presentation.ts
|
|
14209
|
+
// src/reporter/presentation.ts
|
|
14344
14210
|
/**
|
|
14345
14211
|
* presentation.ts — the one place a presentation DECISION is made.
|
|
14346
14212
|
*
|
|
@@ -14562,8 +14428,7 @@ function evidenceTag(f) {
|
|
|
14562
14428
|
if (runtime !== null) tag += ` · runtime: ${runtime}`;
|
|
14563
14429
|
return `[${tag}]`;
|
|
14564
14430
|
}
|
|
14565
|
-
|
|
14566
|
-
//#region src/engine/monorepo-analysis.ts
|
|
14431
|
+
// src/engine/monorepo-analysis.ts
|
|
14567
14432
|
/**
|
|
14568
14433
|
* Package verdict, derived from the one ScoreState band model (BW-104).
|
|
14569
14434
|
* These two constants used to be a private copy of the band boundaries;
|
|
@@ -14670,8 +14535,7 @@ function configurableAggregation(results, config) {
|
|
|
14670
14535
|
strategy: config.weightingStrategy
|
|
14671
14536
|
};
|
|
14672
14537
|
}
|
|
14673
|
-
|
|
14674
|
-
//#region src/lib/fs-atomic.ts
|
|
14538
|
+
// src/lib/fs-atomic.ts
|
|
14675
14539
|
/**
|
|
14676
14540
|
* Atomic file writes (audit S9).
|
|
14677
14541
|
*
|
|
@@ -14791,8 +14655,7 @@ function pidAlive(pid) {
|
|
|
14791
14655
|
return err?.code !== "ESRCH";
|
|
14792
14656
|
}
|
|
14793
14657
|
}
|
|
14794
|
-
|
|
14795
|
-
//#region src/engine/scan-cache.ts
|
|
14658
|
+
// src/engine/scan-cache.ts
|
|
14796
14659
|
/**
|
|
14797
14660
|
* Local incremental scan cache (Beta-to-Stable 1.0 plan, M5.2 / A-2).
|
|
14798
14661
|
*
|
|
@@ -15002,8 +14865,7 @@ function createScanCache(root) {
|
|
|
15002
14865
|
}
|
|
15003
14866
|
};
|
|
15004
14867
|
}
|
|
15005
|
-
|
|
15006
|
-
//#region src/reporter/theme.ts
|
|
14868
|
+
// src/reporter/theme.ts
|
|
15007
14869
|
/**
|
|
15008
14870
|
* Norse-forge theme system. Pure string-in → string-out helpers.
|
|
15009
14871
|
* Respects NO_COLOR and non-TTY via `palette(isTTY)` — every renderer
|
|
@@ -15212,8 +15074,7 @@ function gaugeColorForBand(band, p) {
|
|
|
15212
15074
|
if (band === "unmeasured") return p.dim;
|
|
15213
15075
|
return band === "warning" ? p.warning : p.error;
|
|
15214
15076
|
}
|
|
15215
|
-
|
|
15216
|
-
//#region src/reporter/ui.ts
|
|
15077
|
+
// src/reporter/ui.ts
|
|
15217
15078
|
/**
|
|
15218
15079
|
* Neutral context for direct calls (tests, library use) and as the
|
|
15219
15080
|
* safe default when a command runner is invoked without wiring: no
|
|
@@ -15302,8 +15163,7 @@ function formatDuration(ms) {
|
|
|
15302
15163
|
if (ms === void 0) return "?";
|
|
15303
15164
|
return ms >= 1e3 ? `${(ms / 1e3).toFixed(1)}s` : `${ms}ms`;
|
|
15304
15165
|
}
|
|
15305
|
-
|
|
15306
|
-
//#region src/forensics/classify.ts
|
|
15166
|
+
// src/forensics/classify.ts
|
|
15307
15167
|
/**
|
|
15308
15168
|
* Strong-signal marker tables. Kept deliberately narrow: a marker here
|
|
15309
15169
|
* is a named family fact, not a vibe. Unknown text yields no signal —
|
|
@@ -15423,8 +15283,7 @@ function classifyForensicVerdict(signals) {
|
|
|
15423
15283
|
signals: signalCounts
|
|
15424
15284
|
};
|
|
15425
15285
|
}
|
|
15426
|
-
|
|
15427
|
-
//#region src/forensics/analyze.ts
|
|
15286
|
+
// src/forensics/analyze.ts
|
|
15428
15287
|
const ui$11 = plainContext();
|
|
15429
15288
|
const MAX_RECORDS = 1e5;
|
|
15430
15289
|
function analyze(records, source) {
|
|
@@ -15587,8 +15446,7 @@ function renderFlakyMd(report) {
|
|
|
15587
15446
|
lines.push("");
|
|
15588
15447
|
return lines.join("\n");
|
|
15589
15448
|
}
|
|
15590
|
-
|
|
15591
|
-
//#region src/forensics/evidence-hygiene.ts
|
|
15449
|
+
// src/forensics/evidence-hygiene.ts
|
|
15592
15450
|
/**
|
|
15593
15451
|
* Evidence hygiene — secret redaction + control-char sanitization
|
|
15594
15452
|
* (ENGINE-012).
|
|
@@ -15650,8 +15508,7 @@ function sanitizeErrorText(text, options) {
|
|
|
15650
15508
|
if (result.length > maxLength) result = result.slice(0, maxLength) + `[truncated at ${maxLength} chars]`;
|
|
15651
15509
|
return result;
|
|
15652
15510
|
}
|
|
15653
|
-
|
|
15654
|
-
//#region src/forensics/trace.ts
|
|
15511
|
+
// src/forensics/trace.ts
|
|
15655
15512
|
/**
|
|
15656
15513
|
* Playwright trace ingester (product-gap master plan §9 R5 / WI-17).
|
|
15657
15514
|
*
|
|
@@ -15844,8 +15701,7 @@ function parseTraceArtifact(bytes, artifactName) {
|
|
|
15844
15701
|
const { actions } = parseTraceNdjson(text);
|
|
15845
15702
|
return traceActionsToRecord(actions, artifactName);
|
|
15846
15703
|
}
|
|
15847
|
-
|
|
15848
|
-
//#region src/forensics/parse-jest-json.ts
|
|
15704
|
+
// src/forensics/parse-jest-json.ts
|
|
15849
15705
|
function toStatus$2(raw) {
|
|
15850
15706
|
switch (raw) {
|
|
15851
15707
|
case "passed": return "passed";
|
|
@@ -15905,8 +15761,7 @@ function parseJestJson(json) {
|
|
|
15905
15761
|
}
|
|
15906
15762
|
return out;
|
|
15907
15763
|
}
|
|
15908
|
-
|
|
15909
|
-
//#region src/forensics/parse-junit.ts
|
|
15764
|
+
// src/forensics/parse-junit.ts
|
|
15910
15765
|
const MAX_INPUT$1 = 20971520;
|
|
15911
15766
|
function decodeEntities$1(s) {
|
|
15912
15767
|
return s.replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("'", "'").replaceAll("&", "&");
|
|
@@ -15948,14 +15803,25 @@ function parseJunitXml(xml) {
|
|
|
15948
15803
|
const skipped = /<skipped\b/i.test(inner);
|
|
15949
15804
|
const errors = [];
|
|
15950
15805
|
if (failed) {
|
|
15951
|
-
const
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
15806
|
+
const openRe = /<(?:failure|error)\b/gi;
|
|
15807
|
+
const endRe = /<\/(?:failure|error)\s*>/gi;
|
|
15808
|
+
let open;
|
|
15809
|
+
while ((open = openRe.exec(inner)) !== null) {
|
|
15810
|
+
const gt = inner.indexOf(">", open.index);
|
|
15811
|
+
if (gt === -1) break;
|
|
15812
|
+
const attrsStr = inner.slice(open.index, gt);
|
|
15813
|
+
if (attrsStr.endsWith("/")) {
|
|
15814
|
+
openRe.lastIndex = gt + 1;
|
|
15815
|
+
continue;
|
|
15816
|
+
}
|
|
15817
|
+
endRe.lastIndex = gt + 1;
|
|
15818
|
+
const end = endRe.exec(inner);
|
|
15819
|
+
if (end === null) break;
|
|
15955
15820
|
const msgAttr = /\smessage\s*=\s*"([^"]*)"/i.exec(attrsStr)?.[1];
|
|
15956
15821
|
if (msgAttr) errors.push(sanitizeErrorText(decodeEntities$1(msgAttr)));
|
|
15957
|
-
const body =
|
|
15822
|
+
const body = inner.slice(gt + 1, end.index).trim();
|
|
15958
15823
|
if (body && body !== msgAttr) errors.push(sanitizeErrorText(decodeEntities$1(body)));
|
|
15824
|
+
openRe.lastIndex = end.index + end[0].length;
|
|
15959
15825
|
}
|
|
15960
15826
|
const selfCloseRe = /<(?:failure|error)\b([^>]*)\/>/gi;
|
|
15961
15827
|
let scMatch;
|
|
@@ -15979,8 +15845,7 @@ function parseJunitXml(xml) {
|
|
|
15979
15845
|
}
|
|
15980
15846
|
return out;
|
|
15981
15847
|
}
|
|
15982
|
-
|
|
15983
|
-
//#region src/forensics/parse-trx.ts
|
|
15848
|
+
// src/forensics/parse-trx.ts
|
|
15984
15849
|
const MAX_INPUT = 20971520;
|
|
15985
15850
|
function decodeEntities(s) {
|
|
15986
15851
|
return s.replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("'", "'").replaceAll("&", "&");
|
|
@@ -16091,11 +15956,24 @@ function parseTrxXml(xml) {
|
|
|
16091
15956
|
if (id !== void 0 && name !== void 0) byTestId.set(id, name);
|
|
16092
15957
|
}
|
|
16093
15958
|
const records = [];
|
|
16094
|
-
const
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
|
|
16098
|
-
|
|
15959
|
+
const startRe = /<UnitTestResult\b/gi;
|
|
15960
|
+
const starts = [];
|
|
15961
|
+
let s;
|
|
15962
|
+
while ((s = startRe.exec(xml)) !== null) {
|
|
15963
|
+
starts.push(s.index);
|
|
15964
|
+
if (starts.length > 2e5) break;
|
|
15965
|
+
}
|
|
15966
|
+
const resultsEnd = xml.search(/<\/Results>/i);
|
|
15967
|
+
for (let i = 0; i < starts.length; i++) {
|
|
15968
|
+
const start = starts[i];
|
|
15969
|
+
const tagEnd = xml.indexOf(">", start);
|
|
15970
|
+
if (tagEnd === -1) break;
|
|
15971
|
+
const selfClosing = xml[tagEnd - 1] === "/";
|
|
15972
|
+
const attrs = xml.slice(start + 15, selfClosing ? tagEnd - 1 : tagEnd);
|
|
15973
|
+
const next = starts[i + 1] ?? -1;
|
|
15974
|
+
let bodyEnd = next === -1 ? xml.length : next;
|
|
15975
|
+
if (resultsEnd > tagEnd && resultsEnd < bodyEnd) bodyEnd = resultsEnd;
|
|
15976
|
+
const inner = selfClosing ? "" : xml.slice(tagEnd + 1, bodyEnd);
|
|
16099
15977
|
const testName = attr(attrs, "testName") ?? attr(attrs, "testId");
|
|
16100
15978
|
if (testName === void 0) continue;
|
|
16101
15979
|
const attempt = {
|
|
@@ -16103,7 +15981,8 @@ function parseTrxXml(xml) {
|
|
|
16103
15981
|
status: mapTrxOutcome(attr(attrs, "outcome")),
|
|
16104
15982
|
durationMs: parseDuration(attr(attrs, "duration"))
|
|
16105
15983
|
};
|
|
16106
|
-
const
|
|
15984
|
+
const messageAt = inner.search(/<Message\b/i);
|
|
15985
|
+
const error = messageAt === -1 ? void 0 : messageOf(inner, messageAt);
|
|
16107
15986
|
records.push({
|
|
16108
15987
|
file: "unknown",
|
|
16109
15988
|
title: testName,
|
|
@@ -16115,8 +15994,7 @@ function parseTrxXml(xml) {
|
|
|
16115
15994
|
}
|
|
16116
15995
|
return records;
|
|
16117
15996
|
}
|
|
16118
|
-
|
|
16119
|
-
//#region src/forensics/parse-playwright-json.ts
|
|
15997
|
+
// src/forensics/parse-playwright-json.ts
|
|
16120
15998
|
const MAX_DEPTH = 32;
|
|
16121
15999
|
function toStatus$1(raw) {
|
|
16122
16000
|
switch (raw) {
|
|
@@ -16181,8 +16059,7 @@ function parsePlaywrightJson(json) {
|
|
|
16181
16059
|
walk(root, 0);
|
|
16182
16060
|
return out;
|
|
16183
16061
|
}
|
|
16184
|
-
|
|
16185
|
-
//#region src/forensics/parse-vitest-json.ts
|
|
16062
|
+
// src/forensics/parse-vitest-json.ts
|
|
16186
16063
|
function toStatus(raw) {
|
|
16187
16064
|
switch (raw) {
|
|
16188
16065
|
case "passed": return "passed";
|
|
@@ -16236,8 +16113,7 @@ function parseVitestJson(json) {
|
|
|
16236
16113
|
}
|
|
16237
16114
|
return out;
|
|
16238
16115
|
}
|
|
16239
|
-
|
|
16240
|
-
//#region src/forensics/parse-har.ts
|
|
16116
|
+
// src/forensics/parse-har.ts
|
|
16241
16117
|
const MAX_ENTRIES = 2e4;
|
|
16242
16118
|
function sanitizeTitleUrl(url) {
|
|
16243
16119
|
try {
|
|
@@ -16322,8 +16198,7 @@ function parseHarJsonDetailed(json) {
|
|
|
16322
16198
|
truncated
|
|
16323
16199
|
};
|
|
16324
16200
|
}
|
|
16325
|
-
|
|
16326
|
-
//#region src/forensics/run.ts
|
|
16201
|
+
// src/forensics/run.ts
|
|
16327
16202
|
/**
|
|
16328
16203
|
* Runtime evidence entry point (R4) — the library behind `explain
|
|
16329
16204
|
* --evidence`. The `forensics` verb is retired in the v6 carve; this module
|
|
@@ -16600,8 +16475,7 @@ function listFiles(dir) {
|
|
|
16600
16475
|
walk(dir, 0);
|
|
16601
16476
|
return out.map((p) => p);
|
|
16602
16477
|
}
|
|
16603
|
-
|
|
16604
|
-
//#region src/config/suppressions.ts
|
|
16478
|
+
// src/config/suppressions.ts
|
|
16605
16479
|
/**
|
|
16606
16480
|
* Suppression governance (Sprint-Plan W7, Product-MVP §12).
|
|
16607
16481
|
* `mjolnir scan --suppressions` — every suppression stays visible.
|
|
@@ -16638,8 +16512,7 @@ function renderSuppressions(report) {
|
|
|
16638
16512
|
lines.push("");
|
|
16639
16513
|
return lines.join("\n");
|
|
16640
16514
|
}
|
|
16641
|
-
|
|
16642
|
-
//#region src/plugins/reserved-prefixes.ts
|
|
16515
|
+
// src/plugins/reserved-prefixes.ts
|
|
16643
16516
|
/**
|
|
16644
16517
|
* Core-owned rule-ID prefixes that external rule sources (npm plugins,
|
|
16645
16518
|
* JS modules, JSON manifests) may never claim — one shared law.
|
|
@@ -16669,8 +16542,7 @@ function isReservedPrefix(ruleId) {
|
|
|
16669
16542
|
const upper = ruleId.toUpperCase();
|
|
16670
16543
|
return RESERVED_PREFIXES.some((p) => upper.startsWith(p));
|
|
16671
16544
|
}
|
|
16672
|
-
|
|
16673
|
-
//#region src/plugins/load.ts
|
|
16545
|
+
// src/plugins/load.ts
|
|
16674
16546
|
/**
|
|
16675
16547
|
* Plugin API (Upgrade-Plan-v3 Phase 6, tier-2 item #1).
|
|
16676
16548
|
*
|
|
@@ -16776,8 +16648,7 @@ function loadPlugins(root, gateOpen = false) {
|
|
|
16776
16648
|
}
|
|
16777
16649
|
return result;
|
|
16778
16650
|
}
|
|
16779
|
-
|
|
16780
|
-
//#region src/plugins/local-rules.ts
|
|
16651
|
+
// src/plugins/local-rules.ts
|
|
16781
16652
|
/**
|
|
16782
16653
|
* Local plugin/rule contract (Verification Trust Evolution Plan §18 —
|
|
16783
16654
|
* Local Extensibility).
|
|
@@ -17089,8 +16960,7 @@ async function loadModuleRules(path, result) {
|
|
|
17089
16960
|
result.rules.push(rule);
|
|
17090
16961
|
}
|
|
17091
16962
|
}
|
|
17092
|
-
|
|
17093
|
-
//#region src/plugins/trust-gate.ts
|
|
16963
|
+
// src/plugins/trust-gate.ts
|
|
17094
16964
|
/**
|
|
17095
16965
|
* Plugin trust gate (audit-remediation-master-plan.md, locked decision C2).
|
|
17096
16966
|
*
|
|
@@ -17126,8 +16996,7 @@ function renderGateNotice(skipped) {
|
|
|
17126
16996
|
lines.push("To run them, re-run with --enable-plugins (or set MJOLNIR_ENABLE_PLUGINS=1).", "Declarative JSON rule manifests (mjolnir-rules/*.json) are unaffected — they execute no code.");
|
|
17127
16997
|
return lines.join("\n");
|
|
17128
16998
|
}
|
|
17129
|
-
|
|
17130
|
-
//#region src/engine/scan-pipeline.ts
|
|
16999
|
+
// src/engine/scan-pipeline.ts
|
|
17131
17000
|
/**
|
|
17132
17001
|
* Canonical scan pipeline (M6 — blueprint §9.2).
|
|
17133
17002
|
*
|
|
@@ -17205,9 +17074,10 @@ async function buildUniversalRules$1(root, strict, opts = {}) {
|
|
|
17205
17074
|
for (const p of plugins) for (const r of p.rules) if (r.tier) tierByRuleId.set(r.id, r.tier);
|
|
17206
17075
|
for (const r of local.rules) if (r.tier) tierByRuleId.set(r.id, r.tier);
|
|
17207
17076
|
let rules = [...UNIVERSAL_RULES, ...externalRules];
|
|
17208
|
-
const
|
|
17077
|
+
const quarantined = rules.filter((r) => tierByRuleId.get(r.id) === "quarantine").map((r) => r.id).sort();
|
|
17078
|
+
const withheldRuleIds = strict ? [] : quarantined;
|
|
17209
17079
|
if (!strict) rules = rules.filter((r) => tierByRuleId.get(r.id) !== "quarantine");
|
|
17210
|
-
const rulesWithheld =
|
|
17080
|
+
const rulesWithheld = withheldRuleIds.length;
|
|
17211
17081
|
const rulesApplied = rules.length;
|
|
17212
17082
|
const pluginMeta = [...plugins.map((p) => ({
|
|
17213
17083
|
name: p.name,
|
|
@@ -17604,12 +17474,28 @@ function applyPostScanProcessing(findings, workspace, args, hooks, scanRoot, dec
|
|
|
17604
17474
|
const runtimeIncomplete = discoveredReport !== void 0 && discoveredReport.report.analysisComplete !== true;
|
|
17605
17475
|
let forensicVerdicts;
|
|
17606
17476
|
let evidenceRecords = [];
|
|
17607
|
-
if (discoveredReport && discoveredReport.report.analysisComplete === true)
|
|
17608
|
-
|
|
17609
|
-
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
|
|
17477
|
+
if (discoveredReport && discoveredReport.report.analysisComplete === true) {
|
|
17478
|
+
const before = findings.map((f) => ({
|
|
17479
|
+
runtimeCorroboration: f.runtimeCorroboration,
|
|
17480
|
+
trustLevel: f.trustLevel
|
|
17481
|
+
}));
|
|
17482
|
+
try {
|
|
17483
|
+
const records = buildEvidenceRecords(discoveredReport.report, discoveredReport.path);
|
|
17484
|
+
stampRuntimeCorroboration(findings, discoveredReport.report, workspace.root);
|
|
17485
|
+
forensicVerdicts = summarizeForensicVerdicts(discoveredReport.report);
|
|
17486
|
+
evidenceRecords = records;
|
|
17487
|
+
} catch {
|
|
17488
|
+
findings.forEach((f, i) => {
|
|
17489
|
+
const prev = before[i];
|
|
17490
|
+
if (prev?.runtimeCorroboration === void 0) delete f.runtimeCorroboration;
|
|
17491
|
+
else f.runtimeCorroboration = prev.runtimeCorroboration;
|
|
17492
|
+
if (prev?.trustLevel === void 0) delete f.trustLevel;
|
|
17493
|
+
else f.trustLevel = prev.trustLevel;
|
|
17494
|
+
});
|
|
17495
|
+
forensicVerdicts = void 0;
|
|
17496
|
+
evidenceRecords = [];
|
|
17497
|
+
recordDegradation("runtime-corroboration-failed");
|
|
17498
|
+
}
|
|
17613
17499
|
}
|
|
17614
17500
|
return {
|
|
17615
17501
|
evidenceRecords,
|
|
@@ -18042,8 +17928,7 @@ async function runScanInProject(args, hooks = {}) {
|
|
|
18042
17928
|
resetTsMorphProject();
|
|
18043
17929
|
return result;
|
|
18044
17930
|
}
|
|
18045
|
-
|
|
18046
|
-
//#region src/commands/help.ts
|
|
17931
|
+
// src/commands/help.ts
|
|
18047
17932
|
/** Ordered registry — grouped by the same categories the overview uses. */
|
|
18048
17933
|
const HELP_ENTRIES = [
|
|
18049
17934
|
{
|
|
@@ -18482,8 +18367,7 @@ function renderRootHelp(schemaVersion = 1, options = {}) {
|
|
|
18482
18367
|
lines.push(`Docs: ${DOCS_URL} (JSON schemaVersion ${schemaVersion}, additive-only)`);
|
|
18483
18368
|
return lines.join("\n");
|
|
18484
18369
|
}
|
|
18485
|
-
|
|
18486
|
-
//#region src/integrations/sentry.ts
|
|
18370
|
+
// src/integrations/sentry.ts
|
|
18487
18371
|
/**
|
|
18488
18372
|
* Sentry crash reporting — opt-in, and inert unless a DSN is configured.
|
|
18489
18373
|
*
|
|
@@ -18609,8 +18493,7 @@ async function flushSentry(timeoutMs = FLUSH_TIMEOUT_MS) {
|
|
|
18609
18493
|
return false;
|
|
18610
18494
|
}
|
|
18611
18495
|
}
|
|
18612
|
-
|
|
18613
|
-
//#region src/integrations/ci-install.ts
|
|
18496
|
+
// src/integrations/ci-install.ts
|
|
18614
18497
|
/**
|
|
18615
18498
|
* CI integration (Sprint-Plan W7): generates .github/workflows/mjolnir.yml
|
|
18616
18499
|
* from internal templates ONLY — no user-input interpolation (R3 supply-chain).
|
|
@@ -18697,10 +18580,12 @@ function publishedVersionForInstall(startDir = import.meta.dirname) {
|
|
|
18697
18580
|
return ENGINE_VERSION;
|
|
18698
18581
|
}
|
|
18699
18582
|
/**
|
|
18700
|
-
* Template
|
|
18701
|
-
*
|
|
18702
|
-
*
|
|
18703
|
-
*
|
|
18583
|
+
* Template v3: the reporting steps render the saved report with
|
|
18584
|
+
* `mjolnir mjolnir.json --format github-summary` (annotations + step summary
|
|
18585
|
+
* via ONE emitter) and `--format pr-comment`. Template v2, generated through
|
|
18586
|
+
* 6.0.0, called `summary` and `pr-comment --from`, the verbs those formats
|
|
18587
|
+
* replaced, so its reporting steps exited 10 and, being `continue-on-error`,
|
|
18588
|
+
* did so silently: no annotations and no comment, on every run. The gate
|
|
18704
18589
|
* script is unchanged (reads `partial`, `findings[].severity`).
|
|
18705
18590
|
*/
|
|
18706
18591
|
/** Renders findings into `$GITHUB_STEP_SUMMARY`; tolerates a missing scan result.
|
|
@@ -18725,12 +18610,48 @@ const SUMMARY_SCRIPT_V1 = [
|
|
|
18725
18610
|
"}",
|
|
18726
18611
|
"process.stdout.write(lines.length + \" finding(s)\\n\");"
|
|
18727
18612
|
].join("\n");
|
|
18728
|
-
/** True when a workflow was generated by any Mjölnir template (v1 or
|
|
18613
|
+
/** True when a workflow was generated by any Mjölnir template (v1, v2 or v3).
|
|
18614
|
+
*
|
|
18615
|
+
* The comparison runs on the canonical form, so a workflow generated by an
|
|
18616
|
+
* earlier release still matches. It used to be byte equality with the current
|
|
18617
|
+
* template, and the pinned package version alone made every file from the
|
|
18618
|
+
* previous release read as hand-customised: an upgrade needed `--force`, the
|
|
18619
|
+
* one flag that also overwrites real customisations.
|
|
18620
|
+
*
|
|
18729
18621
|
* The v1 needle is matched in its INDENTED form: the v1 template embedded
|
|
18730
18622
|
* the script via indentBlock(…, 10) inside the `run: |` scalar, so the
|
|
18731
18623
|
* raw unindented substring never appears in a real v1 file. */
|
|
18732
18624
|
function isKnownTemplate(content) {
|
|
18733
|
-
|
|
18625
|
+
const file = canonicalWorkflow(content);
|
|
18626
|
+
return GATES.some((g) => {
|
|
18627
|
+
const npx = canonicalWorkflow(TEMPLATE(g));
|
|
18628
|
+
const action = canonicalWorkflow(ACTION_TEMPLATE(g));
|
|
18629
|
+
return file === npx || file === templateV2(npx) || file === action || file === actionTemplateWithoutVersion(action);
|
|
18630
|
+
}) || file.includes(indentBlock(SUMMARY_SCRIPT_V1, 10));
|
|
18631
|
+
}
|
|
18632
|
+
/**
|
|
18633
|
+
* A generated workflow with the parts that legitimately change between
|
|
18634
|
+
* releases blanked: line endings, the pinned package version (in the npx
|
|
18635
|
+
* tarball and in the Action's `version:` input) and the pinned Action SHA.
|
|
18636
|
+
*/
|
|
18637
|
+
function canonicalWorkflow(text) {
|
|
18638
|
+
return text.replace(/\r\n/g, "\n").replace(/mjolnir-qa-[0-9A-Za-z.+-]+?\.tgz/g, "mjolnir-qa-<version>.tgz").replace(/(\n {10}version: )"[^"\n]*"/g, "$1\"<version>\"").replace(/Sergey-Bar\/Mjolnir@[0-9a-f]{40}/g, "Sergey-Bar/Mjolnir@<sha>");
|
|
18639
|
+
}
|
|
18640
|
+
/**
|
|
18641
|
+
* The action template through 6.0.0, which passed no `version:` and so ran
|
|
18642
|
+
* whatever its pinned Action defaulted to.
|
|
18643
|
+
*/
|
|
18644
|
+
function actionTemplateWithoutVersion(canonical) {
|
|
18645
|
+
return canonical.replace("\n version: \"<version>\"", "");
|
|
18646
|
+
}
|
|
18647
|
+
/**
|
|
18648
|
+
* Template v2, the npx template through 6.0.0, rebuilt from v3: the same file
|
|
18649
|
+
* with its two reporting steps calling the verbs the formats replaced. A v2
|
|
18650
|
+
* workflow is recognised because it is broken, so `ci install` has to be able
|
|
18651
|
+
* to replace it without `--force`.
|
|
18652
|
+
*/
|
|
18653
|
+
function templateV2(v3) {
|
|
18654
|
+
return v3.replace(".tgz mjolnir.json --format github-summary\n", ".tgz summary mjolnir.json\n").replace(".tgz mjolnir.json --format pr-comment > mjolnir-comment.md\n", ".tgz pr-comment --from mjolnir.json > mjolnir-comment.md\n");
|
|
18734
18655
|
}
|
|
18735
18656
|
/** Indents an embedded script so it sits inside a YAML `run: |` block scalar. */
|
|
18736
18657
|
function indentBlock(text, spaces) {
|
|
@@ -18773,11 +18694,11 @@ jobs:
|
|
|
18773
18694
|
- name: Annotations + Job Summary
|
|
18774
18695
|
if: always()
|
|
18775
18696
|
continue-on-error: true
|
|
18776
|
-
run: npx --yes https://registry.npmjs.org/mjolnir-qa/-/mjolnir-qa-${INSTALL_VERSION}.tgz
|
|
18697
|
+
run: npx --yes https://registry.npmjs.org/mjolnir-qa/-/mjolnir-qa-${INSTALL_VERSION}.tgz mjolnir.json --format github-summary
|
|
18777
18698
|
- name: Render PR comment
|
|
18778
18699
|
if: always()
|
|
18779
18700
|
continue-on-error: true
|
|
18780
|
-
run: npx --yes https://registry.npmjs.org/mjolnir-qa/-/mjolnir-qa-${INSTALL_VERSION}.tgz pr-comment
|
|
18701
|
+
run: npx --yes https://registry.npmjs.org/mjolnir-qa/-/mjolnir-qa-${INSTALL_VERSION}.tgz mjolnir.json --format pr-comment > mjolnir-comment.md
|
|
18781
18702
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
18782
18703
|
if: always()
|
|
18783
18704
|
with:
|
|
@@ -18846,12 +18767,22 @@ const GATES = [
|
|
|
18846
18767
|
"warning"
|
|
18847
18768
|
];
|
|
18848
18769
|
/**
|
|
18849
|
-
* The
|
|
18850
|
-
*
|
|
18851
|
-
*
|
|
18852
|
-
*
|
|
18770
|
+
* The Action the action-based template pins: a commit on `main`, by SHA,
|
|
18771
|
+
* because a generated workflow must never resolve a mutable ref.
|
|
18772
|
+
*
|
|
18773
|
+
* It is the merge commit of #740, the first commit on `main` whose
|
|
18774
|
+
* `action.yml` renders the saved report with `--format pr-comment` and
|
|
18775
|
+
* `--format github-summary`. The pin it replaced, 4a588bc, was a pull-request
|
|
18776
|
+
* branch commit that `main` never contained: its `action.yml` installed
|
|
18777
|
+
* `mjolnir-qa@latest` and called the retired `trust-report` verb, and every
|
|
18778
|
+
* workflow `ci install` generated ran it. A pin can only name a commit that
|
|
18779
|
+
* already exists, so it moves in the change AFTER the Action it points at.
|
|
18780
|
+
*
|
|
18781
|
+
* The template passes `version:` because the Action's own default is the
|
|
18782
|
+
* published stable AT THIS COMMIT, which may be an older CLI than the one that
|
|
18783
|
+
* generated the workflow.
|
|
18853
18784
|
*/
|
|
18854
|
-
const ACTION_REF = "Sergey-Bar/Mjolnir@
|
|
18785
|
+
const ACTION_REF = "Sergey-Bar/Mjolnir@6bf46a0e64e2f3ee16266d8456d1b16e5ae16214";
|
|
18855
18786
|
/**
|
|
18856
18787
|
* The action-based workflow for one gate level (P1.3): the root
|
|
18857
18788
|
* action.yml does checkout-independent scanning — setup-node, the scan
|
|
@@ -18874,8 +18805,11 @@ const ACTION_REF = "Sergey-Bar/Mjolnir@4a588bc62d517bc85fc44c0eae64c6587d3bf70b"
|
|
|
18874
18805
|
* finish is not a pass — see the exit-code contract in docs/VERSIONING.md.
|
|
18875
18806
|
* 3. `version:` was pinned to the engine's working version, which while the
|
|
18876
18807
|
* candidate is a release candidate is not on npm, so the generated
|
|
18877
|
-
* workflow could not install what it asked for.
|
|
18878
|
-
*
|
|
18808
|
+
* workflow could not install what it asked for. It was then omitted,
|
|
18809
|
+
* which handed the choice to the pinned Action's default, the stable that
|
|
18810
|
+
* was published when that commit was made. It now names the published
|
|
18811
|
+
* version the installing package resolves, the same pin the npx template
|
|
18812
|
+
* uses.
|
|
18879
18813
|
*/
|
|
18880
18814
|
const ACTION_TEMPLATE = (gate) => `name: Mjölnir
|
|
18881
18815
|
|
|
@@ -18905,6 +18839,7 @@ jobs:
|
|
|
18905
18839
|
continue-on-error: true
|
|
18906
18840
|
uses: ${ACTION_REF}
|
|
18907
18841
|
with:
|
|
18842
|
+
version: "${INSTALL_VERSION}"
|
|
18908
18843
|
scope: changed
|
|
18909
18844
|
format: json
|
|
18910
18845
|
fail-on: ${gate === "advisory" ? "none" : gate}
|
|
@@ -19015,8 +18950,7 @@ function ciInstall(root, gate, options = {}) {
|
|
|
19015
18950
|
diffSummary: []
|
|
19016
18951
|
};
|
|
19017
18952
|
}
|
|
19018
|
-
|
|
19019
|
-
//#region src/cli-io.ts
|
|
18953
|
+
// src/cli-io.ts
|
|
19020
18954
|
const out = (...parts) => console.log(parts.map(String).join(" "));
|
|
19021
18955
|
const err = (...parts) => console.error(parts.map(String).join(" "));
|
|
19022
18956
|
function internalErrorMessage(err, emit, debug) {
|
|
@@ -19185,8 +19119,7 @@ function buildMachineContract(result) {
|
|
|
19185
19119
|
...result.forensicVerdicts !== void 0 ? { forensicVerdicts: result.forensicVerdicts } : {}
|
|
19186
19120
|
};
|
|
19187
19121
|
}
|
|
19188
|
-
|
|
19189
|
-
//#region src/commands/fixture-example.ts
|
|
19122
|
+
// src/commands/fixture-example.ts
|
|
19190
19123
|
/**
|
|
19191
19124
|
* Shared example-fixture selection for `mjolnir explain` (explain.ts)
|
|
19192
19125
|
* and the generated rule docs (rule-docs.ts) — one chooser so both
|
|
@@ -19204,8 +19137,7 @@ function firstFixtureFile(dir) {
|
|
|
19204
19137
|
const entries = readdirSync(dir).filter((f) => !f.startsWith(".")).sort();
|
|
19205
19138
|
return entries.length > 0 ? join(dir, entries[0]) : null;
|
|
19206
19139
|
}
|
|
19207
|
-
|
|
19208
|
-
//#region src/commands/explain.ts
|
|
19140
|
+
// src/commands/explain.ts
|
|
19209
19141
|
/**
|
|
19210
19142
|
* `mjolnir explain <RULE-ID>` — implements Plan.md Sprint 1.3
|
|
19211
19143
|
* (Master-Stabilization-Plan Sprint 5, Task 19).
|
|
@@ -19546,8 +19478,7 @@ function renderVerdictExplain(result, width = DEFAULT_EXPLAIN_WIDTH) {
|
|
|
19546
19478
|
function pctOf(v) {
|
|
19547
19479
|
return `${Math.round(v * 100)}%`;
|
|
19548
19480
|
}
|
|
19549
|
-
|
|
19550
|
-
//#region src/engine/resolution.ts
|
|
19481
|
+
// src/engine/resolution.ts
|
|
19551
19482
|
/** Correlation identity (v1-compatible): ruleId\0file\0message. */
|
|
19552
19483
|
const fingerprint$1 = findingFingerprint;
|
|
19553
19484
|
/**
|
|
@@ -19612,8 +19543,7 @@ function resolve$1(input) {
|
|
|
19612
19543
|
comparedAgainst
|
|
19613
19544
|
};
|
|
19614
19545
|
}
|
|
19615
|
-
|
|
19616
|
-
//#region src/commands/baseline.ts
|
|
19546
|
+
// src/commands/baseline.ts
|
|
19617
19547
|
/**
|
|
19618
19548
|
* A shared finding-set READER. Neither verb it used to serve survives the v6 CLI collapse (plan §3): `baseline` and `diff` are both retired, and the capability they shared is now `mjolnir scan --scope changed`.
|
|
19619
19549
|
* (Master-Stabilization-Plan.md).
|
|
@@ -19767,8 +19697,7 @@ function renderBaselineSaved(path, count, replaced) {
|
|
|
19767
19697
|
lines.push(nextStep("mjolnir scan --scope changed", ui$8) + " — see only what's new.");
|
|
19768
19698
|
return lines.join("\n");
|
|
19769
19699
|
}
|
|
19770
|
-
|
|
19771
|
-
//#region src/commands/verify.ts
|
|
19700
|
+
// src/commands/verify.ts
|
|
19772
19701
|
/**
|
|
19773
19702
|
* `mjolnir ci verify` — the agent-loop digest (product-gap-remediation
|
|
19774
19703
|
* master plan P7, plan 1788853205786 — flag agent, decision 6).
|
|
@@ -19875,8 +19804,7 @@ function renderVerifyDigest(digest) {
|
|
|
19875
19804
|
}
|
|
19876
19805
|
return lines.join("\n").trimEnd();
|
|
19877
19806
|
}
|
|
19878
|
-
|
|
19879
|
-
//#region src/forensics/triage.ts
|
|
19807
|
+
// src/forensics/triage.ts
|
|
19880
19808
|
const ui$7 = plainContext();
|
|
19881
19809
|
const QUARANTINE_MIN_ATTEMPTS = 2;
|
|
19882
19810
|
/** Deterministic triage table rows, worst first. */
|
|
@@ -20023,8 +19951,7 @@ function renderTriageWorkflowJson(report) {
|
|
|
20023
19951
|
rows: workflowRows(report)
|
|
20024
19952
|
}, null, 2) + "\n";
|
|
20025
19953
|
}
|
|
20026
|
-
|
|
20027
|
-
//#region src/commands/pw-report.ts
|
|
19954
|
+
// src/commands/pw-report.ts
|
|
20028
19955
|
const ui$6 = plainContext();
|
|
20029
19956
|
function summarizePwRun(report) {
|
|
20030
19957
|
const slowest = [...report.verdicts].sort((a, b) => b.totalDurationMs - a.totalDurationMs).slice(0, 5).map((v) => ({
|
|
@@ -20058,8 +19985,7 @@ function renderPwRunSummary(s) {
|
|
|
20058
19985
|
}
|
|
20059
19986
|
return lines.join("\n");
|
|
20060
19987
|
}
|
|
20061
|
-
|
|
20062
|
-
//#region src/mcp/server.ts
|
|
19988
|
+
// src/mcp/server.ts
|
|
20063
19989
|
/**
|
|
20064
19990
|
* MCP stdio transport (blueprint §21 — Phase 4, PURE TRANSPORT).
|
|
20065
19991
|
*
|
|
@@ -20077,7 +20003,10 @@ function renderPwRunSummary(s) {
|
|
|
20077
20003
|
* - parameter size caps; ONE scan in flight (serialized queue) reusing
|
|
20078
20004
|
* the pipeline's own budgets; zero network (no fetches anywhere); no
|
|
20079
20005
|
* shell execution beyond Mjölnir's own verbs.
|
|
20080
|
-
* - filesystem boundary
|
|
20006
|
+
* - filesystem boundary: the tools are read-only, and with no further
|
|
20007
|
+
* configuration they accept any path the parent process passes (the
|
|
20008
|
+
* parent is the trust boundary). `MJOLNIR_WORKSPACE_ROOT` confines every
|
|
20009
|
+
* path argument to that directory, symlinks resolved.
|
|
20081
20010
|
* - the plugin trust gate applies unchanged (--enable-plugins is NOT
|
|
20082
20011
|
* exposed over MCP: tools run with the gate CLOSED unless the server
|
|
20083
20012
|
* was started with MJOLNIR_ENABLE_PLUGINS=1 in its own environment).
|
|
@@ -20087,12 +20016,27 @@ const MAX_PARAM_BYTES = 65536;
|
|
|
20087
20016
|
const MAX_DURATION_MS$1 = 36e5;
|
|
20088
20017
|
const DEFAULT_MAX_DURATION_MS$1 = 6e5;
|
|
20089
20018
|
const PROTOCOL_VERSION = "2025-06-18";
|
|
20090
|
-
|
|
20019
|
+
/** The real path of `p`, or its lexical resolution when it does not exist. */
|
|
20020
|
+
function realOrResolved(p) {
|
|
20021
|
+
try {
|
|
20022
|
+
return realpathSync.native(resolve(p));
|
|
20023
|
+
} catch {
|
|
20024
|
+
return resolve(p);
|
|
20025
|
+
}
|
|
20026
|
+
}
|
|
20027
|
+
/**
|
|
20028
|
+
* The optional workspace root. When set, every path argument must resolve
|
|
20029
|
+
* INSIDE it after symlinks are followed.
|
|
20030
|
+
*
|
|
20031
|
+
* Unset is open by design and is documented that way in the header: the MCP
|
|
20032
|
+
* tools are read-only and the stdio parent is the trust boundary. Setting
|
|
20033
|
+
* `MJOLNIR_WORKSPACE_ROOT` is how a host confines them.
|
|
20034
|
+
*/
|
|
20035
|
+
const CONFIGURED_ROOT = process.env.MJOLNIR_WORKSPACE_ROOT ? realOrResolved(process.env.MJOLNIR_WORKSPACE_ROOT) : null;
|
|
20091
20036
|
function isPathAllowed(path) {
|
|
20092
20037
|
if (!CONFIGURED_ROOT) return true;
|
|
20093
|
-
const
|
|
20094
|
-
|
|
20095
|
-
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
20038
|
+
const rel = relative(CONFIGURED_ROOT, realOrResolved(path));
|
|
20039
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
20096
20040
|
}
|
|
20097
20041
|
/** JSON-RPC error codes (subset used here). */
|
|
20098
20042
|
const MCP_ERRORS = {
|
|
@@ -20569,8 +20513,7 @@ async function handleMcpMessage(msg) {
|
|
|
20569
20513
|
}
|
|
20570
20514
|
};
|
|
20571
20515
|
}
|
|
20572
|
-
|
|
20573
|
-
//#region src/mcp/transport.ts
|
|
20516
|
+
// src/mcp/transport.ts
|
|
20574
20517
|
/**
|
|
20575
20518
|
* MCP stdio transport loop (blueprint §21 — the framing around the
|
|
20576
20519
|
* pure-transport server).
|
|
@@ -20667,8 +20610,7 @@ async function handleStdioLine(line, write) {
|
|
|
20667
20610
|
}
|
|
20668
20611
|
write(`${JSON.stringify(response)}\n`);
|
|
20669
20612
|
}
|
|
20670
|
-
|
|
20671
|
-
//#region src/scorer/prioritize.ts
|
|
20613
|
+
// src/scorer/prioritize.ts
|
|
20672
20614
|
/**
|
|
20673
20615
|
* "Fix this first" prioritization (Master-Stabilization-Plan Sprint 5,
|
|
20674
20616
|
* Task 20).
|
|
@@ -20718,8 +20660,7 @@ function prioritize(findings) {
|
|
|
20718
20660
|
function topFixes(findings, n = 3) {
|
|
20719
20661
|
return prioritize(findings).filter((p) => p.scoreGain > 0).slice(0, n);
|
|
20720
20662
|
}
|
|
20721
|
-
|
|
20722
|
-
//#region src/reporter/art.ts
|
|
20663
|
+
// src/reporter/art.ts
|
|
20723
20664
|
/**
|
|
20724
20665
|
* ASCII art assets for the Mjölnir terminal experience.
|
|
20725
20666
|
* Minimal Nordic / engineering aesthetic — professional, not fantasy.
|
|
@@ -20756,8 +20697,7 @@ const TROPHY = String.raw`
|
|
|
20756
20697
|
"─".repeat(58);
|
|
20757
20698
|
/** Wordmark for the 100-state celebration block. */
|
|
20758
20699
|
const FORGED_WORDMARK = "⚡ F O R G E D ⚡";
|
|
20759
|
-
|
|
20760
|
-
//#region src/reporter/tone-blunt.ts
|
|
20700
|
+
// src/reporter/tone-blunt.ts
|
|
20761
20701
|
/**
|
|
20762
20702
|
* Static blunt messages keyed by rule ID. A simple string lookup
|
|
20763
20703
|
* (no per-entry closures) so coverage is not artificially fragmented
|
|
@@ -20829,8 +20769,7 @@ function bluntMessage(f) {
|
|
|
20829
20769
|
if (msg !== void 0) return msg;
|
|
20830
20770
|
return `${f.message} — fix it or suppress it; ignoring it is worse than either.`;
|
|
20831
20771
|
}
|
|
20832
|
-
|
|
20833
|
-
//#region src/reporter/terminal.ts
|
|
20772
|
+
// src/reporter/terminal.ts
|
|
20834
20773
|
/** Non-verbose finding cards shown before the overflow line. The
|
|
20835
20774
|
* JSON/SARIF contract always carries ALL findings — this cap is a
|
|
20836
20775
|
* terminal display concern only. */
|
|
@@ -21312,8 +21251,7 @@ function countBySeverity(result) {
|
|
|
21312
21251
|
}
|
|
21313
21252
|
return counts;
|
|
21314
21253
|
}
|
|
21315
|
-
|
|
21316
|
-
//#region src/engine/trust-classification.ts
|
|
21254
|
+
// src/engine/trust-classification.ts
|
|
21317
21255
|
/**
|
|
21318
21256
|
* Completeness, decided once.
|
|
21319
21257
|
*
|
|
@@ -21419,8 +21357,7 @@ function classifyTrust(result, summary) {
|
|
|
21419
21357
|
gaps
|
|
21420
21358
|
};
|
|
21421
21359
|
}
|
|
21422
|
-
|
|
21423
|
-
//#region src/lib/format.ts
|
|
21360
|
+
// src/lib/format.ts
|
|
21424
21361
|
/**
|
|
21425
21362
|
* Shared formatting utilities. Single definition site for `pct()` (Task 20)
|
|
21426
21363
|
* — the two callers (commands/trust-report.ts, reporter/trust-report.ts)
|
|
@@ -21429,8 +21366,7 @@ function classifyTrust(result, summary) {
|
|
|
21429
21366
|
function pct$1(v) {
|
|
21430
21367
|
return `${Math.round(v * 100)}%`;
|
|
21431
21368
|
}
|
|
21432
|
-
|
|
21433
|
-
//#region src/reporter/trust-report.ts
|
|
21369
|
+
// src/reporter/trust-report.ts
|
|
21434
21370
|
/** The one label rule, so no renderer grows a second opinion. */
|
|
21435
21371
|
function gateLabel(f, gate) {
|
|
21436
21372
|
return isAtGate(f, gate, isAdvisoryFinding) ? "GATE" : "WARN";
|
|
@@ -21571,8 +21507,7 @@ function renderTrustReport(result, opts) {
|
|
|
21571
21507
|
lines.push("");
|
|
21572
21508
|
return lines.join("\n");
|
|
21573
21509
|
}
|
|
21574
|
-
|
|
21575
|
-
//#region src/reporter/sarif.ts
|
|
21510
|
+
// src/reporter/sarif.ts
|
|
21576
21511
|
/**
|
|
21577
21512
|
* SARIF fingerprint values must be printable and stable. The engine's identity
|
|
21578
21513
|
* strings join their parts with NUL (a separator no repository path or message
|
|
@@ -21684,6 +21619,10 @@ function renderSarif(result, repoRootUri) {
|
|
|
21684
21619
|
}
|
|
21685
21620
|
}))
|
|
21686
21621
|
};
|
|
21622
|
+
const srcRoot = (uri) => {
|
|
21623
|
+
const encoded = uri.replaceAll("\\", "/").split("/").map((seg) => seg === "" ? "" : encodeURI(seg).replaceAll("%25", "%").replaceAll("#", "%23").replaceAll("?", "%3F")).join("/");
|
|
21624
|
+
return encoded.endsWith("/") ? encoded : `${encoded}/`;
|
|
21625
|
+
};
|
|
21687
21626
|
const sarif = {
|
|
21688
21627
|
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
21689
21628
|
version: [
|
|
@@ -21693,13 +21632,12 @@ function renderSarif(result, repoRootUri) {
|
|
|
21693
21632
|
].join("."),
|
|
21694
21633
|
runs: [repoRootUri ? {
|
|
21695
21634
|
...run,
|
|
21696
|
-
originalUriBaseIds: { SRCROOT: { uri: repoRootUri
|
|
21635
|
+
originalUriBaseIds: { SRCROOT: { uri: srcRoot(repoRootUri) } }
|
|
21697
21636
|
} : run]
|
|
21698
21637
|
};
|
|
21699
21638
|
return JSON.stringify(sarif, null, 2);
|
|
21700
21639
|
}
|
|
21701
|
-
|
|
21702
|
-
//#region src/reporter/codequality.ts
|
|
21640
|
+
// src/reporter/codequality.ts
|
|
21703
21641
|
function codeQualitySeverity(severity) {
|
|
21704
21642
|
if (severity === "error") return "major";
|
|
21705
21643
|
if (severity === "warning") return "minor";
|
|
@@ -21718,8 +21656,7 @@ function renderCodeQuality(result) {
|
|
|
21718
21656
|
}));
|
|
21719
21657
|
return JSON.stringify(issues, null, 2) + "\n";
|
|
21720
21658
|
}
|
|
21721
|
-
|
|
21722
|
-
//#region src/reporter/mermaid.ts
|
|
21659
|
+
// src/reporter/mermaid.ts
|
|
21723
21660
|
/**
|
|
21724
21661
|
* `--format mermaid` — test-architecture diagram (Sprint 9 Task 38,
|
|
21725
21662
|
* Master-Stabilization-Plan.md). Ranked first among the delight
|
|
@@ -21818,8 +21755,7 @@ function renderMermaid(result) {
|
|
|
21818
21755
|
for (const { id, cls } of styleAssignments) lines.push(` class ${id} ${cls};`);
|
|
21819
21756
|
return lines.join("\n");
|
|
21820
21757
|
}
|
|
21821
|
-
|
|
21822
|
-
//#region src/reporter/progress.ts
|
|
21758
|
+
// src/reporter/progress.ts
|
|
21823
21759
|
/**
|
|
21824
21760
|
* Live scan progress (Terminal + CI UX Overhaul plan, M3).
|
|
21825
21761
|
*
|
|
@@ -21918,8 +21854,7 @@ var ProgressRenderer = class {
|
|
|
21918
21854
|
this.erase();
|
|
21919
21855
|
}
|
|
21920
21856
|
};
|
|
21921
|
-
|
|
21922
|
-
//#region src/lib/git-utils.ts
|
|
21857
|
+
// src/lib/git-utils.ts
|
|
21923
21858
|
/**
|
|
21924
21859
|
* Shared git utilities (Task 18). The `currentCommit` / `targetCommit`
|
|
21925
21860
|
* pattern was duplicated between cli.ts and commands/trust-report.ts.
|
|
@@ -21952,8 +21887,7 @@ function currentCommit(root, opts = {}) {
|
|
|
21952
21887
|
return opts.nullable ? null : "unknown";
|
|
21953
21888
|
}
|
|
21954
21889
|
}
|
|
21955
|
-
|
|
21956
|
-
//#region src/commands/report-io.ts
|
|
21890
|
+
// src/commands/report-io.ts
|
|
21957
21891
|
/**
|
|
21958
21892
|
* Shared saved-report loading (agent-handoff plan §9.0).
|
|
21959
21893
|
*
|
|
@@ -22146,8 +22080,7 @@ function loadSavedReport(reportPath) {
|
|
|
22146
22080
|
function reportExists(reportPath) {
|
|
22147
22081
|
return existsSync(reportPath);
|
|
22148
22082
|
}
|
|
22149
|
-
|
|
22150
|
-
//#region src/commands/trust-report.ts
|
|
22083
|
+
// src/commands/trust-report.ts
|
|
22151
22084
|
function buildArtifactIdentity(result, commit) {
|
|
22152
22085
|
const revisions = /* @__PURE__ */ new Map();
|
|
22153
22086
|
const levels = /* @__PURE__ */ new Map();
|
|
@@ -22173,8 +22106,7 @@ function buildArtifactIdentity(result, commit) {
|
|
|
22173
22106
|
}
|
|
22174
22107
|
};
|
|
22175
22108
|
}
|
|
22176
|
-
|
|
22177
|
-
//#region src/reporter/pr-report-actions.ts
|
|
22109
|
+
// src/reporter/pr-report-actions.ts
|
|
22178
22110
|
/** GitHub-native actions. Social drafts contain only public project information. */
|
|
22179
22111
|
const PROJECT_URL = "https://github.com/Sergey-Bar/Mjolnir";
|
|
22180
22112
|
const SHARE_URL = "https://sergey-bar.github.io/Mjolnir/";
|
|
@@ -22293,8 +22225,7 @@ const PR_BRAND_CONTRACT = {
|
|
|
22293
22225
|
terminology: "trust-engine",
|
|
22294
22226
|
footerFormat: "scanId + generatedBy"
|
|
22295
22227
|
};
|
|
22296
|
-
|
|
22297
|
-
//#region src/reporter/pr-report-shared.ts
|
|
22228
|
+
// src/reporter/pr-report-shared.ts
|
|
22298
22229
|
const UNIFIED_MARKER = "<!-- mjolnir-report:v2 -->";
|
|
22299
22230
|
/**
|
|
22300
22231
|
* The one place a comment asset URL is written down.
|
|
@@ -22698,8 +22629,7 @@ function enforceCommentLimit(lines) {
|
|
|
22698
22629
|
}
|
|
22699
22630
|
return render() + notice;
|
|
22700
22631
|
}
|
|
22701
|
-
|
|
22702
|
-
//#region src/commands/pr-comment.ts
|
|
22632
|
+
// src/commands/pr-comment.ts
|
|
22703
22633
|
/**
|
|
22704
22634
|
* Escape markdown-significant characters (markdown spec).
|
|
22705
22635
|
* Kept exported for downstream consumer compatibility.
|
|
@@ -22721,8 +22651,7 @@ function renderPrComment(result, options = {}) {
|
|
|
22721
22651
|
...options.commit !== void 0 ? { commit: options.commit } : {}
|
|
22722
22652
|
});
|
|
22723
22653
|
}
|
|
22724
|
-
|
|
22725
|
-
//#region src/commands/why.ts
|
|
22654
|
+
// src/commands/why.ts
|
|
22726
22655
|
/**
|
|
22727
22656
|
* `mjolnir explain <file:line> <file>:<line>` — occurrence-level evidence/explanation
|
|
22728
22657
|
* query (agent-handoff plan M2).
|
|
@@ -22894,8 +22823,7 @@ async function runWhyCommand(argv, io = {
|
|
|
22894
22823
|
}));
|
|
22895
22824
|
return filtered.length > 0 ? 0 : 1;
|
|
22896
22825
|
}
|
|
22897
|
-
|
|
22898
|
-
//#region src/commands/handover.ts
|
|
22826
|
+
// src/commands/handover.ts
|
|
22899
22827
|
const ui$5 = plainContext();
|
|
22900
22828
|
const FAKE_GREEN_RULES = /* @__PURE__ */ new Set([
|
|
22901
22829
|
"QA-TEST-003",
|
|
@@ -22983,8 +22911,7 @@ function renderHandover(map) {
|
|
|
22983
22911
|
lines.push("Generated by mjolnir explain --plan — re-run after big refactors.");
|
|
22984
22912
|
return lines.join("\n");
|
|
22985
22913
|
}
|
|
22986
|
-
|
|
22987
|
-
//#region src/commands/stats.ts
|
|
22914
|
+
// src/commands/stats.ts
|
|
22988
22915
|
/**
|
|
22989
22916
|
* `mjolnir stats` — Sprint 6 Task 26 (Master-Stabilization-Plan.md).
|
|
22990
22917
|
*
|
|
@@ -23104,14 +23031,72 @@ function renderStats(stats) {
|
|
|
23104
23031
|
lines.push("different machine — this file is local-only, by design.");
|
|
23105
23032
|
return lines.join("\n");
|
|
23106
23033
|
}
|
|
23107
|
-
|
|
23108
|
-
|
|
23034
|
+
// src/reporter/github.ts
|
|
23035
|
+
/** Escape a workflow-command PROPERTY value. */
|
|
23036
|
+
function escapeAnnotationProperty(value) {
|
|
23037
|
+
return value.replaceAll("%", "%25").replaceAll("\r", "%0D").replaceAll("\n", "%0A").replaceAll(":", "%3A").replaceAll(",", "%2C");
|
|
23038
|
+
}
|
|
23039
|
+
/** Escape a workflow-command MESSAGE. */
|
|
23040
|
+
function escapeAnnotationMessage(value) {
|
|
23041
|
+
return value.replaceAll("%", "%25").replaceAll("\r", "%0D").replaceAll("\n", "%0A");
|
|
23042
|
+
}
|
|
23109
23043
|
/** ANSI-injection guard for the summary path (belt + braces). */
|
|
23110
23044
|
function stripAnsiForSummary(value) {
|
|
23111
23045
|
return value.replace(/\x1b\[[0-9;:?]*[ -/]*[@-~]/g, "");
|
|
23112
23046
|
}
|
|
23113
|
-
|
|
23114
|
-
|
|
23047
|
+
const SEVERITY_TO_COMMAND = {
|
|
23048
|
+
error: "error",
|
|
23049
|
+
warning: "warning",
|
|
23050
|
+
info: "notice"
|
|
23051
|
+
};
|
|
23052
|
+
/** Render ONE workflow-command annotation line (no trailing newline). */
|
|
23053
|
+
function renderAnnotation(a) {
|
|
23054
|
+
const props = [`file=${escapeAnnotationProperty(a.file)}`, `line=${Math.max(1, a.line ?? 1)}`];
|
|
23055
|
+
if (a.column !== void 0) props.push(`column=${Math.max(1, a.column)}`);
|
|
23056
|
+
props.push(`title=${escapeAnnotationProperty(a.title)}`);
|
|
23057
|
+
return `::${SEVERITY_TO_COMMAND[a.severity]} ${props.join(",")}::${escapeAnnotationMessage(a.message)}`;
|
|
23058
|
+
}
|
|
23059
|
+
/** Render annotations for every finding in a report's findings array. */
|
|
23060
|
+
function renderAnnotations(findings) {
|
|
23061
|
+
return findings.map((f) => {
|
|
23062
|
+
return renderAnnotation({
|
|
23063
|
+
severity: f.severity === "error" || f.severity === "warning" ? f.severity : "info",
|
|
23064
|
+
file: f.file,
|
|
23065
|
+
line: f.line,
|
|
23066
|
+
column: f.column,
|
|
23067
|
+
title: f.ruleId,
|
|
23068
|
+
message: f.message
|
|
23069
|
+
});
|
|
23070
|
+
});
|
|
23071
|
+
}
|
|
23072
|
+
/** GitHub caps annotation messages — keep the head, point at the summary. */
|
|
23073
|
+
function truncateMessage(message, max = 250) {
|
|
23074
|
+
if (message.length <= max) return message;
|
|
23075
|
+
return `${message.slice(0, max)}… (full text in the step summary)`;
|
|
23076
|
+
}
|
|
23077
|
+
// src/commands/summary.ts
|
|
23078
|
+
/**
|
|
23079
|
+
* the `--format github-summary` render — CI annotations + step summary
|
|
23080
|
+
* (Terminal + CI UX Overhaul plan, M4).
|
|
23081
|
+
*
|
|
23082
|
+
* Additive command; default report path `mjolnir.json`. Reads a saved
|
|
23083
|
+
* ScanResult JSON (the `--json` scan output) and emits:
|
|
23084
|
+
* 1. GitHub annotations to stdout, only when GITHUB_ACTIONS=true —
|
|
23085
|
+
* one per finding, via the single github.ts emitter.
|
|
23086
|
+
* 2. A step-summary markdown document to $GITHUB_STEP_SUMMARY when
|
|
23087
|
+
* set (else stdout; --stdout forces stdout): score + band, text
|
|
23088
|
+
* score bar, dimensions table, top deductions, collapsible
|
|
23089
|
+
* per-severity details with fix lines, and an honesty notice for
|
|
23090
|
+
* partial/no-score reports.
|
|
23091
|
+
*
|
|
23092
|
+
* Exit codes (frozen contract): 0 on success — this command NEVER
|
|
23093
|
+
* blocks; the gate step decides. 10 missing file argument. 2
|
|
23094
|
+
* unreadable/invalid JSON (a data problem; the message explains).
|
|
23095
|
+
*
|
|
23096
|
+
* Paths: findings are scan-target-relative. The generated workflow
|
|
23097
|
+
* scans the repo root, so the default output is directly usable;
|
|
23098
|
+
* `--path-prefix <dir>` re-scopes for subdirectory scans.
|
|
23099
|
+
*/
|
|
23115
23100
|
const DETAILS_PER_SEVERITY_CAP = 25;
|
|
23116
23101
|
/**
|
|
23117
23102
|
* The score bar. BW-105: the canonical gauge with the inert palette — the
|
|
@@ -23205,8 +23190,92 @@ function renderStepSummary(result, options = {}) {
|
|
|
23205
23190
|
lines.push("<!-- mjolnir-honesty: scores derive from rules with published evidence levels and measured false-positive rates where available. -->");
|
|
23206
23191
|
return lines.join("\n");
|
|
23207
23192
|
}
|
|
23208
|
-
|
|
23209
|
-
|
|
23193
|
+
/** Render one finding as an escaped annotation line. Fields are
|
|
23194
|
+
* sanitized with the same sanitizeData layer the terminal uses
|
|
23195
|
+
* (github.ts's escapers cover %/CR/LF, not OSC/C0), then %/CR/LF-escaped. */
|
|
23196
|
+
function annotationForFinding(f) {
|
|
23197
|
+
return renderAnnotations([{
|
|
23198
|
+
...f,
|
|
23199
|
+
file: sanitizeData(f.file),
|
|
23200
|
+
ruleId: sanitizeData(f.ruleId),
|
|
23201
|
+
message: truncateMessage(stripAnsiForSummary(sanitizeData(f.message)))
|
|
23202
|
+
}])[0];
|
|
23203
|
+
}
|
|
23204
|
+
const KNOWN_SUMMARY_FLAGS = /* @__PURE__ */ new Set([
|
|
23205
|
+
"--stdout",
|
|
23206
|
+
"--path-prefix",
|
|
23207
|
+
"--help",
|
|
23208
|
+
"-h"
|
|
23209
|
+
]);
|
|
23210
|
+
/**
|
|
23211
|
+
* Testable summary command core. Returns the process exit code.
|
|
23212
|
+
* Streams: annotations → stdout (always; GitHub greps them), summary →
|
|
23213
|
+
* step-summary file when set unless --stdout. Unknown flags are a
|
|
23214
|
+
* usage error (exit 10) — a typo'd --stdout must not silently route
|
|
23215
|
+
* the summary to $GITHUB_STEP_SUMMARY.
|
|
23216
|
+
*/
|
|
23217
|
+
function runSummaryCommand(argv, io = {
|
|
23218
|
+
out: (line) => console.log(line),
|
|
23219
|
+
err: (line) => console.error(line)
|
|
23220
|
+
}) {
|
|
23221
|
+
for (let i = 0; i < argv.length; i++) {
|
|
23222
|
+
const a = argv[i] ?? "";
|
|
23223
|
+
if (!a.startsWith("-")) continue;
|
|
23224
|
+
if (a === "--path-prefix") {
|
|
23225
|
+
const val = argv[i + 1];
|
|
23226
|
+
if (val === void 0 || val.startsWith("-")) {
|
|
23227
|
+
io.err(usageErrorMessage({
|
|
23228
|
+
flag: "--path-prefix",
|
|
23229
|
+
token: val
|
|
23230
|
+
}));
|
|
23231
|
+
return 10;
|
|
23232
|
+
}
|
|
23233
|
+
i++;
|
|
23234
|
+
continue;
|
|
23235
|
+
}
|
|
23236
|
+
if (KNOWN_SUMMARY_FLAGS.has(a)) continue;
|
|
23237
|
+
io.err(usageErrorMessage({ token: a }));
|
|
23238
|
+
return 10;
|
|
23239
|
+
}
|
|
23240
|
+
const stdout = argv.includes("--stdout");
|
|
23241
|
+
const prefixIdx = argv.indexOf("--path-prefix");
|
|
23242
|
+
const pathPrefix = prefixIdx !== -1 ? argv[prefixIdx + 1] : void 0;
|
|
23243
|
+
const reportPath = argv.filter((a, i) => a !== void 0 && !a.startsWith("-") && (prefixIdx === -1 || i !== prefixIdx + 1))[0] ?? "mjolnir.json";
|
|
23244
|
+
if (!existsSync(reportPath)) {
|
|
23245
|
+
io.err(`mjolnir: report file not found: ${reportPath}`);
|
|
23246
|
+
io.err(" Run the scan with --json first: mjolnir --json > mjolnir.json");
|
|
23247
|
+
return 10;
|
|
23248
|
+
}
|
|
23249
|
+
let result;
|
|
23250
|
+
try {
|
|
23251
|
+
result = loadSavedReport(reportPath);
|
|
23252
|
+
} catch (err) {
|
|
23253
|
+
io.err(`mjolnir: cannot read ${reportPath}: ${errorMessage(err)}`);
|
|
23254
|
+
return 2;
|
|
23255
|
+
}
|
|
23256
|
+
const env = process.env;
|
|
23257
|
+
if (env["GITHUB_ACTIONS"] === "true") for (const f of result.findings) {
|
|
23258
|
+
const file = pathPrefix ? `${pathPrefix.replace(/\/$/, "")}/${f.file}` : f.file;
|
|
23259
|
+
io.out(annotationForFinding({
|
|
23260
|
+
...f,
|
|
23261
|
+
file
|
|
23262
|
+
}));
|
|
23263
|
+
}
|
|
23264
|
+
const summary = renderStepSummary(result, {
|
|
23265
|
+
stdout,
|
|
23266
|
+
...pathPrefix !== void 0 ? { pathPrefix } : {}
|
|
23267
|
+
});
|
|
23268
|
+
const stepSummaryPath = stdout ? void 0 : env["GITHUB_STEP_SUMMARY"];
|
|
23269
|
+
if (stepSummaryPath) try {
|
|
23270
|
+
appendFileSync(stepSummaryPath, `${summary}\n`);
|
|
23271
|
+
} catch (err) {
|
|
23272
|
+
io.err(`mjolnir: could not write $GITHUB_STEP_SUMMARY (${errorMessage(err)}); printing to stdout instead.`);
|
|
23273
|
+
io.out(summary);
|
|
23274
|
+
}
|
|
23275
|
+
else io.out(summary);
|
|
23276
|
+
return 0;
|
|
23277
|
+
}
|
|
23278
|
+
// src/commands/milestone-args.ts
|
|
23210
23279
|
/**
|
|
23211
23280
|
* The argument layer every milestone command shares.
|
|
23212
23281
|
*
|
|
@@ -23358,8 +23427,7 @@ function isWorkflowFile(path) {
|
|
|
23358
23427
|
const normalized = path.replaceAll("\\", "/");
|
|
23359
23428
|
return normalized.startsWith(".github/workflows/") || normalized === ".gitlab-ci.yml" || normalized === ".gitlab-ci.yaml" || basename(normalized) === "Jenkinsfile";
|
|
23360
23429
|
}
|
|
23361
|
-
|
|
23362
|
-
//#region src/engine/cross-file.ts
|
|
23430
|
+
// src/engine/cross-file.ts
|
|
23363
23431
|
/**
|
|
23364
23432
|
* Cross-file analysis (Upgrade-Plan-v3 Phase 6, tier-3 item #2).
|
|
23365
23433
|
*
|
|
@@ -23405,8 +23473,7 @@ function findDuplicateTestNames(files) {
|
|
|
23405
23473
|
});
|
|
23406
23474
|
return out.sort((a, b) => compareCodePoints(a.name, b.name));
|
|
23407
23475
|
}
|
|
23408
|
-
|
|
23409
|
-
//#region src/engine/cross-file-analysis.ts
|
|
23476
|
+
// src/engine/cross-file-analysis.ts
|
|
23410
23477
|
/**
|
|
23411
23478
|
* Analyze cross-file signals across the scanned test files.
|
|
23412
23479
|
* Pure function — same input always produces same output.
|
|
@@ -23544,8 +23611,7 @@ function renderCrossFileAnalysis(result) {
|
|
|
23544
23611
|
}
|
|
23545
23612
|
return lines.join("\n").trimEnd();
|
|
23546
23613
|
}
|
|
23547
|
-
|
|
23548
|
-
//#region src/engine/evidence-graph.ts
|
|
23614
|
+
// src/engine/evidence-graph.ts
|
|
23549
23615
|
/**
|
|
23550
23616
|
* Build a complete evidence graph from a scan result.
|
|
23551
23617
|
* Pure function — deterministic output for the same input.
|
|
@@ -23691,8 +23757,7 @@ function renderEvidenceGraphResult(result) {
|
|
|
23691
23757
|
lines.push(` ${result.agenticProfile.note}`);
|
|
23692
23758
|
return lines.join("\n").trimEnd();
|
|
23693
23759
|
}
|
|
23694
|
-
|
|
23695
|
-
//#region src/frameworks/scorecard.ts
|
|
23760
|
+
// src/frameworks/scorecard.ts
|
|
23696
23761
|
const SCORECARD_DIMENSIONS = [
|
|
23697
23762
|
"discovery",
|
|
23698
23763
|
"astUsage",
|
|
@@ -24802,8 +24867,7 @@ const FRAMEWORK_SCORECARDS = [
|
|
|
24802
24867
|
makeScorecard("jenkins", FRAMEWORK_OVERRIDES["jenkins"]),
|
|
24803
24868
|
makeScorecard("gitlab-ci", FRAMEWORK_OVERRIDES["gitlab-ci"])
|
|
24804
24869
|
];
|
|
24805
|
-
|
|
24806
|
-
//#region src/engine/framework-maturity.ts
|
|
24870
|
+
// src/engine/framework-maturity.ts
|
|
24807
24871
|
/**
|
|
24808
24872
|
* M5 — Framework Maturity Tracking for Playwright.
|
|
24809
24873
|
*
|
|
@@ -24892,8 +24956,7 @@ function renderPlaywrightMaturityReport(report) {
|
|
|
24892
24956
|
}
|
|
24893
24957
|
return lines.join("\n").trimEnd();
|
|
24894
24958
|
}
|
|
24895
|
-
|
|
24896
|
-
//#region src/engine/suppression-integrity.ts
|
|
24959
|
+
// src/engine/suppression-integrity.ts
|
|
24897
24960
|
/**
|
|
24898
24961
|
* ENGINE-006 — Suppression Integrity: detect suppression abuse,
|
|
24899
24962
|
* unknown rules, expired entries, and compute a full integrity report.
|
|
@@ -24987,8 +25050,7 @@ function computeSuppressionIntegrity(suppressions, findings, knownRuleIds, now =
|
|
|
24987
25050
|
expiredSuppressions: detectExpiredSuppressions(suppressions, now)
|
|
24988
25051
|
};
|
|
24989
25052
|
}
|
|
24990
|
-
|
|
24991
|
-
//#region src/engine/suppression-governance.ts
|
|
25053
|
+
// src/engine/suppression-governance.ts
|
|
24992
25054
|
/**
|
|
24993
25055
|
* M6 — Suppression Policy Governance Gate.
|
|
24994
25056
|
*
|
|
@@ -25051,15 +25113,13 @@ function renderSuppressionGovernanceResult(result) {
|
|
|
25051
25113
|
} else lines.push("All suppression policy checks passed.");
|
|
25052
25114
|
return lines.join("\n").trimEnd();
|
|
25053
25115
|
}
|
|
25054
|
-
|
|
25055
|
-
//#region src/adapters/gitlab-ci.ts
|
|
25116
|
+
// src/adapters/gitlab-ci.ts
|
|
25056
25117
|
function parseGitLabCi(content) {
|
|
25057
25118
|
const parsed = parse(content);
|
|
25058
25119
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("GitLab CI YAML is not a valid object.");
|
|
25059
25120
|
return parsed;
|
|
25060
25121
|
}
|
|
25061
|
-
|
|
25062
|
-
//#region src/engine/cli-command-names.ts
|
|
25122
|
+
// src/engine/cli-command-names.ts
|
|
25063
25123
|
const CLI_COMMAND_NAMES = [
|
|
25064
25124
|
"scan",
|
|
25065
25125
|
"policy",
|
|
@@ -25079,8 +25139,7 @@ const CLI_COMMAND_NAMES = [
|
|
|
25079
25139
|
"help"
|
|
25080
25140
|
];
|
|
25081
25141
|
const CLI_NON_SCAN_COMMANDS = new Set(CLI_COMMAND_NAMES.filter((command) => command !== "scan"));
|
|
25082
|
-
|
|
25083
|
-
//#region src/engine/verification-intelligence.ts
|
|
25142
|
+
// src/engine/verification-intelligence.ts
|
|
25084
25143
|
const CI_WORKFLOW_FILES = [
|
|
25085
25144
|
".github/workflows/ci.yml",
|
|
25086
25145
|
".github/workflows/test.yml",
|
|
@@ -25571,8 +25630,7 @@ function renderCIIntegrityReport(report) {
|
|
|
25571
25630
|
}
|
|
25572
25631
|
return lines.join("\n").trimEnd();
|
|
25573
25632
|
}
|
|
25574
|
-
|
|
25575
|
-
//#region src/commands/milestone.ts
|
|
25633
|
+
// src/commands/milestone.ts
|
|
25576
25634
|
async function runMilestoneScan(args, io, readSources, strict = false) {
|
|
25577
25635
|
const target = resolve(args.target);
|
|
25578
25636
|
const valid = validateTarget(target, io);
|
|
@@ -25804,8 +25862,7 @@ async function runCIIntegrityCommand(argv, io = {
|
|
|
25804
25862
|
return milestoneError(error, io, argv.includes("--debug"));
|
|
25805
25863
|
}
|
|
25806
25864
|
}
|
|
25807
|
-
|
|
25808
|
-
//#region src/scorer/scoring-policy.ts
|
|
25865
|
+
// src/scorer/scoring-policy.ts
|
|
25809
25866
|
/**
|
|
25810
25867
|
* `mjolnir scan --policy` — the scoring policy, as the code states it.
|
|
25811
25868
|
*
|
|
@@ -25867,8 +25924,7 @@ function renderScoringPolicy() {
|
|
|
25867
25924
|
lines.push("Every number above is imported from src/scorer/scorer.ts at run time. A", "policy that cannot be read cannot be checked, and an uncheckable policy", "is the thing this product exists to catch.");
|
|
25868
25925
|
return lines.join("\n");
|
|
25869
25926
|
}
|
|
25870
|
-
|
|
25871
|
-
//#region src/commands/fix.ts
|
|
25927
|
+
// src/commands/fix.ts
|
|
25872
25928
|
/**
|
|
25873
25929
|
* `mjolnir fix` — Safe Auto-Fix with Proof (Tier 1 #3).
|
|
25874
25930
|
*
|
|
@@ -26292,8 +26348,7 @@ function renderFixReport(results, dryRun) {
|
|
|
26292
26348
|
lines.push(`${parts.join(" · ")}${dryRun ? " (dry-run)" : ""}. Every applied fix was verified against the rule that flagged it.`);
|
|
26293
26349
|
return lines.join("\n");
|
|
26294
26350
|
}
|
|
26295
|
-
|
|
26296
|
-
//#region src/commands/rules-catalog.ts
|
|
26351
|
+
// src/commands/rules-catalog.ts
|
|
26297
26352
|
/**
|
|
26298
26353
|
* `mjolnir explain --list` — machine-readable rule catalog with Trust Metadata.
|
|
26299
26354
|
*
|
|
@@ -26510,8 +26565,7 @@ function countCompleteQuads(ruleIds, root = process.cwd()) {
|
|
|
26510
26565
|
total: ruleIds.length
|
|
26511
26566
|
};
|
|
26512
26567
|
}
|
|
26513
|
-
|
|
26514
|
-
//#region src/frameworks/provider-capability-contract.ts
|
|
26568
|
+
// src/frameworks/provider-capability-contract.ts
|
|
26515
26569
|
const CI_PROVIDER_CAPABILITY_SCHEMA = "m30.ci-provider-capabilities@1";
|
|
26516
26570
|
const CI_PROVIDER_IDS = [
|
|
26517
26571
|
"github-actions",
|
|
@@ -26757,8 +26811,7 @@ const CI_PROVIDER_CAPABILITY_RECORDS = [
|
|
|
26757
26811
|
}
|
|
26758
26812
|
];
|
|
26759
26813
|
new Map(CI_PROVIDER_CAPABILITY_RECORDS.map((record) => [record.provider, record]));
|
|
26760
|
-
|
|
26761
|
-
//#region src/v6/maturity.ts
|
|
26814
|
+
// src/v6/maturity.ts
|
|
26762
26815
|
/**
|
|
26763
26816
|
* v6 capability maturity ladder — `M0`–`M5` (ADR 0001).
|
|
26764
26817
|
*
|
|
@@ -26957,8 +27010,7 @@ function nextLevelGapFromEvidence(evidence, owner, revisitTrigger) {
|
|
|
26957
27010
|
question: "How much does it matter? (technical severity stays separate)"
|
|
26958
27011
|
}
|
|
26959
27012
|
].map((axis) => axis.id);
|
|
26960
|
-
|
|
26961
|
-
//#region src/v6/ecosystem-census.ts
|
|
27013
|
+
// src/v6/ecosystem-census.ts
|
|
26962
27014
|
/**
|
|
26963
27015
|
* The leading major of a version or range. `FRAMEWORK_INVENTORY`
|
|
26964
27016
|
* records *validated versions* (`1.44`, `29`) while a manifest records a
|
|
@@ -26971,8 +27023,7 @@ function versionMajor(raw) {
|
|
|
26971
27023
|
if (!match) return null;
|
|
26972
27024
|
return (match[0] ?? "").split(".")[0] ?? null;
|
|
26973
27025
|
}
|
|
26974
|
-
|
|
26975
|
-
//#region src/v6/ecosystem-probe.ts
|
|
27026
|
+
// src/v6/ecosystem-probe.ts
|
|
26976
27027
|
/**
|
|
26977
27028
|
* Filesystem-backed census evidence + the discovery probe (ADR 0010).
|
|
26978
27029
|
*
|
|
@@ -27224,8 +27275,7 @@ function corpusReposOf(root) {
|
|
|
27224
27275
|
}
|
|
27225
27276
|
});
|
|
27226
27277
|
}
|
|
27227
|
-
|
|
27228
|
-
//#region src/v6/capability-registry.ts
|
|
27278
|
+
// src/v6/capability-registry.ts
|
|
27229
27279
|
/**
|
|
27230
27280
|
|
|
27231
27281
|
}
|
|
@@ -27708,8 +27758,7 @@ function validateRegistry(registry) {
|
|
|
27708
27758
|
}
|
|
27709
27759
|
return diagnostics;
|
|
27710
27760
|
}
|
|
27711
|
-
|
|
27712
|
-
//#region src/commands/capability.ts
|
|
27761
|
+
// src/commands/capability.ts
|
|
27713
27762
|
/** Parse the flags. Anything flag-shaped that is not documented is a
|
|
27714
27763
|
* usage error rather than a silently ignored argument, because
|
|
27715
27764
|
* `capability --promote-m3` must not read as "show me everything". */
|
|
@@ -27829,8 +27878,7 @@ function runCapabilityCommand(argv, io = {
|
|
|
27829
27878
|
io.out(json ? capabilityJson(entries, diagnostics) : `${renderCapabilityText(entries, diagnostics)}\n`);
|
|
27830
27879
|
return diagnostics.length > 0 ? 1 : 0;
|
|
27831
27880
|
}
|
|
27832
|
-
|
|
27833
|
-
//#region src/rules/measurement-status.ts
|
|
27881
|
+
// src/rules/measurement-status.ts
|
|
27834
27882
|
/**
|
|
27835
27883
|
* Derive the measurement status for a single rule.
|
|
27836
27884
|
*
|
|
@@ -27877,8 +27925,7 @@ function getMeasurementStatus(rules = RULES) {
|
|
|
27877
27925
|
};
|
|
27878
27926
|
});
|
|
27879
27927
|
}
|
|
27880
|
-
|
|
27881
|
-
//#region src/commands/rule-health.ts
|
|
27928
|
+
// src/commands/rule-health.ts
|
|
27882
27929
|
/**
|
|
27883
27930
|
* Build the per-rule health table from the shipped registry. Deterministic
|
|
27884
27931
|
* order: the RULES registry order (stable across runs).
|
|
@@ -28009,8 +28056,7 @@ function renderRuleHealth(rows, limit) {
|
|
|
28009
28056
|
return lines.join("\n");
|
|
28010
28057
|
}
|
|
28011
28058
|
Object.keys(MEASURED_FP).length;
|
|
28012
|
-
|
|
28013
|
-
//#region src/playwright/selector-health-types.ts
|
|
28059
|
+
// src/playwright/selector-health-types.ts
|
|
28014
28060
|
/** Risk weights per locator shape (0 = resilient, higher = brittle). */
|
|
28015
28061
|
const LOCATOR_RISK = {
|
|
28016
28062
|
"role-based": 0,
|
|
@@ -28019,8 +28065,7 @@ const LOCATOR_RISK = {
|
|
|
28019
28065
|
"css-chain": 60,
|
|
28020
28066
|
xpath: 90
|
|
28021
28067
|
};
|
|
28022
|
-
|
|
28023
|
-
//#region src/playwright/selector-health.ts
|
|
28068
|
+
// src/playwright/selector-health.ts
|
|
28024
28069
|
/**
|
|
28025
28070
|
* Selector Health Score engine (Upgrade-Plan-v2 R3, the headline feature).
|
|
28026
28071
|
*
|
|
@@ -28148,8 +28193,7 @@ function computeSelectorHealth(root, ignoreMatcher = DEFAULT_IGNORE_MATCHER) {
|
|
|
28148
28193
|
walk(root);
|
|
28149
28194
|
return specs.sort((a, b) => a.score - b.score || compareCodePoints(a.file, b.file));
|
|
28150
28195
|
}
|
|
28151
|
-
|
|
28152
|
-
//#region src/cli-handlers.ts
|
|
28196
|
+
// src/cli-handlers.ts
|
|
28153
28197
|
/**
|
|
28154
28198
|
* Handler functions for every Mjolnir CLI verb.
|
|
28155
28199
|
*
|
|
@@ -28158,6 +28202,22 @@ function computeSelectorHealth(root, ignoreMatcher = DEFAULT_IGNORE_MATCHER) {
|
|
|
28158
28202
|
* (Task 6: renderScanOutput helper).
|
|
28159
28203
|
*/
|
|
28160
28204
|
/**
|
|
28205
|
+
* The one catch for a scan-backed verb.
|
|
28206
|
+
*
|
|
28207
|
+
* An invalid `mjolnir.config.json` is the USER's error and exits 10 (usage),
|
|
28208
|
+
* as `scan` always did. `fix`, `ci verify` and `explain --plan` each had
|
|
28209
|
+
* their own catch that only knew the internal-error arm, so the same config
|
|
28210
|
+
* typo exited 20 and printed "this is a bug in Mjölnir" on three verbs.
|
|
28211
|
+
*/
|
|
28212
|
+
function verbFailure(error, io, debug) {
|
|
28213
|
+
if (error instanceof ConfigValidationError) {
|
|
28214
|
+
io.err(error.message);
|
|
28215
|
+
return 10;
|
|
28216
|
+
}
|
|
28217
|
+
internalErrorMessage(error, io.err, debug);
|
|
28218
|
+
return 20;
|
|
28219
|
+
}
|
|
28220
|
+
/**
|
|
28161
28221
|
* The one place a run's gate level is resolved from `--blocking`, then
|
|
28162
28222
|
* `mjolnir.config.json`, then the shipped default.
|
|
28163
28223
|
*
|
|
@@ -28207,9 +28267,11 @@ function renderScanOutput(result, args, target, io, gate) {
|
|
|
28207
28267
|
else if (args.format === "pr-comment") {
|
|
28208
28268
|
const baseline = loadBaseline(join(target, DEFAULT_BASELINE_PATH));
|
|
28209
28269
|
const diff = baseline ? diffAgainstBaseline(result, baseline) : void 0;
|
|
28270
|
+
const commit = commitBoundTo(result, target);
|
|
28210
28271
|
io.out(renderPrComment(result, {
|
|
28211
28272
|
...diff ? { diff } : {},
|
|
28212
|
-
version: ENGINE_VERSION
|
|
28273
|
+
version: ENGINE_VERSION,
|
|
28274
|
+
...commit !== void 0 ? { commit } : {}
|
|
28213
28275
|
}));
|
|
28214
28276
|
} else if (args.format === "github-summary") io.out(renderStepSummary(result));
|
|
28215
28277
|
else if (args.json) io.out(JSON.stringify({
|
|
@@ -28298,6 +28360,58 @@ async function runSuppressionGate(args, target, io) {
|
|
|
28298
28360
|
if (args.base !== void 0) argv.push("--base", args.base);
|
|
28299
28361
|
return runSuppressionGateCommand(argv, io);
|
|
28300
28362
|
}
|
|
28363
|
+
/**
|
|
28364
|
+
* The commit a rendered report may claim, or undefined when it cannot claim one.
|
|
28365
|
+
*
|
|
28366
|
+
* The scan records the HEAD it analysed (`runIdentity.commit`). A report states
|
|
28367
|
+
* that commit only when the repository at `dir` is still at it, so a report
|
|
28368
|
+
* rendered from another commit's JSON reads `unknown` instead of borrowing an
|
|
28369
|
+
* identity nothing here can confirm. The retired `pr-comment --from` verb took
|
|
28370
|
+
* the commit from a `--commit` argument; the formats that replaced it check the
|
|
28371
|
+
* checkout instead, which needs no flag and cannot be handed the wrong value.
|
|
28372
|
+
*/
|
|
28373
|
+
function commitBoundTo(result, dir) {
|
|
28374
|
+
const recorded = result.runIdentity?.commit;
|
|
28375
|
+
if (recorded === void 0) return void 0;
|
|
28376
|
+
return currentCommit(dir, { nullable: true }) === recorded ? recorded : void 0;
|
|
28377
|
+
}
|
|
28378
|
+
/**
|
|
28379
|
+
* `--format github-summary|pr-comment <report.json>`: render a SAVED report.
|
|
28380
|
+
*
|
|
28381
|
+
* The v6 collapse retired `summary <report>` and `pr-comment --from <report>`
|
|
28382
|
+
* onto these two formats and shipped only the half that scans. Every CI
|
|
28383
|
+
* consumer renders the `--json` report its scan step already wrote: the
|
|
28384
|
+
* Action, both `ci install` templates and this repository's own PR workflow.
|
|
28385
|
+
* All of them kept calling the retired verbs, and all of them exited 10. A
|
|
28386
|
+
* second scan is not a substitute, because the comment has to describe the
|
|
28387
|
+
* result the gate read rather than one that happens to agree with it.
|
|
28388
|
+
*
|
|
28389
|
+
* Delegation, not reimplementation, which is the rule `--suppression-gate`
|
|
28390
|
+
* follows. Each retired verb's command still owns its contract, exit codes
|
|
28391
|
+
* included. The render never gates and exits 0. A report that cannot be read
|
|
28392
|
+
* exits 2 for the summary and 10 for the comment, as those verbs did.
|
|
28393
|
+
*
|
|
28394
|
+
* Returns null when the request is a scan: another format, or a target that
|
|
28395
|
+
* is not a file.
|
|
28396
|
+
*/
|
|
28397
|
+
function renderSavedReport(args, target, io) {
|
|
28398
|
+
if (args.format !== "github-summary" && args.format !== "pr-comment") return null;
|
|
28399
|
+
if (!isRegularFile(target)) return null;
|
|
28400
|
+
if (args.format === "github-summary") return runSummaryCommand([target], io);
|
|
28401
|
+
return runPrCommentCommand([
|
|
28402
|
+
"--from",
|
|
28403
|
+
target,
|
|
28404
|
+
"--version",
|
|
28405
|
+
ENGINE_VERSION
|
|
28406
|
+
], io);
|
|
28407
|
+
}
|
|
28408
|
+
function isRegularFile(path) {
|
|
28409
|
+
try {
|
|
28410
|
+
return statSync(path).isFile();
|
|
28411
|
+
} catch {
|
|
28412
|
+
return false;
|
|
28413
|
+
}
|
|
28414
|
+
}
|
|
28301
28415
|
async function runScanCommand(argv, io = {
|
|
28302
28416
|
out,
|
|
28303
28417
|
err
|
|
@@ -28305,6 +28419,8 @@ async function runScanCommand(argv, io = {
|
|
|
28305
28419
|
const args = parseArgsOrUsage(argv, io);
|
|
28306
28420
|
if (!args) return 10;
|
|
28307
28421
|
const target = resolve(args.target);
|
|
28422
|
+
const savedRender = renderSavedReport(args, target, io);
|
|
28423
|
+
if (savedRender !== null) return savedRender;
|
|
28308
28424
|
const invalid = validateScanTarget(target, io.err);
|
|
28309
28425
|
if (invalid !== null) return invalid;
|
|
28310
28426
|
if (args.suppressions) return printSuppressionLedger(target, io);
|
|
@@ -28334,15 +28450,18 @@ async function runScanCommand(argv, io = {
|
|
|
28334
28450
|
} } : {}
|
|
28335
28451
|
});
|
|
28336
28452
|
progress.done();
|
|
28453
|
+
let baselineFailed = false;
|
|
28337
28454
|
if (args.saveBaseline) {
|
|
28338
28455
|
const outPath = join(target, DEFAULT_BASELINE_PATH);
|
|
28339
|
-
|
|
28456
|
+
const notice = args.format === "terminal" ? io.out : io.err;
|
|
28457
|
+
if (result.partial) io.err("baseline NOT saved — this scan is partial, and a truncated before-state would report every finding it missed as RESOLVED. Re-run without the time budget.");
|
|
28458
|
+
else try {
|
|
28340
28459
|
const saved = saveBaseline(result, currentCommit(target) ?? "unknown", outPath);
|
|
28341
|
-
|
|
28460
|
+
notice(renderBaselineSaved(DEFAULT_BASELINE_PATH, result.findings.length, { ...saved.backupPath !== void 0 ? { backupPath: saved.backupPath } : {} }));
|
|
28342
28461
|
} catch (saveErr) {
|
|
28343
28462
|
io.err(`baseline save FAILED — ${saveErr instanceof Error ? saveErr.message : String(saveErr)}`);
|
|
28344
28463
|
io.err("The scan completed; the snapshot was not written. Fix the path permissions and re-run with `--save-baseline`.");
|
|
28345
|
-
|
|
28464
|
+
baselineFailed = true;
|
|
28346
28465
|
}
|
|
28347
28466
|
}
|
|
28348
28467
|
if (args.debug && crashLog.length > 0) {
|
|
@@ -28378,6 +28497,7 @@ async function runScanCommand(argv, io = {
|
|
|
28378
28497
|
}
|
|
28379
28498
|
if (result.partial) return 2;
|
|
28380
28499
|
for (const w of warnings) io.err(w);
|
|
28500
|
+
if (baselineFailed) return 2;
|
|
28381
28501
|
return scanExitCode({
|
|
28382
28502
|
partial: false,
|
|
28383
28503
|
findings: result.findings,
|
|
@@ -28387,12 +28507,7 @@ async function runScanCommand(argv, io = {
|
|
|
28387
28507
|
...args.requireFullCoverage !== void 0 ? { requireFullCoverage: args.requireFullCoverage } : {}
|
|
28388
28508
|
});
|
|
28389
28509
|
} catch (err) {
|
|
28390
|
-
|
|
28391
|
-
io.err(err.message);
|
|
28392
|
-
return 10;
|
|
28393
|
-
}
|
|
28394
|
-
internalErrorMessage(err, io.err, args?.debug === true);
|
|
28395
|
-
return 20;
|
|
28510
|
+
return verbFailure(err, io, args?.debug === true);
|
|
28396
28511
|
}
|
|
28397
28512
|
}
|
|
28398
28513
|
async function runFixCommand(argv, io = {
|
|
@@ -28406,16 +28521,17 @@ async function runFixCommand(argv, io = {
|
|
|
28406
28521
|
const target = resolve(args.target);
|
|
28407
28522
|
const invalid = validateScanTarget(target, io.err);
|
|
28408
28523
|
if (invalid !== null) return invalid;
|
|
28409
|
-
const
|
|
28524
|
+
const result = await runScan$1({
|
|
28410
28525
|
...args,
|
|
28411
28526
|
target,
|
|
28412
28527
|
strict: true
|
|
28413
|
-
})
|
|
28528
|
+
});
|
|
28529
|
+
const fixes = planAndApplyFixes(result, target, { dryRun });
|
|
28414
28530
|
io.out(renderFixReport(fixes, dryRun));
|
|
28531
|
+
if (result.partial) return 2;
|
|
28415
28532
|
return fixes.some((f) => f.status === "failed") ? 1 : 0;
|
|
28416
28533
|
} catch (err) {
|
|
28417
|
-
|
|
28418
|
-
return 20;
|
|
28534
|
+
return verbFailure(err, io, args?.debug === true);
|
|
28419
28535
|
}
|
|
28420
28536
|
}
|
|
28421
28537
|
async function runVerifyCommand(argv, io = {
|
|
@@ -28432,14 +28548,20 @@ async function runVerifyCommand(argv, io = {
|
|
|
28432
28548
|
...args,
|
|
28433
28549
|
target
|
|
28434
28550
|
});
|
|
28435
|
-
const
|
|
28551
|
+
const baseline = loadBaseline(join(target, DEFAULT_BASELINE_PATH), (w) => io.err(w));
|
|
28552
|
+
const digest = buildVerifyDigest(result, baseline);
|
|
28436
28553
|
io.out(renderVerifyDigest(digest));
|
|
28437
28554
|
if (result.partial) return 2;
|
|
28438
28555
|
if (!digest.hasBaseline) return 2;
|
|
28439
|
-
|
|
28556
|
+
const { config } = loadConfig(target, { knownRuleIds: KNOWN_RULE_IDS$1 });
|
|
28557
|
+
return scanExitCode({
|
|
28558
|
+
partial: false,
|
|
28559
|
+
findings: diffAgainstBaseline(result, baseline).newFindings,
|
|
28560
|
+
gate: resolveGateLevel(args.blocking, config),
|
|
28561
|
+
isAdvisory: isAdvisoryFinding
|
|
28562
|
+
});
|
|
28440
28563
|
} catch (err) {
|
|
28441
|
-
|
|
28442
|
-
return 20;
|
|
28564
|
+
return verbFailure(err, io, args?.debug === true);
|
|
28443
28565
|
}
|
|
28444
28566
|
}
|
|
28445
28567
|
async function runPrCommentCommand(argv, io = {
|
|
@@ -28454,13 +28576,14 @@ async function runPrCommentCommand(argv, io = {
|
|
|
28454
28576
|
return 10;
|
|
28455
28577
|
}
|
|
28456
28578
|
try {
|
|
28457
|
-
const
|
|
28579
|
+
const reportPath = resolve(fromPath);
|
|
28580
|
+
const saved = loadSavedReport(reportPath);
|
|
28458
28581
|
const option = (flag) => {
|
|
28459
28582
|
const index = argv.indexOf(flag);
|
|
28460
28583
|
return index >= 0 ? argv[index + 1] : void 0;
|
|
28461
28584
|
};
|
|
28462
28585
|
const version = option("--version");
|
|
28463
|
-
const commit = option("--commit");
|
|
28586
|
+
const commit = option("--commit") ?? commitBoundTo(saved, dirname(reportPath));
|
|
28464
28587
|
const repoUrl = option("--repo-url");
|
|
28465
28588
|
io.out(renderPrComment(saved, {
|
|
28466
28589
|
...version ? { version } : {},
|
|
@@ -28525,15 +28648,19 @@ async function runHandoverCommand(argv, io = {
|
|
|
28525
28648
|
target
|
|
28526
28649
|
});
|
|
28527
28650
|
let forensics = null;
|
|
28651
|
+
let forensicsFailed = false;
|
|
28528
28652
|
const resultsDir = join(target, "test-results");
|
|
28529
28653
|
if (existsSync(resultsDir)) try {
|
|
28530
28654
|
forensics = runForensics(resultsDir, { writeFlakyMd: false }).report;
|
|
28531
|
-
} catch {
|
|
28655
|
+
} catch (forensicsErr) {
|
|
28656
|
+
io.err(`warning: test-results/ could not be read (${errorMessage(forensicsErr)}) — the map below is static-only.`);
|
|
28657
|
+
forensicsFailed = true;
|
|
28658
|
+
}
|
|
28532
28659
|
io.out(renderHandover(buildHandover(result, forensics)));
|
|
28660
|
+
if (result.partial || forensicsFailed) return 2;
|
|
28533
28661
|
return 0;
|
|
28534
28662
|
} catch (err) {
|
|
28535
|
-
|
|
28536
|
-
return 20;
|
|
28663
|
+
return verbFailure(err, io, args?.debug === true);
|
|
28537
28664
|
}
|
|
28538
28665
|
}
|
|
28539
28666
|
function runPwReportCommand(argv, io = {
|
|
@@ -28667,7 +28794,7 @@ function runEvidenceArm(argv, io) {
|
|
|
28667
28794
|
const { report } = runForensics(resolve(target), { writeFlakyMd });
|
|
28668
28795
|
if (asJson) io.out(JSON.stringify(report, null, 2));
|
|
28669
28796
|
else io.out(asMd ? renderTriageMd(report) : renderTriage(report));
|
|
28670
|
-
return report.analysisComplete === false ?
|
|
28797
|
+
return report.analysisComplete === false ? 2 : 0;
|
|
28671
28798
|
} catch (err) {
|
|
28672
28799
|
internalErrorMessage(err, io.err, argv.includes("--debug"));
|
|
28673
28800
|
return 20;
|
|
@@ -28820,8 +28947,7 @@ async function runDoctorPlaywright(argv, io = { out }) {
|
|
|
28820
28947
|
return 20;
|
|
28821
28948
|
}
|
|
28822
28949
|
}
|
|
28823
|
-
|
|
28824
|
-
//#region src/rules/rule-metadata-schema.ts
|
|
28950
|
+
// src/rules/rule-metadata-schema.ts
|
|
28825
28951
|
const VALID_SEVERITIES = SEVERITY_ORDER$1;
|
|
28826
28952
|
const VALID_CATEGORIES = RULE_CATEGORIES;
|
|
28827
28953
|
const VALID_QA_IMPACTS = Object.keys(QA_IMPACT_LABELS);
|
|
@@ -28869,8 +28995,7 @@ function validateAllRulesMetadata(rules) {
|
|
|
28869
28995
|
for (const rule of rules) all.push(...validateRuleMetadata(rule));
|
|
28870
28996
|
return all;
|
|
28871
28997
|
}
|
|
28872
|
-
|
|
28873
|
-
//#region src/engine/detector-hash.ts
|
|
28998
|
+
// src/engine/detector-hash.ts
|
|
28874
28999
|
/**
|
|
28875
29000
|
* Detector revision integrity engine (certification-audit Phase 3,
|
|
28876
29001
|
* G4/D8v2/D17).
|
|
@@ -29138,8 +29263,7 @@ const RULE_ID_RE = new RegExp(`^QA-(?:${[
|
|
|
29138
29263
|
appliesTo: "test-files"
|
|
29139
29264
|
}
|
|
29140
29265
|
].map((f) => f.token).join("|")})-\\d{3}$`);
|
|
29141
|
-
|
|
29142
|
-
//#region src/commands/doctor.ts
|
|
29266
|
+
// src/commands/doctor.ts
|
|
29143
29267
|
/**
|
|
29144
29268
|
* `mjolnir doctor` — self-audit of Mjolnir's own rule base.
|
|
29145
29269
|
*
|
|
@@ -30086,8 +30210,7 @@ function stripNonDeterministicFields(json, fields = NON_DETERMINISTIC_FIELDS) {
|
|
|
30086
30210
|
for (const field of fields) delete json[field];
|
|
30087
30211
|
return json;
|
|
30088
30212
|
}
|
|
30089
|
-
|
|
30090
|
-
//#region src/commands/doctor-run.ts
|
|
30213
|
+
// src/commands/doctor-run.ts
|
|
30091
30214
|
/**
|
|
30092
30215
|
* Doctor CLI entry (certification-audit Phase 5, G6): the command handler
|
|
30093
30216
|
* lives beside the check implementations instead of in cli.ts — the CLI
|
|
@@ -30185,8 +30308,7 @@ function installedPackageRoot() {
|
|
|
30185
30308
|
return process.cwd();
|
|
30186
30309
|
}
|
|
30187
30310
|
}
|
|
30188
|
-
|
|
30189
|
-
//#region src/commands/handoff.ts
|
|
30311
|
+
// src/commands/handoff.ts
|
|
30190
30312
|
/**
|
|
30191
30313
|
* `mjolnir handoff [mjolnir.json]` — the deterministic fix-handoff
|
|
30192
30314
|
* artifact (agent-handoff plan M3).
|
|
@@ -30496,8 +30618,7 @@ function runHandoffCommand(argv, io = {
|
|
|
30496
30618
|
}));
|
|
30497
30619
|
return 0;
|
|
30498
30620
|
}
|
|
30499
|
-
|
|
30500
|
-
//#region src/commands/install-agents.ts
|
|
30621
|
+
// src/commands/install-agents.ts
|
|
30501
30622
|
/**
|
|
30502
30623
|
* `mjolnir install` — instruction-surface installer (agent-handoff
|
|
30503
30624
|
* plan M4).
|
|
@@ -30823,8 +30944,7 @@ function executeHookInstall(entry) {
|
|
|
30823
30944
|
default: return false;
|
|
30824
30945
|
}
|
|
30825
30946
|
}
|
|
30826
|
-
|
|
30827
|
-
//#region src/commands/release-trust.ts
|
|
30947
|
+
// src/commands/release-trust.ts
|
|
30828
30948
|
/**
|
|
30829
30949
|
* `mjolnir ci release-trust` — the Release Trust Verdict (product-gap
|
|
30830
30950
|
* master plan §5, plan 1789009691197 R4a).
|
|
@@ -31535,8 +31655,7 @@ function runReleaseTrustCommand(argv, io = {
|
|
|
31535
31655
|
return 20;
|
|
31536
31656
|
}
|
|
31537
31657
|
}
|
|
31538
|
-
|
|
31539
|
-
//#region src/commands/policy.ts
|
|
31658
|
+
// src/commands/policy.ts
|
|
31540
31659
|
/**
|
|
31541
31660
|
* `mjolnir policy` — Policy-as-Code (TL-2).
|
|
31542
31661
|
*
|
|
@@ -31660,8 +31779,7 @@ async function runPolicyCommand(argv, io) {
|
|
|
31660
31779
|
io.err(`Unknown policy subcommand: ${subcommand}`);
|
|
31661
31780
|
return 10;
|
|
31662
31781
|
}
|
|
31663
|
-
|
|
31664
|
-
//#region src/commands/analyze.ts
|
|
31782
|
+
// src/commands/analyze.ts
|
|
31665
31783
|
/**
|
|
31666
31784
|
* `mjolnir analyze --cross-file` — Cross-File Analysis (SDET-7).
|
|
31667
31785
|
*
|
|
@@ -31797,8 +31915,7 @@ function runAnalyzeCommand(argv, io) {
|
|
|
31797
31915
|
return 20;
|
|
31798
31916
|
}
|
|
31799
31917
|
}
|
|
31800
|
-
|
|
31801
|
-
//#region src/commands/ci-adapter.ts
|
|
31918
|
+
// src/commands/ci-adapter.ts
|
|
31802
31919
|
/**
|
|
31803
31920
|
* `mjolnir ci adapters` — CI Adapters (SDET-4).
|
|
31804
31921
|
*
|
|
@@ -31919,8 +32036,7 @@ function runCiAdapterCommand(argv, io) {
|
|
|
31919
32036
|
io.out(output);
|
|
31920
32037
|
return 0;
|
|
31921
32038
|
}
|
|
31922
|
-
|
|
31923
|
-
//#region src/engine/machine-contract-verification.ts
|
|
32039
|
+
// src/engine/machine-contract-verification.ts
|
|
31924
32040
|
/**
|
|
31925
32041
|
* M11 — Machine Contract Verification.
|
|
31926
32042
|
*
|
|
@@ -32082,8 +32198,7 @@ function renderContractVerification(result) {
|
|
|
32082
32198
|
}
|
|
32083
32199
|
return lines.join("\n").trimEnd();
|
|
32084
32200
|
}
|
|
32085
|
-
|
|
32086
|
-
//#region src/commands/contract-verify.ts
|
|
32201
|
+
// src/commands/contract-verify.ts
|
|
32087
32202
|
/**
|
|
32088
32203
|
* `contract-verify` — the machine-contract and exit-code validators, and the
|
|
32089
32204
|
* command that runs them.
|
|
@@ -32240,8 +32355,7 @@ async function runContractVerifyCommand(argv, io = {
|
|
|
32240
32355
|
return milestoneError(error, io, argv.includes("--debug"));
|
|
32241
32356
|
}
|
|
32242
32357
|
}
|
|
32243
|
-
|
|
32244
|
-
//#region src/commands/share.ts
|
|
32358
|
+
// src/commands/share.ts
|
|
32245
32359
|
/**
|
|
32246
32360
|
* `mjolnir share` — React Doctor's proof loop, without its server.
|
|
32247
32361
|
*
|
|
@@ -32471,8 +32585,7 @@ async function runShareCommand(argv, io) {
|
|
|
32471
32585
|
return 20;
|
|
32472
32586
|
}
|
|
32473
32587
|
}
|
|
32474
|
-
|
|
32475
|
-
//#region src/engine/historical-trust.ts
|
|
32588
|
+
// src/engine/historical-trust.ts
|
|
32476
32589
|
/**
|
|
32477
32590
|
* Compute a trust snapshot from a scan result and run identity.
|
|
32478
32591
|
* Pure function — deterministic output for the same input.
|
|
@@ -32658,8 +32771,7 @@ function renderTrustTrend(trend) {
|
|
|
32658
32771
|
}
|
|
32659
32772
|
return lines.join("\n").trimEnd();
|
|
32660
32773
|
}
|
|
32661
|
-
|
|
32662
|
-
//#region src/commands/release-trend.ts
|
|
32774
|
+
// src/commands/release-trend.ts
|
|
32663
32775
|
/**
|
|
32664
32776
|
* `ci release-trend` — how the signed release's trust record has moved across
|
|
32665
32777
|
* releases.
|
|
@@ -32716,8 +32828,7 @@ async function runTrustTrendCommand(argv, io = {
|
|
|
32716
32828
|
return milestoneError(error, io, argv.includes("--debug"));
|
|
32717
32829
|
}
|
|
32718
32830
|
}
|
|
32719
|
-
|
|
32720
|
-
//#region src/cli.ts
|
|
32831
|
+
// src/cli.ts
|
|
32721
32832
|
/**
|
|
32722
32833
|
* Mjölnir CLI entry point (W1-02).
|
|
32723
32834
|
* Exit codes (§24.1, frozen): 0 clean · 1 findings ≥ gate · 2 partial ·
|
|
@@ -32730,8 +32841,8 @@ async function runTrustTrendCommand(argv, io = {
|
|
|
32730
32841
|
* bundled `dist/cli.mjs`, so resolving package.json at runtime depends on
|
|
32731
32842
|
* where the file happens to sit after install. This follows the same
|
|
32732
32843
|
* discipline as SARIF's `driver.version` — kept in sync by
|
|
32733
|
-
* `
|
|
32734
|
-
*
|
|
32844
|
+
* `npm run version:surface:sync` (it rewrites every identity surface) on
|
|
32845
|
+
* release and guarded by
|
|
32735
32846
|
* `tests/contract/version-consistency.spec.ts` locally. R4c moved the literal to
|
|
32736
32847
|
* src/engine/version.ts (a leaf module) so the scan pipeline's run
|
|
32737
32848
|
* identity can carry it without a cli.ts import cycle; this re-export
|
|
@@ -33085,7 +33196,7 @@ function runCiInstall(argv, io = {
|
|
|
33085
33196
|
return 10;
|
|
33086
33197
|
}
|
|
33087
33198
|
io.out(`${result.existed ? "Updated" : "Created"} ${result.written}`);
|
|
33088
|
-
io.out(noAction ? `Plain-npx template (--no-action). Gate: ${gate}.` : `Action-based template: uses
|
|
33199
|
+
io.out(noAction ? `Plain-npx template (--no-action). Gate: ${gate}.` : `Action-based template: uses ${ACTION_REF} (immutable pin). Gate: ${gate}.`);
|
|
33089
33200
|
io.out(gate === "advisory" ? "Advisory mode reports findings without blocking. Opt in with --gate error or --gate warning." : `Blocking mode fails on ${gate} findings.`);
|
|
33090
33201
|
io.out("Change with: mjolnir ci install --gate error|warning|advisory");
|
|
33091
33202
|
if (!noAction) io.out("Prefer the plain-npx workflow? Re-run with --no-action.");
|
|
@@ -33236,7 +33347,6 @@ if (isEntryPoint()) {
|
|
|
33236
33347
|
await flushSentry();
|
|
33237
33348
|
}
|
|
33238
33349
|
}
|
|
33239
|
-
//#endregion
|
|
33240
33350
|
export { ENGINE_VERSION as CLI_VERSION, DEFAULT_MAX_DURATION_MS, EVIDENCE_OVERRIDES, EXPLAIN_ARMS, KNOWN_RULE_IDS, MAX_DURATION_MS, OVERLAP_META_BY_RULE_ID, SUITE_INVALIDATING_RULE_IDS, buildUniversalRules, discoverAndParseRuntimeReport, err, exitForFindings, fallbackWorkspace, internalErrorMessage, isEntryPoint, isValidFindingRecord, levenshtein, main, nearestFlags, out, parseArgs, parseArgsOrUsage, pathMatchesGlob, printUsage, renderCiSubcommandHelp, renderScanOutput, runAnalyzeCommand, runCIIntegrityCommand, runCiAdapterCommand, runCiInstall, runContractVerifyCommand, runCrossFileCommand, runDoctorCommand, runDoctorPlaywright, runEvidenceArm, runEvidenceGraphCommand, runExplainCommand, runFixCommand, runFrameworkMaturityCommand, runHandoffCommand, runHandoverCommand, runHelpCommand, runInstallCommand, runPolicyCommand, runPrCommentCommand, runPwReportCommand, runReleaseTrustCommand, runRulesCommand, runScan, runScanCommand, runStatsCommand, runSuppressionGateCommand, runSuppressions, runTrustTrendCommand, runVerifyCommand, runWhyCommand, usageErrorMessage, validateScanTarget };
|
|
33241
33351
|
|
|
33242
33352
|
//# sourceMappingURL=cli.mjs.map
|