mjolnir-qa 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +508 -1
- package/README.md +314 -29
- package/dist/cli.d.mts +12 -480
- package/dist/cli.mjs +808 -620
- package/dist/mcp/stdio.mjs +318 -397
- package/package.json +24 -34
- package/dist/rolldown-runtime-8H4AJuhK.mjs +0 -14
package/dist/mcp/stdio.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
4
4
|
import { createHash, randomBytes } from "node:crypto";
|
|
5
|
-
import { closeSync, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
5
|
+
import { closeSync, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
6
6
|
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
8
8
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -12,7 +12,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
12
12
|
import { Language, Parser } from "web-tree-sitter";
|
|
13
13
|
import { parse } from "yaml";
|
|
14
14
|
import { inflateRawSync } from "node:zlib";
|
|
15
|
-
|
|
15
|
+
// src/types.ts
|
|
16
16
|
/** Severity ladder. Order matters for sorting and gating. */
|
|
17
17
|
const SEVERITY_ORDER = [
|
|
18
18
|
"error",
|
|
@@ -99,8 +99,7 @@ const DEDUCTIONS = {
|
|
|
99
99
|
warning: 3,
|
|
100
100
|
info: 1
|
|
101
101
|
};
|
|
102
|
-
|
|
103
|
-
//#region src/engine/completion.ts
|
|
102
|
+
// src/engine/completion.ts
|
|
104
103
|
function deriveCompletion(input) {
|
|
105
104
|
const truncationReasons = [...new Set(input.truncationReasons)].sort();
|
|
106
105
|
const degradations = [...input.degradations ?? []].sort((a, b) => a.reason < b.reason ? -1 : a.reason > b.reason ? 1 : 0);
|
|
@@ -142,8 +141,7 @@ function deriveCompletion(input) {
|
|
|
142
141
|
}
|
|
143
142
|
};
|
|
144
143
|
}
|
|
145
|
-
|
|
146
|
-
//#region src/engine/degradation-ledger.ts
|
|
144
|
+
// src/engine/degradation-ledger.ts
|
|
147
145
|
/**
|
|
148
146
|
* Append-only. Records are never edited and never removed by a recording
|
|
149
147
|
* site; a reader takes a window, and the window is defined by index, so
|
|
@@ -179,8 +177,7 @@ function summarizeDegradations(window) {
|
|
|
179
177
|
count
|
|
180
178
|
})).sort((a, b) => a.reason < b.reason ? -1 : a.reason > b.reason ? 1 : 0);
|
|
181
179
|
}
|
|
182
|
-
|
|
183
|
-
//#region src/engine/runtime-corroboration.ts
|
|
180
|
+
// src/engine/runtime-corroboration.ts
|
|
184
181
|
/**
|
|
185
182
|
* Stamp runtime corroboration + trust levels onto findings (mutates in
|
|
186
183
|
* place, the same contract as stampEvidenceLevels). Returns the number
|
|
@@ -196,7 +193,8 @@ function stampRuntimeCorroboration(findings, report, scanRoot) {
|
|
|
196
193
|
let n = p.replace(/\\/g, "/");
|
|
197
194
|
if (scanRoot) {
|
|
198
195
|
const root = scanRoot.replace(/\\/g, "/").replace(/\/?$/, "/");
|
|
199
|
-
|
|
196
|
+
const head = n.slice(0, root.length);
|
|
197
|
+
if (/^[a-z]:\//i.test(root) ? head.toLowerCase() === root.toLowerCase() : head === root) n = n.slice(root.length);
|
|
200
198
|
}
|
|
201
199
|
return n.replace(/^\.\//, "").replace(/^\/+/, "");
|
|
202
200
|
};
|
|
@@ -262,8 +260,7 @@ function deriveTrustLevel(finding, corroboration) {
|
|
|
262
260
|
if (corroboration.level === "test") return "L4";
|
|
263
261
|
return "L3";
|
|
264
262
|
}
|
|
265
|
-
|
|
266
|
-
//#region src/rules/measured-fp.generated.ts
|
|
263
|
+
// src/rules/measured-fp.generated.ts
|
|
267
264
|
const MEASURED_FP = {
|
|
268
265
|
"QA-CI-001": {
|
|
269
266
|
fpRate: .105,
|
|
@@ -777,8 +774,7 @@ const MEASURED_FP = {
|
|
|
777
774
|
ciHigh: .4343
|
|
778
775
|
}
|
|
779
776
|
};
|
|
780
|
-
|
|
781
|
-
//#region src/engine/trust-summary.ts
|
|
777
|
+
// src/engine/trust-summary.ts
|
|
782
778
|
/**
|
|
783
779
|
* Trust level of the summary — the BEST trust level any finding in the
|
|
784
780
|
* scan reached. Static-only findings settle at L2 (deriveTrustLevel's
|
|
@@ -902,8 +898,7 @@ function buildTrustSummary(result, declarationsByFile) {
|
|
|
902
898
|
ceilingReasons: reasons
|
|
903
899
|
};
|
|
904
900
|
}
|
|
905
|
-
|
|
906
|
-
//#region src/governance/run-mode.ts
|
|
901
|
+
// src/governance/run-mode.ts
|
|
907
902
|
/**
|
|
908
903
|
* Which mode a run is in, and how the other one is reached.
|
|
909
904
|
*
|
|
@@ -954,8 +949,7 @@ function resolveRunMode(input) {
|
|
|
954
949
|
if (declared === "local-only" || declared === "hosted") return declared;
|
|
955
950
|
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.`);
|
|
956
951
|
}
|
|
957
|
-
|
|
958
|
-
//#region src/engine/run-identity.ts
|
|
952
|
+
// src/engine/run-identity.ts
|
|
959
953
|
/**
|
|
960
954
|
* Run Identity (product-gap master plan §7, plan 1789009691197 R4c) —
|
|
961
955
|
* the deterministic binding that anchors every trust claim to its
|
|
@@ -1084,8 +1078,7 @@ function buildEvidenceGraph(parts) {
|
|
|
1084
1078
|
} } : {}
|
|
1085
1079
|
};
|
|
1086
1080
|
}
|
|
1087
|
-
|
|
1088
|
-
//#region src/engine/file-executor.ts
|
|
1081
|
+
// src/engine/file-executor.ts
|
|
1089
1082
|
function cacheHitOutcome(job) {
|
|
1090
1083
|
return {
|
|
1091
1084
|
path: job.path,
|
|
@@ -1176,8 +1169,7 @@ function createExecutor(name, concurrency, runOne) {
|
|
|
1176
1169
|
execute: runOne
|
|
1177
1170
|
};
|
|
1178
1171
|
}
|
|
1179
|
-
|
|
1180
|
-
//#region src/scope/git-resolve.ts
|
|
1172
|
+
// src/scope/git-resolve.ts
|
|
1181
1173
|
/**
|
|
1182
1174
|
* Absolute-path git resolution (audit S1).
|
|
1183
1175
|
*
|
|
@@ -1252,8 +1244,7 @@ function runGit(root, args) {
|
|
|
1252
1244
|
return null;
|
|
1253
1245
|
}
|
|
1254
1246
|
}
|
|
1255
|
-
|
|
1256
|
-
//#region src/lib/safe-json.ts
|
|
1247
|
+
// src/lib/safe-json.ts
|
|
1257
1248
|
/**
|
|
1258
1249
|
* Reusable safe-JSON parse-and-validate helper.
|
|
1259
1250
|
*
|
|
@@ -1290,8 +1281,7 @@ function isRecord(v) {
|
|
|
1290
1281
|
return false;
|
|
1291
1282
|
}
|
|
1292
1283
|
}
|
|
1293
|
-
|
|
1294
|
-
//#region src/engine/candidate-binding.ts
|
|
1284
|
+
// src/engine/candidate-binding.ts
|
|
1295
1285
|
/**
|
|
1296
1286
|
* Candidate binding (plan V5-010).
|
|
1297
1287
|
*
|
|
@@ -1424,18 +1414,17 @@ function readCandidateBinding(root, manifestPath = CANDIDATE_MANIFEST_PATH) {
|
|
|
1424
1414
|
return null;
|
|
1425
1415
|
}
|
|
1426
1416
|
}
|
|
1427
|
-
|
|
1428
|
-
//#region src/engine/version.ts
|
|
1417
|
+
// src/engine/version.ts
|
|
1429
1418
|
/**
|
|
1430
1419
|
* Engine version, as a leaf module: `runIdentity` (R4c) needs the engine
|
|
1431
1420
|
* version inside scan-pipeline WITHOUT importing cli.ts (a cycle — cli
|
|
1432
1421
|
* imports the pipeline). The literal follows the same discipline as
|
|
1433
1422
|
* CLI_VERSION and SARIF's driver.version: kept in sync on release by
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
1423
|
+
* `npm run version:surface:sync` (this file is an identity surface) and
|
|
1424
|
+
* guarded by the version-consistency
|
|
1436
1425
|
* spec. cli.ts re-exports this as CLI_VERSION.
|
|
1437
1426
|
*/
|
|
1438
|
-
const ENGINE_VERSION = "6.
|
|
1427
|
+
const ENGINE_VERSION = "6.1.0";
|
|
1439
1428
|
resolveBuildId();
|
|
1440
1429
|
/**
|
|
1441
1430
|
* The decision, with the process boundary already behind `runGit`.
|
|
@@ -1475,16 +1464,7 @@ function resolveBuildId() {
|
|
|
1475
1464
|
};
|
|
1476
1465
|
return buildIdFrom(runGit);
|
|
1477
1466
|
}
|
|
1478
|
-
|
|
1479
|
-
//#region src/engine/contract-versions.ts
|
|
1480
|
-
/**
|
|
1481
|
-
* Versioned contract registry (VERSION-001).
|
|
1482
|
-
*
|
|
1483
|
-
* Single source of truth for every version constant the engine exposes
|
|
1484
|
-
* to consumers (run identity, evidence graph, machine contract).
|
|
1485
|
-
* Constants are `as const` — literal types flow into the identity
|
|
1486
|
-
* surface without widening.
|
|
1487
|
-
*/
|
|
1467
|
+
// src/engine/contract-versions.ts
|
|
1488
1468
|
const TRUST_MODEL_VERSION = "1.0.0";
|
|
1489
1469
|
/**
|
|
1490
1470
|
* 2.0.0 (ADR 0014): the core tier carries an evidence FLOOR. A core rule's
|
|
@@ -1496,8 +1476,7 @@ const TRUST_MODEL_VERSION = "1.0.0";
|
|
|
1496
1476
|
*/
|
|
1497
1477
|
const SCORING_MODEL_VERSION = "2.0.0";
|
|
1498
1478
|
const FRAMEWORK_SUPPORT_MATRIX_VERSION = "1.0.0";
|
|
1499
|
-
|
|
1500
|
-
//#region src/discovery/evidence-discovery.ts
|
|
1479
|
+
// src/discovery/evidence-discovery.ts
|
|
1501
1480
|
/**
|
|
1502
1481
|
* Zero-config evidence discovery (Mega MVP Master Plan v3.1 §26 WI-11).
|
|
1503
1482
|
*
|
|
@@ -1589,8 +1568,7 @@ function discoverEvidenceCandidates(scanRoot) {
|
|
|
1589
1568
|
}
|
|
1590
1569
|
return out.sort((a, b) => a.depth - b.depth || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
1591
1570
|
}
|
|
1592
|
-
|
|
1593
|
-
//#region src/discovery/workspace.ts
|
|
1571
|
+
// src/discovery/workspace.ts
|
|
1594
1572
|
/**
|
|
1595
1573
|
* Repository discovery (Sprint-Plan W1-03).
|
|
1596
1574
|
* Finds project root, parses package.json, detects npm/yarn/pnpm workspaces.
|
|
@@ -1637,8 +1615,7 @@ function basename$1(p) {
|
|
|
1637
1615
|
const parts = p.split(/[\\/]/);
|
|
1638
1616
|
return parts[parts.length - 1];
|
|
1639
1617
|
}
|
|
1640
|
-
|
|
1641
|
-
//#region src/lib/fs-bounded.ts
|
|
1618
|
+
// src/lib/fs-bounded.ts
|
|
1642
1619
|
function closeQuiet(fd) {
|
|
1643
1620
|
try {
|
|
1644
1621
|
closeSync(fd);
|
|
@@ -1690,8 +1667,7 @@ function readFileBounded(path, maxBytes) {
|
|
|
1690
1667
|
if (fd !== void 0) closeQuiet(fd);
|
|
1691
1668
|
}
|
|
1692
1669
|
}
|
|
1693
|
-
|
|
1694
|
-
//#region src/config/config-schema.ts
|
|
1670
|
+
// src/config/config-schema.ts
|
|
1695
1671
|
const VALID_GATES = /* @__PURE__ */ new Set([
|
|
1696
1672
|
"advisory",
|
|
1697
1673
|
"error",
|
|
@@ -1754,15 +1730,18 @@ function validateConfigSchema(config) {
|
|
|
1754
1730
|
}
|
|
1755
1731
|
}
|
|
1756
1732
|
if (cfg["plugins"] !== void 0) {
|
|
1757
|
-
if (cfg["plugins"]
|
|
1733
|
+
if (!Array.isArray(cfg["plugins"])) errors.push(`plugins must be an array of package names or { "package": "<name>" } objects, got ${typeof cfg["plugins"]}`);
|
|
1734
|
+
else for (let i = 0; i < cfg["plugins"].length; i++) {
|
|
1735
|
+
const entry = cfg["plugins"][i];
|
|
1736
|
+
if (!(typeof entry === "string" || entry !== null && typeof entry === "object" && !Array.isArray(entry) && typeof entry["package"] === "string")) errors.push(`plugins[${i}] must be a package name or { "package": "<name>" }, got ${JSON.stringify(entry)}`);
|
|
1737
|
+
}
|
|
1758
1738
|
}
|
|
1759
1739
|
return {
|
|
1760
1740
|
valid: errors.length === 0,
|
|
1761
1741
|
errors
|
|
1762
1742
|
};
|
|
1763
1743
|
}
|
|
1764
|
-
|
|
1765
|
-
//#region src/config/config.ts
|
|
1744
|
+
// src/config/config.ts
|
|
1766
1745
|
/**
|
|
1767
1746
|
* Config file support (Sprint-Plan W7, Product-MVP §27 + GAP-F).
|
|
1768
1747
|
* Zero-config preserved: config presence never changes detection
|
|
@@ -1820,7 +1799,7 @@ function loadConfig(root, options = {}) {
|
|
|
1820
1799
|
return {
|
|
1821
1800
|
config: parsed,
|
|
1822
1801
|
path: p,
|
|
1823
|
-
warnings: validate(parsed, options.knownRuleIds)
|
|
1802
|
+
warnings: validate(parsed, options.knownRuleIds, options.now, name)
|
|
1824
1803
|
};
|
|
1825
1804
|
} catch (err) {
|
|
1826
1805
|
const msg = err.message;
|
|
@@ -1833,7 +1812,7 @@ function loadConfig(root, options = {}) {
|
|
|
1833
1812
|
warnings: []
|
|
1834
1813
|
};
|
|
1835
1814
|
}
|
|
1836
|
-
function validate(cfg, knownRuleIds) {
|
|
1815
|
+
function validate(cfg, knownRuleIds, now = /* @__PURE__ */ new Date(), fileName = "mjolnir.config.json") {
|
|
1837
1816
|
const warnings = [];
|
|
1838
1817
|
const schemaResult = validateConfigSchema(cfg);
|
|
1839
1818
|
if (!schemaResult.valid) throw new Error(schemaResult.errors.join("; "));
|
|
@@ -1853,10 +1832,10 @@ function validate(cfg, knownRuleIds) {
|
|
|
1853
1832
|
if (!Array.isArray(ign.files)) throw new Error(`ignore for ${ign.ruleId}: "files" must be an array of glob strings, got ${typeof ign.files}`);
|
|
1854
1833
|
for (const g of ign.files) if (typeof g !== "string") throw new Error(`ignore for ${ign.ruleId}: "files" entries must be strings, got ${typeof g} (${JSON.stringify(g)})`);
|
|
1855
1834
|
}
|
|
1856
|
-
if (ign.expires !== void 0 && Number.isNaN(
|
|
1857
|
-
if (ign.expires !== void 0 && !
|
|
1835
|
+
if (ign.expires !== void 0 && Number.isNaN(utcMidnight(ign.expires))) throw new Error(`ignore for ${ign.ruleId}: "expires" must be an ISO date (YYYY-MM-DD), got "${ign.expires}"`);
|
|
1836
|
+
if (ign.expires !== void 0 && !isSuppressionActive(ign, now)) warnings.push(`warning: ignore for ${ign.ruleId} has expired (expires "${ign.expires}") — the entry is kept but currently inactive.`);
|
|
1858
1837
|
}
|
|
1859
|
-
for (const key of Object.keys(cfg)) if (!KNOWN_CONFIG_KEYS.has(key)) warnings.push(`warning:
|
|
1838
|
+
for (const key of Object.keys(cfg)) if (!KNOWN_CONFIG_KEYS.has(key)) warnings.push(`warning: ${fileName} has an unknown top-level key "${key}" — ignored (typo? see README §Configuration).`);
|
|
1860
1839
|
if (cfg.exclude !== void 0) {
|
|
1861
1840
|
if (!Array.isArray(cfg.exclude)) throw new Error(`exclude must be an array of strings, got ${typeof cfg.exclude}`);
|
|
1862
1841
|
for (const p of cfg.exclude) if (typeof p !== "string") throw new Error(`exclude entries must be strings, got ${typeof p} (${JSON.stringify(p)})`);
|
|
@@ -1878,12 +1857,22 @@ function isSuppressionActive(ign, now = /* @__PURE__ */ new Date()) {
|
|
|
1878
1857
|
if (!ign.expires) return true;
|
|
1879
1858
|
return utcMidnight(ign.expires) > utcMidnightOf(now);
|
|
1880
1859
|
}
|
|
1881
|
-
/**
|
|
1860
|
+
/**
|
|
1861
|
+
* UTC-midnight timestamp of an ISO calendar date (YYYY-MM-DD), or NaN.
|
|
1862
|
+
*
|
|
1863
|
+
* NaN for anything that is not a real calendar day, so a load-time check and
|
|
1864
|
+
* the active/expired verdict share one parser and cannot disagree.
|
|
1865
|
+
*/
|
|
1882
1866
|
function utcMidnight(isoDate) {
|
|
1883
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(isoDate)) return NaN;
|
|
1884
1867
|
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(isoDate);
|
|
1885
1868
|
if (!m) return NaN;
|
|
1886
|
-
|
|
1869
|
+
const year = Number(m[1]);
|
|
1870
|
+
const month = Number(m[2]) - 1;
|
|
1871
|
+
const day = Number(m[3]);
|
|
1872
|
+
const t = Date.UTC(year, month, day);
|
|
1873
|
+
const back = new Date(t);
|
|
1874
|
+
if (back.getUTCFullYear() !== year || back.getUTCMonth() !== month || back.getUTCDate() !== day) return NaN;
|
|
1875
|
+
return t;
|
|
1887
1876
|
}
|
|
1888
1877
|
/** UTC-midnight timestamp of a Date (calendar day, timezone-independent). */
|
|
1889
1878
|
function utcMidnightOf(d) {
|
|
@@ -1896,8 +1885,7 @@ function applySeverityOverrides(findings, cfg) {
|
|
|
1896
1885
|
if (override && SEVERITY_ORDER.includes(override)) f.severity = override;
|
|
1897
1886
|
}
|
|
1898
1887
|
}
|
|
1899
|
-
|
|
1900
|
-
//#region src/lib/compare.ts
|
|
1888
|
+
// src/lib/compare.ts
|
|
1901
1889
|
/**
|
|
1902
1890
|
* Order that does not depend on who is asking.
|
|
1903
1891
|
*
|
|
@@ -1943,8 +1931,7 @@ function compareCodePoints(a, b) {
|
|
|
1943
1931
|
function compareLocalized(locale) {
|
|
1944
1932
|
return (a, b) => a.localeCompare(b, locale);
|
|
1945
1933
|
}
|
|
1946
|
-
|
|
1947
|
-
//#region src/lib/glob.ts
|
|
1934
|
+
// src/lib/glob.ts
|
|
1948
1935
|
/**
|
|
1949
1936
|
* The shared segment compiler: a glob to a regex BODY, with no anchors.
|
|
1950
1937
|
*
|
|
@@ -1998,8 +1985,7 @@ function anyDepthGlobRegExp(glob) {
|
|
|
1998
1985
|
function pathMatchesGlob(path, glob) {
|
|
1999
1986
|
return anchoredGlobRegExp(glob).test(path.replaceAll("\\", "/"));
|
|
2000
1987
|
}
|
|
2001
|
-
|
|
2002
|
-
//#region src/discovery/ignores.ts
|
|
1988
|
+
// src/discovery/ignores.ts
|
|
2003
1989
|
/**
|
|
2004
1990
|
* Ignore resolution (Product-MVP.txt R1 — Tier-1 launch blocker).
|
|
2005
1991
|
*
|
|
@@ -2155,8 +2141,7 @@ function isLintFixtureDir(absoluteDir) {
|
|
|
2155
2141
|
return false;
|
|
2156
2142
|
}
|
|
2157
2143
|
}
|
|
2158
|
-
|
|
2159
|
-
//#region src/discovery/shared-walk.ts
|
|
2144
|
+
// src/discovery/shared-walk.ts
|
|
2160
2145
|
/**
|
|
2161
2146
|
* Shared directory walker (audit P-2).
|
|
2162
2147
|
*
|
|
@@ -2219,8 +2204,7 @@ function sharedWalk(options) {
|
|
|
2219
2204
|
};
|
|
2220
2205
|
walk(options.root);
|
|
2221
2206
|
}
|
|
2222
|
-
|
|
2223
|
-
//#region src/discovery/frameworks.ts
|
|
2207
|
+
// src/discovery/frameworks.ts
|
|
2224
2208
|
/**
|
|
2225
2209
|
* Framework detection via config resolution (Sprint-Plan W3-01, R8).
|
|
2226
2210
|
*
|
|
@@ -2303,8 +2287,7 @@ function detectFrameworks(ws) {
|
|
|
2303
2287
|
unknown: false
|
|
2304
2288
|
};
|
|
2305
2289
|
}
|
|
2306
|
-
|
|
2307
|
-
//#region src/engine/ts-ast.ts
|
|
2290
|
+
// src/engine/ts-ast.ts
|
|
2308
2291
|
/**
|
|
2309
2292
|
* AST precision layer (Upgrade-Plan-v3 Phase 3).
|
|
2310
2293
|
*
|
|
@@ -2503,8 +2486,7 @@ function getCodeOnlyText(file) {
|
|
|
2503
2486
|
function resetTsMorphProject() {
|
|
2504
2487
|
looseProject = null;
|
|
2505
2488
|
}
|
|
2506
|
-
|
|
2507
|
-
//#region src/engine/code-text.ts
|
|
2489
|
+
// src/engine/code-text.ts
|
|
2508
2490
|
/**
|
|
2509
2491
|
* Code-only text masking (Phase 1 — Tempering Plan).
|
|
2510
2492
|
*
|
|
@@ -2864,8 +2846,7 @@ function getCodeTextForCounting(text, language) {
|
|
|
2864
2846
|
function blankRange(chars, start, end) {
|
|
2865
2847
|
for (let i = start; i < end && i < chars.length; i++) if (chars[i] !== "\n" && chars[i] !== "\r") chars[i] = " ";
|
|
2866
2848
|
}
|
|
2867
|
-
|
|
2868
|
-
//#region src/engine/adapter.ts
|
|
2849
|
+
// src/engine/adapter.ts
|
|
2869
2850
|
/**
|
|
2870
2851
|
* The §15.1 framework filter, shared by every language adapter: a rule
|
|
2871
2852
|
* that declares `frameworks` runs on a file only when the file carries
|
|
@@ -2879,8 +2860,7 @@ function frameworkFilterApplies(rule, file) {
|
|
|
2879
2860
|
if (tags === void 0 || tags.length === 0) return true;
|
|
2880
2861
|
return rule.frameworks.some((f) => tags.includes(f));
|
|
2881
2862
|
}
|
|
2882
|
-
|
|
2883
|
-
//#region src/engine/shared-run-rules.ts
|
|
2863
|
+
// src/engine/shared-run-rules.ts
|
|
2884
2864
|
/**
|
|
2885
2865
|
* Run ONE rule with crash isolation, and return what it produced.
|
|
2886
2866
|
*
|
|
@@ -2917,8 +2897,7 @@ function runRuleIsolated(rule, file, onCrash) {
|
|
|
2917
2897
|
return null;
|
|
2918
2898
|
}
|
|
2919
2899
|
}
|
|
2920
|
-
|
|
2921
|
-
//#region src/adapters/typescript.ts
|
|
2900
|
+
// src/adapters/typescript.ts
|
|
2922
2901
|
/**
|
|
2923
2902
|
* TypeScript/JavaScript adapter (R1).
|
|
2924
2903
|
* Wraps the proven regex-based engine behind the LanguageAdapter
|
|
@@ -3171,8 +3150,7 @@ function loadWorkspaceShim(root) {
|
|
|
3171
3150
|
return null;
|
|
3172
3151
|
}
|
|
3173
3152
|
}
|
|
3174
|
-
|
|
3175
|
-
//#region src/engine/tree-sitter-ast.ts
|
|
3153
|
+
// src/engine/tree-sitter-ast.ts
|
|
3176
3154
|
/**
|
|
3177
3155
|
* Tree-sitter WASM AST layer for Java and C# — Sprint 8 Task 36
|
|
3178
3156
|
* (Master-Stabilization-Plan.md).
|
|
@@ -3391,8 +3369,7 @@ async function releaseTreeSitterResources() {
|
|
|
3391
3369
|
} catch {}
|
|
3392
3370
|
}
|
|
3393
3371
|
}
|
|
3394
|
-
|
|
3395
|
-
//#region src/adapters/python.ts
|
|
3372
|
+
// src/adapters/python.ts
|
|
3396
3373
|
/**
|
|
3397
3374
|
* Python/pytest adapter (Upgrade-Plan-v2 R2).
|
|
3398
3375
|
*
|
|
@@ -3550,8 +3527,7 @@ const pythonAdapter = {
|
|
|
3550
3527
|
function readText$2(p) {
|
|
3551
3528
|
return readFileSync(p, "utf8");
|
|
3552
3529
|
}
|
|
3553
|
-
|
|
3554
|
-
//#region src/adapters/java.ts
|
|
3530
|
+
// src/adapters/java.ts
|
|
3555
3531
|
/**
|
|
3556
3532
|
* Java/JUnit adapter (Upgrade-Plan-v3 Phase 4).
|
|
3557
3533
|
*
|
|
@@ -3619,7 +3595,9 @@ const javaAdapter = {
|
|
|
3619
3595
|
const artifact = /<artifactId>([^<]+)<\/artifactId>/.exec(block[0])?.[1] ?? "";
|
|
3620
3596
|
for (const { re, tag } of JAVA_ARTIFACT_TAGS) if (re.test(artifact)) frameworks.add(tag);
|
|
3621
3597
|
}
|
|
3622
|
-
} catch {
|
|
3598
|
+
} catch {
|
|
3599
|
+
recordDegradation("java-manifest-unreadable");
|
|
3600
|
+
}
|
|
3623
3601
|
for (const gradle of ["build.gradle", "build.gradle.kts"]) {
|
|
3624
3602
|
const path = join(root, gradle);
|
|
3625
3603
|
if (!existsSync(path)) continue;
|
|
@@ -3629,7 +3607,9 @@ const javaAdapter = {
|
|
|
3629
3607
|
const artifact = m[1].split(":")[1];
|
|
3630
3608
|
for (const { re, tag } of JAVA_ARTIFACT_TAGS) if (artifact !== void 0 && re.test(artifact)) frameworks.add(tag);
|
|
3631
3609
|
}
|
|
3632
|
-
} catch {
|
|
3610
|
+
} catch {
|
|
3611
|
+
recordDegradation("java-manifest-unreadable");
|
|
3612
|
+
}
|
|
3633
3613
|
}
|
|
3634
3614
|
if (frameworks.size === 0) return {
|
|
3635
3615
|
frameworks: [],
|
|
@@ -3717,8 +3697,7 @@ function javaFileTags(file) {
|
|
|
3717
3697
|
function readText$1(path) {
|
|
3718
3698
|
return readFileSync(path, "utf8");
|
|
3719
3699
|
}
|
|
3720
|
-
|
|
3721
|
-
//#region src/adapters/csharp.ts
|
|
3700
|
+
// src/adapters/csharp.ts
|
|
3722
3701
|
/**
|
|
3723
3702
|
* C#/.NET adapter (Upgrade-Plan-v3 Phase 5).
|
|
3724
3703
|
*
|
|
@@ -3788,9 +3767,13 @@ const csharpAdapter = {
|
|
|
3788
3767
|
const pkg = m[1];
|
|
3789
3768
|
for (const { re, tag } of CS_PACKAGE_TAGS) if (re.test(pkg)) frameworks.add(tag);
|
|
3790
3769
|
}
|
|
3791
|
-
} catch {
|
|
3770
|
+
} catch {
|
|
3771
|
+
recordDegradation("csharp-project-unreadable");
|
|
3772
|
+
}
|
|
3792
3773
|
}
|
|
3793
|
-
} catch {
|
|
3774
|
+
} catch {
|
|
3775
|
+
recordDegradation("csharp-project-unreadable");
|
|
3776
|
+
}
|
|
3794
3777
|
if (frameworks.size === 0) return {
|
|
3795
3778
|
frameworks: [],
|
|
3796
3779
|
unknown: true
|
|
@@ -3874,8 +3857,7 @@ function csharpFileTags(file) {
|
|
|
3874
3857
|
function readText(path) {
|
|
3875
3858
|
return readFileSync(path, "utf8");
|
|
3876
3859
|
}
|
|
3877
|
-
|
|
3878
|
-
//#region src/discovery/yaml-guards.ts
|
|
3860
|
+
// src/discovery/yaml-guards.ts
|
|
3879
3861
|
/**
|
|
3880
3862
|
* Shared hostile-YAML guards (product-gap master plan P3 common law —
|
|
3881
3863
|
* `azure-pipelines.yml` detection rides the same YAML machinery as the
|
|
@@ -3944,8 +3926,7 @@ function enforceDepthCap(value, max, depthMessage = GENERIC_LABELS.depthMessage)
|
|
|
3944
3926
|
});
|
|
3945
3927
|
}
|
|
3946
3928
|
}
|
|
3947
|
-
|
|
3948
|
-
//#region src/discovery/workflow-parser.ts
|
|
3929
|
+
// src/discovery/workflow-parser.ts
|
|
3949
3930
|
/**
|
|
3950
3931
|
* Safe GitHub Actions workflow parser (Phase 0.3, W4-03 debt).
|
|
3951
3932
|
*
|
|
@@ -3991,8 +3972,7 @@ function parseWorkflow(text) {
|
|
|
3991
3972
|
}
|
|
3992
3973
|
return { jobs };
|
|
3993
3974
|
}
|
|
3994
|
-
|
|
3995
|
-
//#region src/adapters/github-actions.ts
|
|
3975
|
+
// src/adapters/github-actions.ts
|
|
3996
3976
|
/**
|
|
3997
3977
|
* GitHub Actions adapter (R1).
|
|
3998
3978
|
* Owns workflow discovery and safe-YAML parsing; CI rules consume the
|
|
@@ -4077,8 +4057,7 @@ const githubActionsAdapter = {
|
|
|
4077
4057
|
};
|
|
4078
4058
|
var WorkflowParseSkipped = class extends Error {};
|
|
4079
4059
|
const LIMITS_MAX_BYTES$2 = 1048576;
|
|
4080
|
-
|
|
4081
|
-
//#region src/discovery/azure-pipeline-parser.ts
|
|
4060
|
+
// src/discovery/azure-pipeline-parser.ts
|
|
4082
4061
|
/**
|
|
4083
4062
|
* Safe Azure DevOps pipeline parser (product-gap master plan P3b).
|
|
4084
4063
|
*
|
|
@@ -4312,8 +4291,7 @@ function isAzurePipelineFixture(normalizedPath) {
|
|
|
4312
4291
|
const base = normalizedPath.replaceAll("\\", "/").split("/").pop() ?? "";
|
|
4313
4292
|
return AZURE_PIPELINE_FILENAMES_SET.has(base);
|
|
4314
4293
|
}
|
|
4315
|
-
|
|
4316
|
-
//#region src/adapters/azure-pipelines.ts
|
|
4294
|
+
// src/adapters/azure-pipelines.ts
|
|
4317
4295
|
/**
|
|
4318
4296
|
* Azure DevOps pipeline adapter (product-gap master plan P3b).
|
|
4319
4297
|
* Owns `azure-pipelines.yml` discovery (repo root, the platform's
|
|
@@ -4393,8 +4371,7 @@ const azurePipelinesAdapter = {
|
|
|
4393
4371
|
};
|
|
4394
4372
|
var AzureParseSkipped = class extends Error {};
|
|
4395
4373
|
const LIMITS_MAX_BYTES$1 = 1048576;
|
|
4396
|
-
|
|
4397
|
-
//#region src/adapters/jenkins.ts
|
|
4374
|
+
// src/adapters/jenkins.ts
|
|
4398
4375
|
/**
|
|
4399
4376
|
* Jenkins adapter (product-gap master plan P3c).
|
|
4400
4377
|
* The root `Jenkinsfile` is a TEXT-target file kind: declarative and
|
|
@@ -4468,8 +4445,7 @@ const jenkinsAdapter = {
|
|
|
4468
4445
|
}
|
|
4469
4446
|
};
|
|
4470
4447
|
const LIMITS_MAX_BYTES = 1048576;
|
|
4471
|
-
|
|
4472
|
-
//#region src/discovery/scan-adapters.ts
|
|
4448
|
+
// src/discovery/scan-adapters.ts
|
|
4473
4449
|
/**
|
|
4474
4450
|
* The canonical adapter registry (audits H-6/H-9).
|
|
4475
4451
|
*
|
|
@@ -4606,8 +4582,7 @@ function isInsideSkippedDir(root, absPath, skipNames) {
|
|
|
4606
4582
|
return relative(root, absPath).replaceAll("\\", "/").split("/").slice(0, -1).some((seg) => skipNames.includes(seg));
|
|
4607
4583
|
}
|
|
4608
4584
|
typescriptAdapter.testFileGlobs, pythonAdapter.testFileGlobs, javaAdapter.testFileGlobs, csharpAdapter.testFileGlobs, githubActionsAdapter.testFileGlobs, azurePipelinesAdapter.testFileGlobs, jenkinsAdapter.testFileGlobs;
|
|
4609
|
-
|
|
4610
|
-
//#region src/scope/changed.ts
|
|
4585
|
+
// src/scope/changed.ts
|
|
4611
4586
|
/**
|
|
4612
4587
|
* Changed-scope engine (Sprint-Plan W6, Product-MVP §9 `--scope changed`).
|
|
4613
4588
|
*
|
|
@@ -4712,6 +4687,7 @@ function computeChangedScope(root, baseBranch) {
|
|
|
4712
4687
|
...untrackedSet
|
|
4713
4688
|
])].sort();
|
|
4714
4689
|
const changed = {};
|
|
4690
|
+
const fullyChanged = /* @__PURE__ */ new Set();
|
|
4715
4691
|
const batchDiff = (args, files) => {
|
|
4716
4692
|
const perFile = /* @__PURE__ */ new Map();
|
|
4717
4693
|
if (files.length === 0) return perFile;
|
|
@@ -4763,7 +4739,8 @@ function computeChangedScope(root, baseBranch) {
|
|
|
4763
4739
|
if (untrackedSet.has(file)) {
|
|
4764
4740
|
const all = allLinesOf(root, file);
|
|
4765
4741
|
if (all === null) {
|
|
4766
|
-
|
|
4742
|
+
fullyChanged.add(file);
|
|
4743
|
+
changed[file] = lines;
|
|
4767
4744
|
continue;
|
|
4768
4745
|
}
|
|
4769
4746
|
for (const l of all) lines.add(l);
|
|
@@ -4772,11 +4749,10 @@ function computeChangedScope(root, baseBranch) {
|
|
|
4772
4749
|
}
|
|
4773
4750
|
return {
|
|
4774
4751
|
changed,
|
|
4752
|
+
...fullyChanged.size > 0 ? { fullyChanged } : {},
|
|
4775
4753
|
degraded: false
|
|
4776
4754
|
};
|
|
4777
4755
|
}
|
|
4778
|
-
/** Sentinel size for "fully changed" — bounded, mirrors LIMITS.maxFileBytes scale. */
|
|
4779
|
-
const LIMITS_MAX_LINES = 1e6;
|
|
4780
4756
|
/** Every 1-based line number of an untracked file (it is all new). */
|
|
4781
4757
|
function allLinesOf(root, file) {
|
|
4782
4758
|
const full = join(root, file);
|
|
@@ -4826,6 +4802,7 @@ function parseChangedLines(diff) {
|
|
|
4826
4802
|
function filterToChanged(findings, diff) {
|
|
4827
4803
|
if (diff.degraded) return findings;
|
|
4828
4804
|
return findings.filter((f) => {
|
|
4805
|
+
if (diff.fullyChanged?.has(f.file)) return true;
|
|
4829
4806
|
const lines = diff.changed[f.file];
|
|
4830
4807
|
if (!lines) return false;
|
|
4831
4808
|
for (let l = Math.max(1, f.line - 3); l <= f.line; l++) if (lines.has(l)) return true;
|
|
@@ -4850,8 +4827,7 @@ function computeStagedFiles(root) {
|
|
|
4850
4827
|
if (raw === null) return null;
|
|
4851
4828
|
return raw.split("\0").filter((s) => s.length > 0);
|
|
4852
4829
|
}
|
|
4853
|
-
|
|
4854
|
-
//#region src/rules/rule.ts
|
|
4830
|
+
// src/rules/rule.ts
|
|
4855
4831
|
/**
|
|
4856
4832
|
* Invoke an `astQuery` hook with the mandatory-fallback contract: no
|
|
4857
4833
|
* hook or no AST on the context resolves to `undefined` (→ regex
|
|
@@ -4864,8 +4840,7 @@ function tryAstQuery(hook, ctx) {
|
|
|
4864
4840
|
function defineRule(rule) {
|
|
4865
4841
|
return rule;
|
|
4866
4842
|
}
|
|
4867
|
-
|
|
4868
|
-
//#region src/rules/shared/positions.ts
|
|
4843
|
+
// src/rules/shared/positions.ts
|
|
4869
4844
|
/**
|
|
4870
4845
|
* Shared position helpers (Phase 6 — Tempering Plan).
|
|
4871
4846
|
* One lineAt, one colAt, one matchBrace. The other 79 copies are dead.
|
|
@@ -4910,8 +4885,7 @@ function lineAt(text, index) {
|
|
|
4910
4885
|
function colAt(text, index) {
|
|
4911
4886
|
return index - (index <= 0 ? -1 : text.lastIndexOf("\n", index - 1));
|
|
4912
4887
|
}
|
|
4913
|
-
|
|
4914
|
-
//#region src/rules/test/qa-test-001-focused-test.ts
|
|
4888
|
+
// src/rules/test/qa-test-001-focused-test.ts
|
|
4915
4889
|
/**
|
|
4916
4890
|
* QA-TEST-001 — Focused test committed (`fit`/`fdescribe`/`.only`).
|
|
4917
4891
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -4977,8 +4951,7 @@ const focusedTestCommitted = defineRule({
|
|
|
4977
4951
|
return findings;
|
|
4978
4952
|
}
|
|
4979
4953
|
});
|
|
4980
|
-
|
|
4981
|
-
//#region src/rules/test/qa-test-002-skipped-test.ts
|
|
4954
|
+
// src/rules/test/qa-test-002-skipped-test.ts
|
|
4982
4955
|
/**
|
|
4983
4956
|
* QA-TEST-002 — Skipped test (`it.skip`, `xit`, `test.skip`, `describe.skip`).
|
|
4984
4957
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -5034,8 +5007,7 @@ const skippedTest = defineRule({
|
|
|
5034
5007
|
return findings;
|
|
5035
5008
|
}
|
|
5036
5009
|
});
|
|
5037
|
-
|
|
5038
|
-
//#region src/rules/shared/masking.ts
|
|
5010
|
+
// src/rules/shared/masking.ts
|
|
5039
5011
|
/** True when the offset was blanked in codeText (inside a string or comment). */
|
|
5040
5012
|
function isMasked(ctx, index) {
|
|
5041
5013
|
const mask = ctx.codeText;
|
|
@@ -5096,8 +5068,7 @@ function isInsideEmbeddedCode(ctx, index) {
|
|
|
5096
5068
|
const hasCallSyntax = /\w\s*\(/.test(inner);
|
|
5097
5069
|
return hasNestedQuote && hasCallSyntax;
|
|
5098
5070
|
}
|
|
5099
|
-
|
|
5100
|
-
//#region src/rules/test/qa-test-003-no-assertions.ts
|
|
5071
|
+
// src/rules/test/qa-test-003-no-assertions.ts
|
|
5101
5072
|
/**
|
|
5102
5073
|
* QA-TEST-003 — Test with no assertions.
|
|
5103
5074
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5175,8 +5146,7 @@ function matchBrace$2(text, open) {
|
|
|
5175
5146
|
}
|
|
5176
5147
|
return -1;
|
|
5177
5148
|
}
|
|
5178
|
-
|
|
5179
|
-
//#region src/rules/test/qa-test-004-hard-sleep.ts
|
|
5149
|
+
// src/rules/test/qa-test-004-hard-sleep.ts
|
|
5180
5150
|
/**
|
|
5181
5151
|
* QA-TEST-004 — Hard sleep (`sleep()`, `waitForTimeout()`, `setTimeout` as wait).
|
|
5182
5152
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -5241,8 +5211,7 @@ const hardSleep = defineRule({
|
|
|
5241
5211
|
return findings;
|
|
5242
5212
|
}
|
|
5243
5213
|
});
|
|
5244
|
-
|
|
5245
|
-
//#region src/rules/test/qa-test-006-retry-abuse.ts
|
|
5214
|
+
// src/rules/test/qa-test-006-retry-abuse.ts
|
|
5246
5215
|
/**
|
|
5247
5216
|
* QA-TEST-006 — Retry abuse in test config.
|
|
5248
5217
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -5308,8 +5277,7 @@ const retryAbuse = defineRule({
|
|
|
5308
5277
|
return findings;
|
|
5309
5278
|
}
|
|
5310
5279
|
});
|
|
5311
|
-
|
|
5312
|
-
//#region src/rules/test/qa-test-010-empty-body.ts
|
|
5280
|
+
// src/rules/test/qa-test-010-empty-body.ts
|
|
5313
5281
|
/**
|
|
5314
5282
|
* QA-TEST-010 — Empty test body.
|
|
5315
5283
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5364,8 +5332,7 @@ const emptyTestBody = defineRule({
|
|
|
5364
5332
|
return findings;
|
|
5365
5333
|
}
|
|
5366
5334
|
});
|
|
5367
|
-
|
|
5368
|
-
//#region src/rules/quality/qa-tqual-002-tautological.ts
|
|
5335
|
+
// src/rules/quality/qa-tqual-002-tautological.ts
|
|
5369
5336
|
/**
|
|
5370
5337
|
* QA-TQUAL-002 — Tautological assertion.
|
|
5371
5338
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5442,8 +5409,7 @@ const tautologicalAssertion = defineRule({
|
|
|
5442
5409
|
return findings;
|
|
5443
5410
|
}
|
|
5444
5411
|
});
|
|
5445
|
-
|
|
5446
|
-
//#region src/rules/quality/qa-tqual-009-promise-assertion.ts
|
|
5412
|
+
// src/rules/quality/qa-tqual-009-promise-assertion.ts
|
|
5447
5413
|
/**
|
|
5448
5414
|
* QA-TQUAL-009 — Unawaited assertion inside a promise chain.
|
|
5449
5415
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5613,8 +5579,7 @@ function matchParen$2(text, open) {
|
|
|
5613
5579
|
}
|
|
5614
5580
|
return -1;
|
|
5615
5581
|
}
|
|
5616
|
-
|
|
5617
|
-
//#region src/rules/quality/qa-tqual-011-commented-out.ts
|
|
5582
|
+
// src/rules/quality/qa-tqual-011-commented-out.ts
|
|
5618
5583
|
/**
|
|
5619
5584
|
* QA-TQUAL-011 — Commented-out tests.
|
|
5620
5585
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -5727,8 +5692,7 @@ function commentRanges(ctx) {
|
|
|
5727
5692
|
}
|
|
5728
5693
|
return ranges;
|
|
5729
5694
|
}
|
|
5730
|
-
|
|
5731
|
-
//#region src/rules/playwright/qa-pw-002-unawaited-assertion.ts
|
|
5695
|
+
// src/rules/playwright/qa-pw-002-unawaited-assertion.ts
|
|
5732
5696
|
/**
|
|
5733
5697
|
* QA-PW-002 — Missing `await` on a Playwright locator assertion.
|
|
5734
5698
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5873,8 +5837,7 @@ function runRegexFallback(text, path) {
|
|
|
5873
5837
|
});
|
|
5874
5838
|
return findings;
|
|
5875
5839
|
}
|
|
5876
|
-
|
|
5877
|
-
//#region src/rules/playwright/qa-pw-003-debug-artifacts.ts
|
|
5840
|
+
// src/rules/playwright/qa-pw-003-debug-artifacts.ts
|
|
5878
5841
|
/**
|
|
5879
5842
|
* QA-PW-003 — Committed `page.pause()` or `test.only()` in e2e specs.
|
|
5880
5843
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5926,8 +5889,7 @@ const committedDebugArtifacts = defineRule({
|
|
|
5926
5889
|
return findings;
|
|
5927
5890
|
}
|
|
5928
5891
|
});
|
|
5929
|
-
|
|
5930
|
-
//#region src/rules/playwright/qa-pw-004-brittle-selectors.ts
|
|
5892
|
+
// src/rules/playwright/qa-pw-004-brittle-selectors.ts
|
|
5931
5893
|
/**
|
|
5932
5894
|
* QA-PW-004 — Brittle CSS/XPath selectors vs role-based locators.
|
|
5933
5895
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -6019,8 +5981,7 @@ function stripComments(ctx) {
|
|
|
6019
5981
|
} catch {}
|
|
6020
5982
|
return ctx.text.replace(/\/\/[^\n]*/g, (cm) => " ".repeat(cm.length));
|
|
6021
5983
|
}
|
|
6022
|
-
|
|
6023
|
-
//#region src/rules/ci/verification-gate.ts
|
|
5984
|
+
// src/rules/ci/verification-gate.ts
|
|
6024
5985
|
/**
|
|
6025
5986
|
* Shared "is this shell command a verification gate?" matcher for the QA-CI
|
|
6026
5987
|
* rules. A gate is a command whose failure MUST fail the job — test runners,
|
|
@@ -6060,8 +6021,7 @@ const VERIFICATION_GATE_RE = new RegExp([
|
|
|
6060
6021
|
function looksLikeVerificationGate(cmd) {
|
|
6061
6022
|
return VERIFICATION_GATE_RE.test(cmd);
|
|
6062
6023
|
}
|
|
6063
|
-
|
|
6064
|
-
//#region src/rules/ci/azure-gates.ts
|
|
6024
|
+
// src/rules/ci/azure-gates.ts
|
|
6065
6025
|
/** Discriminator: the Azure parser is the only one emitting `platform`. */
|
|
6066
6026
|
function isAzurePipelineDoc(ast) {
|
|
6067
6027
|
return typeof ast === "object" && ast !== null && ast.platform === "azure-pipelines";
|
|
@@ -6145,8 +6105,7 @@ function locateAzureJobKey(text, job, keyName) {
|
|
|
6145
6105
|
if (m) return lineAt(text, m.index);
|
|
6146
6106
|
return 1;
|
|
6147
6107
|
}
|
|
6148
|
-
|
|
6149
|
-
//#region src/rules/ci/qa-ci-001-continue-on-error.ts
|
|
6108
|
+
// src/rules/ci/qa-ci-001-continue-on-error.ts
|
|
6150
6109
|
/**
|
|
6151
6110
|
* QA-CI-001 — `continue-on-error: true` masks a failing job.
|
|
6152
6111
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -6374,8 +6333,7 @@ function describeAzureStep(step) {
|
|
|
6374
6333
|
if (step.task) return step.task;
|
|
6375
6334
|
return "step";
|
|
6376
6335
|
}
|
|
6377
|
-
|
|
6378
|
-
//#region src/rules/ci/qa-ci-002-swallowed-exit.ts
|
|
6336
|
+
// src/rules/ci/qa-ci-002-swallowed-exit.ts
|
|
6379
6337
|
/**
|
|
6380
6338
|
* QA-CI-002 — `|| true` swallows a command's exit code.
|
|
6381
6339
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -6430,8 +6388,7 @@ const swallowedExitCode = defineRule({
|
|
|
6430
6388
|
return findings;
|
|
6431
6389
|
}
|
|
6432
6390
|
});
|
|
6433
|
-
|
|
6434
|
-
//#region src/rules/ci/qa-ci-007-retry-masking.ts
|
|
6391
|
+
// src/rules/ci/qa-ci-007-retry-masking.ts
|
|
6435
6392
|
/**
|
|
6436
6393
|
* QA-CI-007 — Retry masking test failures.
|
|
6437
6394
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -6582,8 +6539,7 @@ function azureArm$1(ctx, doc, findings) {
|
|
|
6582
6539
|
});
|
|
6583
6540
|
}
|
|
6584
6541
|
}
|
|
6585
|
-
|
|
6586
|
-
//#region src/rules/ci/qa-ci-005-report-never-generated.ts
|
|
6542
|
+
// src/rules/ci/qa-ci-005-report-never-generated.ts
|
|
6587
6543
|
/**
|
|
6588
6544
|
* QA-CI-005 — Report consumed but never generated.
|
|
6589
6545
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -6741,8 +6697,7 @@ function findJobConsumerLine(text, jobName, consumerRe) {
|
|
|
6741
6697
|
const anywhere = new RegExp(consumerRe.source, "i").exec(text);
|
|
6742
6698
|
return anywhere ? lineAt(text, anywhere.index) : 1;
|
|
6743
6699
|
}
|
|
6744
|
-
|
|
6745
|
-
//#region src/rules/ci/jenkins-gates.ts
|
|
6700
|
+
// src/rules/ci/jenkins-gates.ts
|
|
6746
6701
|
/**
|
|
6747
6702
|
* Jenkinsfile block scanner for the QA-CI Jenkins arms (product-gap
|
|
6748
6703
|
* master plan P3c). Jenkinsfiles are Groovy text — "no new language
|
|
@@ -6988,8 +6943,7 @@ function textIsVerificationGate(text) {
|
|
|
6988
6943
|
function lineOfOffset(text, offset) {
|
|
6989
6944
|
return lineAt(text, offset);
|
|
6990
6945
|
}
|
|
6991
|
-
|
|
6992
|
-
//#region src/rules/ci/qa-ci-008-always-success.ts
|
|
6946
|
+
// src/rules/ci/qa-ci-008-always-success.ts
|
|
6993
6947
|
/**
|
|
6994
6948
|
* QA-CI-008 — Always-success step at the end of a job.
|
|
6995
6949
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7163,8 +7117,7 @@ function jenkinsArm$1(ctx, findings) {
|
|
|
7163
7117
|
});
|
|
7164
7118
|
}
|
|
7165
7119
|
}
|
|
7166
|
-
|
|
7167
|
-
//#region src/rules/ci/qa-ci-009-exit-code.ts
|
|
7120
|
+
// src/rules/ci/qa-ci-009-exit-code.ts
|
|
7168
7121
|
/**
|
|
7169
7122
|
* QA-CI-009 — Test command does not propagate exit code.
|
|
7170
7123
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7299,8 +7252,7 @@ function jenkinsArm(ctx, findings) {
|
|
|
7299
7252
|
});
|
|
7300
7253
|
}
|
|
7301
7254
|
}
|
|
7302
|
-
|
|
7303
|
-
//#region src/rules/ci/qa-ci-010-non-blocking.ts
|
|
7255
|
+
// src/rules/ci/qa-ci-010-non-blocking.ts
|
|
7304
7256
|
/**
|
|
7305
7257
|
* QA-CI-010 — Required test job is non-blocking for branch protection.
|
|
7306
7258
|
* Severity: error · Confidence: medium · deterministic-defect
|
|
@@ -7383,8 +7335,7 @@ function findLine(text, re) {
|
|
|
7383
7335
|
function escapeRe(s) {
|
|
7384
7336
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7385
7337
|
}
|
|
7386
|
-
|
|
7387
|
-
//#region src/rules/ci/qa-ci-013-can-never-fail.ts
|
|
7338
|
+
// src/rules/ci/qa-ci-013-can-never-fail.ts
|
|
7388
7339
|
/**
|
|
7389
7340
|
* QA-CI-013 — Verification stage/job conditioned so it can never fail the
|
|
7390
7341
|
* pipeline (product-gap master plan P3b).
|
|
@@ -7488,8 +7439,7 @@ const canNeverFailGate = defineRule({
|
|
|
7488
7439
|
function jobHasGate(job) {
|
|
7489
7440
|
return (job.steps ?? []).some((s) => s !== void 0 && stepIsVerificationGate$1(s));
|
|
7490
7441
|
}
|
|
7491
|
-
|
|
7492
|
-
//#region src/rules/ci/qa-ci-014-swallowed-verification.ts
|
|
7442
|
+
// src/rules/ci/qa-ci-014-swallowed-verification.ts
|
|
7493
7443
|
/**
|
|
7494
7444
|
* QA-CI-014 — try/catch swallowing a verification-stage failure without
|
|
7495
7445
|
* rethrow or failure-marking (product-gap master plan P3c).
|
|
@@ -7546,8 +7496,7 @@ const swallowedVerificationFailure = defineRule({
|
|
|
7546
7496
|
return findings;
|
|
7547
7497
|
}
|
|
7548
7498
|
});
|
|
7549
|
-
|
|
7550
|
-
//#region src/rules/python/qa-py-003-no-assertions.ts
|
|
7499
|
+
// src/rules/python/qa-py-003-no-assertions.ts
|
|
7551
7500
|
/**
|
|
7552
7501
|
* QA-PY-003 — Test function with no assertions.
|
|
7553
7502
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7668,8 +7617,7 @@ function extractBlock$1(text, afterColon) {
|
|
|
7668
7617
|
const lineEnd = rest.indexOf("\n", firstIdx);
|
|
7669
7618
|
return lineEnd === -1 ? rest : rest.slice(0, lineEnd);
|
|
7670
7619
|
}
|
|
7671
|
-
|
|
7672
|
-
//#region src/rules/python/qa-py-005-hard-sleep.ts
|
|
7620
|
+
// src/rules/python/qa-py-005-hard-sleep.ts
|
|
7673
7621
|
/**
|
|
7674
7622
|
* QA-PY-005 — time.sleep() in tests.
|
|
7675
7623
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -7716,8 +7664,7 @@ const pyHardSleep = defineRule({
|
|
|
7716
7664
|
return findings;
|
|
7717
7665
|
}
|
|
7718
7666
|
});
|
|
7719
|
-
|
|
7720
|
-
//#region src/rules/python/qa-py-002-skipped-test.ts
|
|
7667
|
+
// src/rules/python/qa-py-002-skipped-test.ts
|
|
7721
7668
|
/**
|
|
7722
7669
|
* QA-PY-002 — Skipped test (`@pytest.mark.skip`, `xfail` non-strict).
|
|
7723
7670
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -7777,8 +7724,7 @@ const pySkippedTest = defineRule({
|
|
|
7777
7724
|
return findings;
|
|
7778
7725
|
}
|
|
7779
7726
|
});
|
|
7780
|
-
|
|
7781
|
-
//#region src/rules/python/qa-py-012-tautological.ts
|
|
7727
|
+
// src/rules/python/qa-py-012-tautological.ts
|
|
7782
7728
|
/**
|
|
7783
7729
|
* QA-PY-012 — Tautological assertion (`assert True`, `assert x == x`).
|
|
7784
7730
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7833,8 +7779,7 @@ const pyTautological = defineRule({
|
|
|
7833
7779
|
return findings;
|
|
7834
7780
|
}
|
|
7835
7781
|
});
|
|
7836
|
-
|
|
7837
|
-
//#region src/rules/python/qa-py-001-focused-test.ts
|
|
7782
|
+
// src/rules/python/qa-py-001-focused-test.ts
|
|
7838
7783
|
/**
|
|
7839
7784
|
* QA-PY-001 — Focused test committed.
|
|
7840
7785
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7888,8 +7833,7 @@ const pyFocusedTest = defineRule({
|
|
|
7888
7833
|
return findings;
|
|
7889
7834
|
}
|
|
7890
7835
|
});
|
|
7891
|
-
|
|
7892
|
-
//#region src/engine/jv-cs-ast.ts
|
|
7836
|
+
// src/engine/jv-cs-ast.ts
|
|
7893
7837
|
/** Narrow the loose `ast` seam back to a tree-sitter Tree. */
|
|
7894
7838
|
function getTreeSitterTree(ast) {
|
|
7895
7839
|
if (!(ast instanceof Object) || !("rootNode" in ast)) return void 0;
|
|
@@ -8093,8 +8037,7 @@ function isHelperIdiom(name) {
|
|
|
8093
8037
|
}
|
|
8094
8038
|
return false;
|
|
8095
8039
|
}
|
|
8096
|
-
|
|
8097
|
-
//#region src/engine/python-ast.ts
|
|
8040
|
+
// src/engine/python-ast.ts
|
|
8098
8041
|
function getPythonTree(ast) {
|
|
8099
8042
|
return getTreeSitterTree(ast);
|
|
8100
8043
|
}
|
|
@@ -8179,8 +8122,7 @@ function pythonWithRaisesBlocks(tree) {
|
|
|
8179
8122
|
});
|
|
8180
8123
|
return out;
|
|
8181
8124
|
}
|
|
8182
|
-
|
|
8183
|
-
//#region src/rules/python/qa-py-007-raises-without-match.ts
|
|
8125
|
+
// src/rules/python/qa-py-007-raises-without-match.ts
|
|
8184
8126
|
/**
|
|
8185
8127
|
* QA-PY-007 — pytest.raises without match.
|
|
8186
8128
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -8307,8 +8249,7 @@ function astArm(ctx, tree) {
|
|
|
8307
8249
|
}
|
|
8308
8250
|
return findings;
|
|
8309
8251
|
}
|
|
8310
|
-
|
|
8311
|
-
//#region src/rules/python/qa-py-009-commented-out.ts
|
|
8252
|
+
// src/rules/python/qa-py-009-commented-out.ts
|
|
8312
8253
|
/**
|
|
8313
8254
|
* QA-PY-009 — Commented-out tests.
|
|
8314
8255
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -8355,8 +8296,7 @@ const pyCommentedOutTest = defineRule({
|
|
|
8355
8296
|
return findings;
|
|
8356
8297
|
}
|
|
8357
8298
|
});
|
|
8358
|
-
|
|
8359
|
-
//#region src/rules/python/qa-py-004-bare-truthiness.ts
|
|
8299
|
+
// src/rules/python/qa-py-004-bare-truthiness.ts
|
|
8360
8300
|
/**
|
|
8361
8301
|
* QA-PY-004 — Bare truthiness assert on complex object.
|
|
8362
8302
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -8435,8 +8375,7 @@ const pyBareTruthinessAssert = defineRule({
|
|
|
8435
8375
|
return findings;
|
|
8436
8376
|
}
|
|
8437
8377
|
});
|
|
8438
|
-
|
|
8439
|
-
//#region src/rules/python/qa-py-011-mutable-fixture.ts
|
|
8378
|
+
// src/rules/python/qa-py-011-mutable-fixture.ts
|
|
8440
8379
|
/**
|
|
8441
8380
|
* QA-PY-011 — Mutable fixture shared across tests.
|
|
8442
8381
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -8487,8 +8426,7 @@ const pyMutableFixture = defineRule({
|
|
|
8487
8426
|
return findings;
|
|
8488
8427
|
}
|
|
8489
8428
|
});
|
|
8490
|
-
|
|
8491
|
-
//#region src/rules/playwright/qa-pw-101-wait-for-timeout.ts
|
|
8429
|
+
// src/rules/playwright/qa-pw-101-wait-for-timeout.ts
|
|
8492
8430
|
/**
|
|
8493
8431
|
* QA-PW-101 — waitForTimeout() anywhere.
|
|
8494
8432
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -8535,8 +8473,7 @@ const pwWaitForTimeout = defineRule({
|
|
|
8535
8473
|
return findings;
|
|
8536
8474
|
}
|
|
8537
8475
|
});
|
|
8538
|
-
|
|
8539
|
-
//#region src/rules/playwright/qa-pw-102-wait-load.ts
|
|
8476
|
+
// src/rules/playwright/qa-pw-102-wait-load.ts
|
|
8540
8477
|
/**
|
|
8541
8478
|
* QA-PW-102 — waitForEvent('load') / waitForLoadState('load') instead of
|
|
8542
8479
|
* web-first assertions.
|
|
@@ -8590,8 +8527,7 @@ const pwWaitForLoadEvent = defineRule({
|
|
|
8590
8527
|
return findings;
|
|
8591
8528
|
}
|
|
8592
8529
|
});
|
|
8593
|
-
|
|
8594
|
-
//#region src/rules/playwright/qa-pw-113-deep-frames.ts
|
|
8530
|
+
// src/rules/playwright/qa-pw-113-deep-frames.ts
|
|
8595
8531
|
/**
|
|
8596
8532
|
* QA-PW-113 — frameLocator chains > 2 deep.
|
|
8597
8533
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -8641,8 +8577,7 @@ const pwDeepFrameLocator = defineRule({
|
|
|
8641
8577
|
function count(fragment) {
|
|
8642
8578
|
return fragment.match(/frameLocator\s*\(/g).length;
|
|
8643
8579
|
}
|
|
8644
|
-
|
|
8645
|
-
//#region src/rules/playwright/qa-pw-117-serial.ts
|
|
8580
|
+
// src/rules/playwright/qa-pw-117-serial.ts
|
|
8646
8581
|
/**
|
|
8647
8582
|
* QA-PW-117 — test.describe.serial without justification comment.
|
|
8648
8583
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -8696,8 +8631,7 @@ const pwSerialNoJustification = defineRule({
|
|
|
8696
8631
|
return findings;
|
|
8697
8632
|
}
|
|
8698
8633
|
});
|
|
8699
|
-
|
|
8700
|
-
//#region src/rules/playwright/qa-pw-121-config-retries.ts
|
|
8634
|
+
// src/rules/playwright/qa-pw-121-config-retries.ts
|
|
8701
8635
|
/**
|
|
8702
8636
|
* QA-PW-121 — playwright.config: retries >= 3 or workers = 0.
|
|
8703
8637
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -8762,8 +8696,7 @@ const pwConfigRetryAbuse = defineRule({
|
|
|
8762
8696
|
return findings;
|
|
8763
8697
|
}
|
|
8764
8698
|
});
|
|
8765
|
-
|
|
8766
|
-
//#region src/rules/playwright/qa-pw-122-no-trace.ts
|
|
8699
|
+
// src/rules/playwright/qa-pw-122-no-trace.ts
|
|
8767
8700
|
/**
|
|
8768
8701
|
* QA-PW-122 — No trace capture on retry in playwright.config.
|
|
8769
8702
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -8812,8 +8745,7 @@ const pwNoTraceOnRetry = defineRule({
|
|
|
8812
8745
|
return findings;
|
|
8813
8746
|
}
|
|
8814
8747
|
});
|
|
8815
|
-
|
|
8816
|
-
//#region src/rules/playwright/qa-pw-124-project-split.ts
|
|
8748
|
+
// src/rules/playwright/qa-pw-124-project-split.ts
|
|
8817
8749
|
/**
|
|
8818
8750
|
* QA-PW-124 — Projects defined but no smoke/regression split.
|
|
8819
8751
|
* Severity: info · Confidence: high · deterministic-defect
|
|
@@ -8875,8 +8807,7 @@ const pwNoProjectSplit = defineRule({
|
|
|
8875
8807
|
return findings;
|
|
8876
8808
|
}
|
|
8877
8809
|
});
|
|
8878
|
-
|
|
8879
|
-
//#region src/rules/playwright/qa-pw-104-trial-click.ts
|
|
8810
|
+
// src/rules/playwright/qa-pw-104-trial-click.ts
|
|
8880
8811
|
/**
|
|
8881
8812
|
* QA-PW-104 — `{ trial: true }` misuse — check-then-act race.
|
|
8882
8813
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -8922,8 +8853,7 @@ const pwTrialMisuse = defineRule({
|
|
|
8922
8853
|
return findings;
|
|
8923
8854
|
}
|
|
8924
8855
|
});
|
|
8925
|
-
|
|
8926
|
-
//#region src/rules/playwright/qa-pw-116-storage-state.ts
|
|
8856
|
+
// src/rules/playwright/qa-pw-116-storage-state.ts
|
|
8927
8857
|
/**
|
|
8928
8858
|
* QA-PW-116 — storageState reuse without expiry strategy.
|
|
8929
8859
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -8969,8 +8899,7 @@ const pwStorageStateNoExpiry = defineRule({
|
|
|
8969
8899
|
return findings;
|
|
8970
8900
|
}
|
|
8971
8901
|
});
|
|
8972
|
-
|
|
8973
|
-
//#region src/rules/playwright/qa-pw-125-global-setup.ts
|
|
8902
|
+
// src/rules/playwright/qa-pw-125-global-setup.ts
|
|
8974
8903
|
/**
|
|
8975
8904
|
* QA-PW-125 — globalSetup mutating shared external state.
|
|
8976
8905
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9028,8 +8957,7 @@ const pwGlobalSetupSharedState = defineRule({
|
|
|
9028
8957
|
return findings;
|
|
9029
8958
|
}
|
|
9030
8959
|
});
|
|
9031
|
-
|
|
9032
|
-
//#region src/rules/playwright/qa-pw-115-shared-page.ts
|
|
8960
|
+
// src/rules/playwright/qa-pw-115-shared-page.ts
|
|
9033
8961
|
/**
|
|
9034
8962
|
* QA-PW-115 — Shared `page` across parallel tests without isolation.
|
|
9035
8963
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9077,8 +9005,7 @@ const pwSharedPage = defineRule({
|
|
|
9077
9005
|
return findings;
|
|
9078
9006
|
}
|
|
9079
9007
|
});
|
|
9080
|
-
|
|
9081
|
-
//#region src/rules/playwright/qa-pw-140.ts
|
|
9008
|
+
// src/rules/playwright/qa-pw-140.ts
|
|
9082
9009
|
/**
|
|
9083
9010
|
* QA-PW-140 — Screenshot without maxDiffPixelRatio.
|
|
9084
9011
|
*
|
|
@@ -9152,8 +9079,7 @@ function matchParen(text, open) {
|
|
|
9152
9079
|
}
|
|
9153
9080
|
return -1;
|
|
9154
9081
|
}
|
|
9155
|
-
|
|
9156
|
-
//#region src/rules/playwright/qa-pw-123-hardcoded-url.ts
|
|
9082
|
+
// src/rules/playwright/qa-pw-123-hardcoded-url.ts
|
|
9157
9083
|
/**
|
|
9158
9084
|
* QA-PW-123 — Hardcoded baseURL in specs.
|
|
9159
9085
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9201,8 +9127,7 @@ const hardcodedBaseUrl = defineRule({
|
|
|
9201
9127
|
return findings;
|
|
9202
9128
|
}
|
|
9203
9129
|
});
|
|
9204
|
-
|
|
9205
|
-
//#region src/rules/quality/qa-env-001-env-coupling.ts
|
|
9130
|
+
// src/rules/quality/qa-env-001-env-coupling.ts
|
|
9206
9131
|
/**
|
|
9207
9132
|
* QA-ENV-001 — "Works On My Machine" detector core (Tier 5 #24).
|
|
9208
9133
|
* Environment coupling in test files: fixed ports, OS paths,
|
|
@@ -9269,8 +9194,7 @@ const envCoupling = defineRule({
|
|
|
9269
9194
|
return findings;
|
|
9270
9195
|
}
|
|
9271
9196
|
});
|
|
9272
|
-
|
|
9273
|
-
//#region src/rules/playwright/qa-pw-141-retry-no-triage.ts
|
|
9197
|
+
// src/rules/playwright/qa-pw-141-retry-no-triage.ts
|
|
9274
9198
|
/**
|
|
9275
9199
|
* QA-PW-141 — playwright.config retries set high enough to mask flakes.
|
|
9276
9200
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9327,8 +9251,7 @@ const pwRetryMaskingNoForensics = defineRule({
|
|
|
9327
9251
|
return findings;
|
|
9328
9252
|
}
|
|
9329
9253
|
});
|
|
9330
|
-
|
|
9331
|
-
//#region src/rules/playwright/qa-pw-142-blanket-route.ts
|
|
9254
|
+
// src/rules/playwright/qa-pw-142-blanket-route.ts
|
|
9332
9255
|
/**
|
|
9333
9256
|
* QA-PW-142 — page.route() blanket-mocking instead of scoped intercepts.
|
|
9334
9257
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9383,8 +9306,7 @@ const pwBlanketRouteMock = defineRule({
|
|
|
9383
9306
|
return findings;
|
|
9384
9307
|
}
|
|
9385
9308
|
});
|
|
9386
|
-
|
|
9387
|
-
//#region src/rules/playwright/qa-pw-143-no-artifacts.ts
|
|
9309
|
+
// src/rules/playwright/qa-pw-143-no-artifacts.ts
|
|
9388
9310
|
/**
|
|
9389
9311
|
* QA-PW-143 — No screenshot/video artifact capture on failure.
|
|
9390
9312
|
* Severity: info · Confidence: high · deterministic-defect
|
|
@@ -9437,8 +9359,7 @@ const pwNoFailureArtifacts = defineRule({
|
|
|
9437
9359
|
return findings;
|
|
9438
9360
|
}
|
|
9439
9361
|
});
|
|
9440
|
-
|
|
9441
|
-
//#region src/rules/playwright/qa-pw-144-single-browser.ts
|
|
9362
|
+
// src/rules/playwright/qa-pw-144-single-browser.ts
|
|
9442
9363
|
/**
|
|
9443
9364
|
* QA-PW-144 — Single-browser project matrix (no cross-browser coverage).
|
|
9444
9365
|
* Severity: info · Confidence: high · deterministic-defect
|
|
@@ -9500,8 +9421,7 @@ const pwSingleBrowserMatrix = defineRule({
|
|
|
9500
9421
|
return findings;
|
|
9501
9422
|
}
|
|
9502
9423
|
});
|
|
9503
|
-
|
|
9504
|
-
//#region src/rules/playwright/qa-pw-146-css-locator.ts
|
|
9424
|
+
// src/rules/playwright/qa-pw-146-css-locator.ts
|
|
9505
9425
|
/**
|
|
9506
9426
|
* QA-PW-146 — CSS/XPath string selector where Playwright's locator
|
|
9507
9427
|
* standard expects a normalized locator (plan §17.3).
|
|
@@ -9580,8 +9500,7 @@ const pwLocatorNormalize = defineRule({
|
|
|
9580
9500
|
return findings;
|
|
9581
9501
|
}
|
|
9582
9502
|
});
|
|
9583
|
-
|
|
9584
|
-
//#region src/rules/playwright/qa-pw-147-codegen-artifact.ts
|
|
9503
|
+
// src/rules/playwright/qa-pw-147-codegen-artifact.ts
|
|
9585
9504
|
/**
|
|
9586
9505
|
* QA-PW-147 — Playwright codegen artifact left unedited (plan §17.1).
|
|
9587
9506
|
* Severity: info · Confidence: medium · observation
|
|
@@ -9640,8 +9559,7 @@ const pwCodegenArtifact = defineRule({
|
|
|
9640
9559
|
return findings;
|
|
9641
9560
|
}
|
|
9642
9561
|
});
|
|
9643
|
-
|
|
9644
|
-
//#region src/rules/python/qa-py-101-sync-async-mix.ts
|
|
9562
|
+
// src/rules/python/qa-py-101-sync-async-mix.ts
|
|
9645
9563
|
/**
|
|
9646
9564
|
* QA-PY-101 — Playwright-Python: sync API in async test (or vice versa).
|
|
9647
9565
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9690,8 +9608,7 @@ const pyPwSyncAsyncMix = defineRule({
|
|
|
9690
9608
|
return findings;
|
|
9691
9609
|
}
|
|
9692
9610
|
});
|
|
9693
|
-
|
|
9694
|
-
//#region src/rules/python/qa-py-103-wait-for-timeout.ts
|
|
9611
|
+
// src/rules/python/qa-py-103-wait-for-timeout.ts
|
|
9695
9612
|
/**
|
|
9696
9613
|
* QA-PY-103 — Playwright-Python: wait_for_timeout() as synchronization.
|
|
9697
9614
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9738,8 +9655,7 @@ const pyPwWaitForTimeout = defineRule({
|
|
|
9738
9655
|
return findings;
|
|
9739
9656
|
}
|
|
9740
9657
|
});
|
|
9741
|
-
|
|
9742
|
-
//#region src/rules/python/qa-py-105-pw-no-assertions.ts
|
|
9658
|
+
// src/rules/python/qa-py-105-pw-no-assertions.ts
|
|
9743
9659
|
/**
|
|
9744
9660
|
* QA-PY-105 — Playwright-Python: no assertion in Playwright test.
|
|
9745
9661
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -9819,8 +9735,7 @@ function extractBlock(text, afterColon) {
|
|
|
9819
9735
|
const lineEnd = rest.indexOf("\n", firstContent.index);
|
|
9820
9736
|
return lineEnd === -1 ? rest : rest.slice(0, lineEnd);
|
|
9821
9737
|
}
|
|
9822
|
-
|
|
9823
|
-
//#region src/rules/java/qa-jv-101-disabled-test.ts
|
|
9738
|
+
// src/rules/java/qa-jv-101-disabled-test.ts
|
|
9824
9739
|
/**
|
|
9825
9740
|
* QA-JV-101 — @Disabled test without justification.
|
|
9826
9741
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9874,8 +9789,7 @@ const jvDisabledTest = defineRule({
|
|
|
9874
9789
|
return findings;
|
|
9875
9790
|
}
|
|
9876
9791
|
});
|
|
9877
|
-
|
|
9878
|
-
//#region src/rules/java/qa-jv-103-no-assertions.ts
|
|
9792
|
+
// src/rules/java/qa-jv-103-no-assertions.ts
|
|
9879
9793
|
/**
|
|
9880
9794
|
* QA-JV-103 — Test method without assertions.
|
|
9881
9795
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -10032,8 +9946,7 @@ function matchBrace$1(text, open) {
|
|
|
10032
9946
|
}
|
|
10033
9947
|
return -1;
|
|
10034
9948
|
}
|
|
10035
|
-
|
|
10036
|
-
//#region src/engine/qa-model.ts
|
|
9949
|
+
// src/engine/qa-model.ts
|
|
10037
9950
|
/**
|
|
10038
9951
|
* Extract the semantic model for one parsed file. Resolves undefined
|
|
10039
9952
|
* when the language has no extractor or the parse stage produced no
|
|
@@ -10576,8 +10489,7 @@ function extractPythonModel(file) {
|
|
|
10576
10489
|
nodes
|
|
10577
10490
|
};
|
|
10578
10491
|
}
|
|
10579
|
-
|
|
10580
|
-
//#region src/rules/java/qa-jv-105-wait-for-timeout.ts
|
|
10492
|
+
// src/rules/java/qa-jv-105-wait-for-timeout.ts
|
|
10581
10493
|
/**
|
|
10582
10494
|
* QA-JV-105 — waitForTimeout / hardcoded navigation waits.
|
|
10583
10495
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -10662,8 +10574,7 @@ const jvWaitForTimeout = defineRule({
|
|
|
10662
10574
|
return findings;
|
|
10663
10575
|
}
|
|
10664
10576
|
});
|
|
10665
|
-
|
|
10666
|
-
//#region src/rules/csharp/qa-cs-101-skipped-test.ts
|
|
10577
|
+
// src/rules/csharp/qa-cs-101-skipped-test.ts
|
|
10667
10578
|
/**
|
|
10668
10579
|
* QA-CS-101 — [Ignore]/[Skip] test without justification.
|
|
10669
10580
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -10727,8 +10638,7 @@ const csSkippedTest = defineRule({
|
|
|
10727
10638
|
return findings;
|
|
10728
10639
|
}
|
|
10729
10640
|
});
|
|
10730
|
-
|
|
10731
|
-
//#region src/rules/csharp/qa-cs-103-no-assertions.ts
|
|
10641
|
+
// src/rules/csharp/qa-cs-103-no-assertions.ts
|
|
10732
10642
|
/**
|
|
10733
10643
|
* QA-CS-103 — Test method without assertions.
|
|
10734
10644
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -10762,7 +10672,6 @@ const csSkippedTest = defineRule({
|
|
|
10762
10672
|
* Deep-diffing those requires symbol semantics Mjölnir deliberately does
|
|
10763
10673
|
* not promise for C# (plan §13.4).
|
|
10764
10674
|
*/
|
|
10765
|
-
/** Helper/verification idiom: verify/check/assert-prefixed names. */
|
|
10766
10675
|
const WHY$5 = "Without an assertion the test can only fail by throwing — it cannot detect behavioral regressions.";
|
|
10767
10676
|
const FIX$4 = "Add an assertion on the expected outcome, or remove the test.";
|
|
10768
10677
|
function makeFinding(path, position, name) {
|
|
@@ -10926,8 +10835,7 @@ function matchBrace(text, open) {
|
|
|
10926
10835
|
}
|
|
10927
10836
|
return -1;
|
|
10928
10837
|
}
|
|
10929
|
-
|
|
10930
|
-
//#region src/rules/csharp/qa-cs-105-wait-for-timeout.ts
|
|
10838
|
+
// src/rules/csharp/qa-cs-105-wait-for-timeout.ts
|
|
10931
10839
|
/**
|
|
10932
10840
|
* QA-CS-105 — Playwright-.NET: WaitForTimeoutAsync hard sleep.
|
|
10933
10841
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -11015,8 +10923,7 @@ const csWaitForTimeout = defineRule({
|
|
|
11015
10923
|
return findings;
|
|
11016
10924
|
}
|
|
11017
10925
|
});
|
|
11018
|
-
|
|
11019
|
-
//#region src/rules/cypress/qa-cyp-001-cy-wait.ts
|
|
10926
|
+
// src/rules/cypress/qa-cyp-001-cy-wait.ts
|
|
11020
10927
|
/**
|
|
11021
10928
|
* QA-CYP-001 — Fixed `cy.wait(n)` (hard-coded wait).
|
|
11022
10929
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -11095,8 +11002,7 @@ function isCypressFile(ctx) {
|
|
|
11095
11002
|
if (/\.cy\.[jt]sx?$/.test(ctx.path)) return true;
|
|
11096
11003
|
return /\bcy\s*\./.test(ctx.codeText ?? ctx.text);
|
|
11097
11004
|
}
|
|
11098
|
-
|
|
11099
|
-
//#region src/rules/cypress/qa-cyp-002-focused-test.ts
|
|
11005
|
+
// src/rules/cypress/qa-cyp-002-focused-test.ts
|
|
11100
11006
|
/**
|
|
11101
11007
|
* QA-CYP-002 — Focused test (`.only`).
|
|
11102
11008
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -11159,8 +11065,7 @@ const cypFocusedTest = defineRule({
|
|
|
11159
11065
|
return findings;
|
|
11160
11066
|
}
|
|
11161
11067
|
});
|
|
11162
|
-
|
|
11163
|
-
//#region src/rules/cypress/qa-cyp-003-config-security.ts
|
|
11068
|
+
// src/rules/cypress/qa-cyp-003-config-security.ts
|
|
11164
11069
|
/**
|
|
11165
11070
|
* QA-CYP-003 — Cypress config disables `chromeWebSecurity`.
|
|
11166
11071
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -11230,8 +11135,7 @@ const cypConfigSecurity = defineRule({
|
|
|
11230
11135
|
return findings;
|
|
11231
11136
|
}
|
|
11232
11137
|
});
|
|
11233
|
-
|
|
11234
|
-
//#region src/rules/selenium/qa-se-sleep-lookup.ts
|
|
11138
|
+
// src/rules/selenium/qa-se-sleep-lookup.ts
|
|
11235
11139
|
/**
|
|
11236
11140
|
* Selenium sleep-then-interact rules (Verification Trust Evolution Plan
|
|
11237
11141
|
* §15.3 — "Selenium implicit-wait abuse / missing WebDriverWait",
|
|
@@ -11382,8 +11286,7 @@ const sePythonSleepLookup = defineRule({
|
|
|
11382
11286
|
return sleepBeforeLookup(ctx, PY_SLEEP_RE, PY_LOOKUP_RE, "QA-SE-003");
|
|
11383
11287
|
}
|
|
11384
11288
|
});
|
|
11385
|
-
|
|
11386
|
-
//#region src/rules/shared/family.ts
|
|
11289
|
+
// src/rules/shared/family.ts
|
|
11387
11290
|
/**
|
|
11388
11291
|
* Cross-language pattern rule family generator (Phase 6 — Tempering Plan).
|
|
11389
11292
|
*
|
|
@@ -11449,8 +11352,7 @@ function definePatternFamily(opts) {
|
|
|
11449
11352
|
}
|
|
11450
11353
|
}));
|
|
11451
11354
|
}
|
|
11452
|
-
|
|
11453
|
-
//#region src/rules/families/hard-sleep.ts
|
|
11355
|
+
// src/rules/families/hard-sleep.ts
|
|
11454
11356
|
/**
|
|
11455
11357
|
* Hard-sleep family (Phase 6 — Tempering Plan).
|
|
11456
11358
|
* Java: Thread.sleep(), C#: Thread.Sleep() / Task.Delay()
|
|
@@ -11601,8 +11503,7 @@ const hardSleepFamily = definePatternFamily({
|
|
|
11601
11503
|
fix: "Use `await Assertions.Expect(locator).ToBeVisibleAsync()` or locator.WaitForAsync()."
|
|
11602
11504
|
}]
|
|
11603
11505
|
});
|
|
11604
|
-
|
|
11605
|
-
//#region src/rules/families/network-idle.ts
|
|
11506
|
+
// src/rules/families/network-idle.ts
|
|
11606
11507
|
/**
|
|
11607
11508
|
* Network-idle family (Phase 6 — Tempering Plan).
|
|
11608
11509
|
* All three language variants of waitForLoadState(networkidle).
|
|
@@ -11663,8 +11564,7 @@ const networkIdleFamily = definePatternFamily({
|
|
|
11663
11564
|
}
|
|
11664
11565
|
]
|
|
11665
11566
|
});
|
|
11666
|
-
|
|
11667
|
-
//#region src/rules/families/hardcoded-url.ts
|
|
11567
|
+
// src/rules/families/hardcoded-url.ts
|
|
11668
11568
|
/**
|
|
11669
11569
|
* Hardcoded-URL family (Phase 6 — Tempering Plan).
|
|
11670
11570
|
* Detects absolute URLs in navigation/request calls across languages.
|
|
@@ -11714,8 +11614,7 @@ const hardcodedUrlFamily = definePatternFamily({
|
|
|
11714
11614
|
fix: "Use `--base-url` / `base_url` fixture, or `os.environ[...]`."
|
|
11715
11615
|
}]
|
|
11716
11616
|
});
|
|
11717
|
-
|
|
11718
|
-
//#region src/rules/families/shared-page.ts
|
|
11617
|
+
// src/rules/families/shared-page.ts
|
|
11719
11618
|
/**
|
|
11720
11619
|
* Shared-page family (Phase 6 — Tempering Plan).
|
|
11721
11620
|
* Detects static/module-level Page/Browser instances shared across tests.
|
|
@@ -11776,8 +11675,7 @@ const sharedPageFamily = definePatternFamily({
|
|
|
11776
11675
|
}
|
|
11777
11676
|
]
|
|
11778
11677
|
});
|
|
11779
|
-
|
|
11780
|
-
//#region src/rules/families/brittle-selectors.ts
|
|
11678
|
+
// src/rules/families/brittle-selectors.ts
|
|
11781
11679
|
/**
|
|
11782
11680
|
* Brittle-selectors family (Phase 6 — Tempering Plan).
|
|
11783
11681
|
* Detects XPath/structural CSS selectors across Java, C#, Python.
|
|
@@ -11889,8 +11787,7 @@ const brittleSelectorsFamily = [
|
|
|
11889
11787
|
}
|
|
11890
11788
|
], "Prefer role-based locators (`get_by_role(...)`) or data-testid attributes.")
|
|
11891
11789
|
];
|
|
11892
|
-
|
|
11893
|
-
//#region src/rules/families/retry-masking.ts
|
|
11790
|
+
// src/rules/families/retry-masking.ts
|
|
11894
11791
|
/**
|
|
11895
11792
|
* Retry-masking family (Phase 6 — Tempering Plan).
|
|
11896
11793
|
* Detects retry annotations/attributes that mask flaky tests.
|
|
@@ -12010,8 +11907,7 @@ const retryMaskingFamily = [defineRule({
|
|
|
12010
11907
|
return findings;
|
|
12011
11908
|
}
|
|
12012
11909
|
})];
|
|
12013
|
-
|
|
12014
|
-
//#region src/rules/index.ts
|
|
11910
|
+
// src/rules/index.ts
|
|
12015
11911
|
const RULES = [
|
|
12016
11912
|
focusedTestCommitted,
|
|
12017
11913
|
skippedTest,
|
|
@@ -12092,8 +11988,7 @@ const RULES = [
|
|
|
12092
11988
|
function getRule(id) {
|
|
12093
11989
|
return RULES.find((r) => r.id === id);
|
|
12094
11990
|
}
|
|
12095
|
-
|
|
12096
|
-
//#region src/lib/wilson.ts
|
|
11991
|
+
// src/lib/wilson.ts
|
|
12097
11992
|
/**
|
|
12098
11993
|
* 95% Wilson score interval on a proportion (plan §20.2).
|
|
12099
11994
|
*
|
|
@@ -12203,8 +12098,7 @@ function effectiveTier(rule) {
|
|
|
12203
12098
|
if (rule.tier !== void 0) return rule.tier;
|
|
12204
12099
|
return measurementTier(rule);
|
|
12205
12100
|
}
|
|
12206
|
-
|
|
12207
|
-
//#region src/scorer/scorer.ts
|
|
12101
|
+
// src/scorer/scorer.ts
|
|
12208
12102
|
/**
|
|
12209
12103
|
* Transparent scorer (Product-MVP §8).
|
|
12210
12104
|
* score = 100 − Σ(severity deductions), floor 0.
|
|
@@ -12356,9 +12250,9 @@ function countTestDeclarations(text, codeText) {
|
|
|
12356
12250
|
const source = codeText ?? text;
|
|
12357
12251
|
const patterns = [
|
|
12358
12252
|
/\b(?:it|test)(?:\.\w+)*\s*(?:\(|`)/g,
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12253
|
+
/^[ \t]*(?:async[ \t]+)?def[ \t]+test_\w*/gm,
|
|
12254
|
+
/^[ \t]*@Test\b/gm,
|
|
12255
|
+
/^[ \t]*\[(?:Test|Fact|Theory|TestMethod)\b/gm
|
|
12362
12256
|
];
|
|
12363
12257
|
let count = 0;
|
|
12364
12258
|
for (const re of patterns) {
|
|
@@ -12367,8 +12261,7 @@ function countTestDeclarations(text, codeText) {
|
|
|
12367
12261
|
}
|
|
12368
12262
|
return count;
|
|
12369
12263
|
}
|
|
12370
|
-
|
|
12371
|
-
//#region src/engine/rule-runner.ts
|
|
12264
|
+
// src/engine/rule-runner.ts
|
|
12372
12265
|
/**
|
|
12373
12266
|
* Legacy appliesTo → adapter id mapping.
|
|
12374
12267
|
*
|
|
@@ -12407,8 +12300,7 @@ function asUniversal(rule) {
|
|
|
12407
12300
|
}
|
|
12408
12301
|
};
|
|
12409
12302
|
}
|
|
12410
|
-
|
|
12411
|
-
//#region src/engine/tier-policy.ts
|
|
12303
|
+
// src/engine/tier-policy.ts
|
|
12412
12304
|
/**
|
|
12413
12305
|
* Tier policy (audit H-1): the declared tier is authoritative over a
|
|
12414
12306
|
* rule's severity and evidence claims.
|
|
@@ -12510,8 +12402,7 @@ function enforceTierPolicy(findings, tierByRuleId) {
|
|
|
12510
12402
|
if (EVIDENCE_RANK$1[effective] < EVIDENCE_RANK$1[floor.evidenceLevel]) f.evidenceLevel = floor.evidenceLevel;
|
|
12511
12403
|
}
|
|
12512
12404
|
}
|
|
12513
|
-
|
|
12514
|
-
//#region src/engine/evidence-core.ts
|
|
12405
|
+
// src/engine/evidence-core.ts
|
|
12515
12406
|
/**
|
|
12516
12407
|
* The core's own version — stamped into every record's provenance so a
|
|
12517
12408
|
* consumer can tell which normalization contract produced a record.
|
|
@@ -12578,8 +12469,7 @@ function countEvidence(records) {
|
|
|
12578
12469
|
}
|
|
12579
12470
|
return counts;
|
|
12580
12471
|
}
|
|
12581
|
-
|
|
12582
|
-
//#region src/engine/provenance.ts
|
|
12472
|
+
// src/engine/provenance.ts
|
|
12583
12473
|
const GENERATED_HEADER_RE = /^\s*(?:\/\/|#|\/\*)\s*(?:auto[- ]?generated|generated by|do not edit)/i;
|
|
12584
12474
|
/**
|
|
12585
12475
|
* Codegen fingerprint: the recorder names its first test exactly
|
|
@@ -12620,8 +12510,7 @@ function computeAgenticProfile(files, findings) {
|
|
|
12620
12510
|
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."
|
|
12621
12511
|
};
|
|
12622
12512
|
}
|
|
12623
|
-
|
|
12624
|
-
//#region src/engine/overlap-dedup.ts
|
|
12513
|
+
// src/engine/overlap-dedup.ts
|
|
12625
12514
|
const TIER_RANK = {
|
|
12626
12515
|
core: 0,
|
|
12627
12516
|
extended: 1,
|
|
@@ -12716,8 +12605,7 @@ function dropOrderBefore(a, b, metaByRuleId) {
|
|
|
12716
12605
|
if (ranksBefore(b, a, metaByRuleId)) return true;
|
|
12717
12606
|
return (a.column ?? Number.MAX_SAFE_INTEGER) < (b.column ?? Number.MAX_SAFE_INTEGER);
|
|
12718
12607
|
}
|
|
12719
|
-
|
|
12720
|
-
//#region src/engine/finding-identity.ts
|
|
12608
|
+
// src/engine/finding-identity.ts
|
|
12721
12609
|
/**
|
|
12722
12610
|
* Correlation identity for before/after comparison: ruleId + file +
|
|
12723
12611
|
* message, deliberately EXCLUDING line — a source edit that shifts a
|
|
@@ -12726,8 +12614,7 @@ function dropOrderBefore(a, b, metaByRuleId) {
|
|
|
12726
12614
|
function findingFingerprint(f) {
|
|
12727
12615
|
return `${f.ruleId}\u0000${f.file}\u0000${f.message}`;
|
|
12728
12616
|
}
|
|
12729
|
-
|
|
12730
|
-
//#region src/engine/correlation-engine.ts
|
|
12617
|
+
// src/engine/correlation-engine.ts
|
|
12731
12618
|
const EVIDENCE_RANK = {
|
|
12732
12619
|
E0: 0,
|
|
12733
12620
|
E1: 1,
|
|
@@ -12857,8 +12744,7 @@ function correlateFindings(findings) {
|
|
|
12857
12744
|
});
|
|
12858
12745
|
return conclusions;
|
|
12859
12746
|
}
|
|
12860
|
-
|
|
12861
|
-
//#region src/engine/dependency-graph.ts
|
|
12747
|
+
// src/engine/dependency-graph.ts
|
|
12862
12748
|
/**
|
|
12863
12749
|
* Dependency Graph (ECO-005).
|
|
12864
12750
|
*
|
|
@@ -12995,8 +12881,7 @@ function parseManifest(filePath) {
|
|
|
12995
12881
|
if (filePath.endsWith("pyproject.toml")) return parsePyprojectToml(filePath);
|
|
12996
12882
|
if (filePath.endsWith("pom.xml")) return parsePomXml(filePath);
|
|
12997
12883
|
}
|
|
12998
|
-
|
|
12999
|
-
//#region src/engine/incremental-analysis.ts
|
|
12884
|
+
// src/engine/incremental-analysis.ts
|
|
13000
12885
|
/**
|
|
13001
12886
|
* Semantic inputs that affect the full scan beyond individual file content.
|
|
13002
12887
|
* If any of these changed, an incremental run is NOT safe — a full scan
|
|
@@ -13032,8 +12917,7 @@ function isIncrementalSafe(changedFiles) {
|
|
|
13032
12917
|
reasons
|
|
13033
12918
|
};
|
|
13034
12919
|
}
|
|
13035
|
-
|
|
13036
|
-
//#region src/brand/tokens.ts
|
|
12920
|
+
// src/brand/tokens.ts
|
|
13037
12921
|
/**
|
|
13038
12922
|
* The single source of brand truth.
|
|
13039
12923
|
*
|
|
@@ -13319,8 +13203,7 @@ function deriveScoreState(score) {
|
|
|
13319
13203
|
rune: RUNES.critical
|
|
13320
13204
|
};
|
|
13321
13205
|
}
|
|
13322
|
-
|
|
13323
|
-
//#region src/engine/monorepo-analysis.ts
|
|
13206
|
+
// src/engine/monorepo-analysis.ts
|
|
13324
13207
|
/**
|
|
13325
13208
|
* Package verdict, derived from the one ScoreState band model (BW-104).
|
|
13326
13209
|
* These two constants used to be a private copy of the band boundaries;
|
|
@@ -13427,8 +13310,7 @@ function configurableAggregation(results, config) {
|
|
|
13427
13310
|
strategy: config.weightingStrategy
|
|
13428
13311
|
};
|
|
13429
13312
|
}
|
|
13430
|
-
|
|
13431
|
-
//#region src/lib/fs-atomic.ts
|
|
13313
|
+
// src/lib/fs-atomic.ts
|
|
13432
13314
|
/**
|
|
13433
13315
|
* Atomic file writes (audit S9).
|
|
13434
13316
|
*
|
|
@@ -13511,8 +13393,7 @@ function renameWithWindowsRetry(from, to) {
|
|
|
13511
13393
|
throw err;
|
|
13512
13394
|
}
|
|
13513
13395
|
}
|
|
13514
|
-
|
|
13515
|
-
//#region src/engine/scan-cache.ts
|
|
13396
|
+
// src/engine/scan-cache.ts
|
|
13516
13397
|
/**
|
|
13517
13398
|
* Local incremental scan cache (Beta-to-Stable 1.0 plan, M5.2 / A-2).
|
|
13518
13399
|
*
|
|
@@ -13612,7 +13493,9 @@ function hashDir(dir, hash, depth) {
|
|
|
13612
13493
|
else if (entry.isFile() && /\.tsx?$/.test(entry.name)) try {
|
|
13613
13494
|
hash.update(entry.name);
|
|
13614
13495
|
hash.update(readFileSync(full));
|
|
13615
|
-
} catch {
|
|
13496
|
+
} catch {
|
|
13497
|
+
recordDegradation("rules-tree-file-unreadable");
|
|
13498
|
+
}
|
|
13616
13499
|
}
|
|
13617
13500
|
}
|
|
13618
13501
|
/**
|
|
@@ -13722,8 +13605,7 @@ function createScanCache(root) {
|
|
|
13722
13605
|
}
|
|
13723
13606
|
};
|
|
13724
13607
|
}
|
|
13725
|
-
|
|
13726
|
-
//#region src/reporter/theme.ts
|
|
13608
|
+
// src/reporter/theme.ts
|
|
13727
13609
|
/**
|
|
13728
13610
|
* Norse-forge theme system. Pure string-in → string-out helpers.
|
|
13729
13611
|
* Respects NO_COLOR and non-TTY via `palette(isTTY)` — every renderer
|
|
@@ -13823,8 +13705,7 @@ function rgb([r, g, b]) {
|
|
|
13823
13705
|
function palette(enabled) {
|
|
13824
13706
|
return enabled ? on : off;
|
|
13825
13707
|
}
|
|
13826
|
-
|
|
13827
|
-
//#region src/reporter/ui.ts
|
|
13708
|
+
// src/reporter/ui.ts
|
|
13828
13709
|
/**
|
|
13829
13710
|
* Neutral context for direct calls (tests, library use) and as the
|
|
13830
13711
|
* safe default when a command runner is invoked without wiring: no
|
|
@@ -13849,8 +13730,7 @@ function sectionHeader(title, ui) {
|
|
|
13849
13730
|
const glyph = ui.ascii ? "=" : "▍";
|
|
13850
13731
|
return ` ${ui.p.accent(`${glyph} ${title}`)}`;
|
|
13851
13732
|
}
|
|
13852
|
-
|
|
13853
|
-
//#region src/forensics/classify.ts
|
|
13733
|
+
// src/forensics/classify.ts
|
|
13854
13734
|
/**
|
|
13855
13735
|
* Strong-signal marker tables. Kept deliberately narrow: a marker here
|
|
13856
13736
|
* is a named family fact, not a vibe. Unknown text yields no signal —
|
|
@@ -13970,8 +13850,7 @@ function classifyForensicVerdict(signals) {
|
|
|
13970
13850
|
signals: signalCounts
|
|
13971
13851
|
};
|
|
13972
13852
|
}
|
|
13973
|
-
|
|
13974
|
-
//#region src/forensics/analyze.ts
|
|
13853
|
+
// src/forensics/analyze.ts
|
|
13975
13854
|
const ui$5 = plainContext();
|
|
13976
13855
|
const MAX_RECORDS = 1e5;
|
|
13977
13856
|
function analyze(records, source) {
|
|
@@ -14134,8 +14013,7 @@ function renderFlakyMd(report) {
|
|
|
14134
14013
|
lines.push("");
|
|
14135
14014
|
return lines.join("\n");
|
|
14136
14015
|
}
|
|
14137
|
-
|
|
14138
|
-
//#region src/forensics/evidence-hygiene.ts
|
|
14016
|
+
// src/forensics/evidence-hygiene.ts
|
|
14139
14017
|
/**
|
|
14140
14018
|
* Evidence hygiene — secret redaction + control-char sanitization
|
|
14141
14019
|
* (ENGINE-012).
|
|
@@ -14197,8 +14075,7 @@ function sanitizeErrorText(text, options) {
|
|
|
14197
14075
|
if (result.length > maxLength) result = result.slice(0, maxLength) + `[truncated at ${maxLength} chars]`;
|
|
14198
14076
|
return result;
|
|
14199
14077
|
}
|
|
14200
|
-
|
|
14201
|
-
//#region src/forensics/trace.ts
|
|
14078
|
+
// src/forensics/trace.ts
|
|
14202
14079
|
/**
|
|
14203
14080
|
* Playwright trace ingester (product-gap master plan §9 R5 / WI-17).
|
|
14204
14081
|
*
|
|
@@ -14391,8 +14268,7 @@ function parseTraceArtifact(bytes, artifactName) {
|
|
|
14391
14268
|
const { actions } = parseTraceNdjson(text);
|
|
14392
14269
|
return traceActionsToRecord(actions, artifactName);
|
|
14393
14270
|
}
|
|
14394
|
-
|
|
14395
|
-
//#region src/forensics/parse-jest-json.ts
|
|
14271
|
+
// src/forensics/parse-jest-json.ts
|
|
14396
14272
|
function toStatus$2(raw) {
|
|
14397
14273
|
switch (raw) {
|
|
14398
14274
|
case "passed": return "passed";
|
|
@@ -14452,8 +14328,7 @@ function parseJestJson(json) {
|
|
|
14452
14328
|
}
|
|
14453
14329
|
return out;
|
|
14454
14330
|
}
|
|
14455
|
-
|
|
14456
|
-
//#region src/forensics/parse-junit.ts
|
|
14331
|
+
// src/forensics/parse-junit.ts
|
|
14457
14332
|
const MAX_INPUT$1 = 20971520;
|
|
14458
14333
|
function decodeEntities$1(s) {
|
|
14459
14334
|
return s.replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("'", "'").replaceAll("&", "&");
|
|
@@ -14495,14 +14370,25 @@ function parseJunitXml(xml) {
|
|
|
14495
14370
|
const skipped = /<skipped\b/i.test(inner);
|
|
14496
14371
|
const errors = [];
|
|
14497
14372
|
if (failed) {
|
|
14498
|
-
const
|
|
14499
|
-
|
|
14500
|
-
|
|
14501
|
-
|
|
14373
|
+
const openRe = /<(?:failure|error)\b/gi;
|
|
14374
|
+
const endRe = /<\/(?:failure|error)\s*>/gi;
|
|
14375
|
+
let open;
|
|
14376
|
+
while ((open = openRe.exec(inner)) !== null) {
|
|
14377
|
+
const gt = inner.indexOf(">", open.index);
|
|
14378
|
+
if (gt === -1) break;
|
|
14379
|
+
const attrsStr = inner.slice(open.index, gt);
|
|
14380
|
+
if (attrsStr.endsWith("/")) {
|
|
14381
|
+
openRe.lastIndex = gt + 1;
|
|
14382
|
+
continue;
|
|
14383
|
+
}
|
|
14384
|
+
endRe.lastIndex = gt + 1;
|
|
14385
|
+
const end = endRe.exec(inner);
|
|
14386
|
+
if (end === null) break;
|
|
14502
14387
|
const msgAttr = /\smessage\s*=\s*"([^"]*)"/i.exec(attrsStr)?.[1];
|
|
14503
14388
|
if (msgAttr) errors.push(sanitizeErrorText(decodeEntities$1(msgAttr)));
|
|
14504
|
-
const body =
|
|
14389
|
+
const body = inner.slice(gt + 1, end.index).trim();
|
|
14505
14390
|
if (body && body !== msgAttr) errors.push(sanitizeErrorText(decodeEntities$1(body)));
|
|
14391
|
+
openRe.lastIndex = end.index + end[0].length;
|
|
14506
14392
|
}
|
|
14507
14393
|
const selfCloseRe = /<(?:failure|error)\b([^>]*)\/>/gi;
|
|
14508
14394
|
let scMatch;
|
|
@@ -14526,8 +14412,7 @@ function parseJunitXml(xml) {
|
|
|
14526
14412
|
}
|
|
14527
14413
|
return out;
|
|
14528
14414
|
}
|
|
14529
|
-
|
|
14530
|
-
//#region src/forensics/parse-trx.ts
|
|
14415
|
+
// src/forensics/parse-trx.ts
|
|
14531
14416
|
const MAX_INPUT = 20971520;
|
|
14532
14417
|
function decodeEntities(s) {
|
|
14533
14418
|
return s.replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("'", "'").replaceAll("&", "&");
|
|
@@ -14638,11 +14523,24 @@ function parseTrxXml(xml) {
|
|
|
14638
14523
|
if (id !== void 0 && name !== void 0) byTestId.set(id, name);
|
|
14639
14524
|
}
|
|
14640
14525
|
const records = [];
|
|
14641
|
-
const
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14526
|
+
const startRe = /<UnitTestResult\b/gi;
|
|
14527
|
+
const starts = [];
|
|
14528
|
+
let s;
|
|
14529
|
+
while ((s = startRe.exec(xml)) !== null) {
|
|
14530
|
+
starts.push(s.index);
|
|
14531
|
+
if (starts.length > 2e5) break;
|
|
14532
|
+
}
|
|
14533
|
+
const resultsEnd = xml.search(/<\/Results>/i);
|
|
14534
|
+
for (let i = 0; i < starts.length; i++) {
|
|
14535
|
+
const start = starts[i];
|
|
14536
|
+
const tagEnd = xml.indexOf(">", start);
|
|
14537
|
+
if (tagEnd === -1) break;
|
|
14538
|
+
const selfClosing = xml[tagEnd - 1] === "/";
|
|
14539
|
+
const attrs = xml.slice(start + 15, selfClosing ? tagEnd - 1 : tagEnd);
|
|
14540
|
+
const next = starts[i + 1] ?? -1;
|
|
14541
|
+
let bodyEnd = next === -1 ? xml.length : next;
|
|
14542
|
+
if (resultsEnd > tagEnd && resultsEnd < bodyEnd) bodyEnd = resultsEnd;
|
|
14543
|
+
const inner = selfClosing ? "" : xml.slice(tagEnd + 1, bodyEnd);
|
|
14646
14544
|
const testName = attr(attrs, "testName") ?? attr(attrs, "testId");
|
|
14647
14545
|
if (testName === void 0) continue;
|
|
14648
14546
|
const attempt = {
|
|
@@ -14650,7 +14548,8 @@ function parseTrxXml(xml) {
|
|
|
14650
14548
|
status: mapTrxOutcome(attr(attrs, "outcome")),
|
|
14651
14549
|
durationMs: parseDuration(attr(attrs, "duration"))
|
|
14652
14550
|
};
|
|
14653
|
-
const
|
|
14551
|
+
const messageAt = inner.search(/<Message\b/i);
|
|
14552
|
+
const error = messageAt === -1 ? void 0 : messageOf(inner, messageAt);
|
|
14654
14553
|
records.push({
|
|
14655
14554
|
file: "unknown",
|
|
14656
14555
|
title: testName,
|
|
@@ -14662,8 +14561,7 @@ function parseTrxXml(xml) {
|
|
|
14662
14561
|
}
|
|
14663
14562
|
return records;
|
|
14664
14563
|
}
|
|
14665
|
-
|
|
14666
|
-
//#region src/forensics/parse-playwright-json.ts
|
|
14564
|
+
// src/forensics/parse-playwright-json.ts
|
|
14667
14565
|
const MAX_DEPTH = 32;
|
|
14668
14566
|
function toStatus$1(raw) {
|
|
14669
14567
|
switch (raw) {
|
|
@@ -14728,8 +14626,7 @@ function parsePlaywrightJson(json) {
|
|
|
14728
14626
|
walk(root, 0);
|
|
14729
14627
|
return out;
|
|
14730
14628
|
}
|
|
14731
|
-
|
|
14732
|
-
//#region src/forensics/parse-vitest-json.ts
|
|
14629
|
+
// src/forensics/parse-vitest-json.ts
|
|
14733
14630
|
function toStatus(raw) {
|
|
14734
14631
|
switch (raw) {
|
|
14735
14632
|
case "passed": return "passed";
|
|
@@ -14783,8 +14680,7 @@ function parseVitestJson(json) {
|
|
|
14783
14680
|
}
|
|
14784
14681
|
return out;
|
|
14785
14682
|
}
|
|
14786
|
-
|
|
14787
|
-
//#region src/forensics/parse-har.ts
|
|
14683
|
+
// src/forensics/parse-har.ts
|
|
14788
14684
|
const MAX_ENTRIES = 2e4;
|
|
14789
14685
|
function sanitizeTitleUrl(url) {
|
|
14790
14686
|
try {
|
|
@@ -14869,8 +14765,7 @@ function parseHarJsonDetailed(json) {
|
|
|
14869
14765
|
truncated
|
|
14870
14766
|
};
|
|
14871
14767
|
}
|
|
14872
|
-
|
|
14873
|
-
//#region src/forensics/run.ts
|
|
14768
|
+
// src/forensics/run.ts
|
|
14874
14769
|
/**
|
|
14875
14770
|
* Runtime evidence entry point (R4) — the library behind `explain
|
|
14876
14771
|
* --evidence`. The `forensics` verb is retired in the v6 carve; this module
|
|
@@ -15035,13 +14930,15 @@ function runForensics(target, options = {}) {
|
|
|
15035
14930
|
report.incompleteReasons = [.../* @__PURE__ */ new Set([...report.incompleteReasons, ...incompleteReasons])];
|
|
15036
14931
|
}
|
|
15037
14932
|
let flakyMdPath;
|
|
14933
|
+
let flakyMdWriteError;
|
|
15038
14934
|
if ((options.writeFlakyMd ?? true) && report.totalTests > 0) {
|
|
15039
14935
|
const base = stat.isFile() ? dirname(target) : target;
|
|
15040
14936
|
flakyMdPath = join(base, "FLAKY.md");
|
|
15041
14937
|
try {
|
|
15042
14938
|
writeFileAtomic(flakyMdPath, renderFlakyMd(report));
|
|
15043
|
-
} catch {
|
|
14939
|
+
} catch (e) {
|
|
15044
14940
|
flakyMdPath = void 0;
|
|
14941
|
+
flakyMdWriteError = e instanceof Error ? e.message : String(e);
|
|
15045
14942
|
}
|
|
15046
14943
|
}
|
|
15047
14944
|
return {
|
|
@@ -15049,7 +14946,7 @@ function runForensics(target, options = {}) {
|
|
|
15049
14946
|
output: [
|
|
15050
14947
|
renderLeaderboard(report),
|
|
15051
14948
|
"",
|
|
15052
|
-
flakyMdPath !== void 0 ? renderFlakyMdHint() : report.totalTests === 0 && (report.totalNetworkObservations ?? 0) > 0 && options.writeFlakyMd !== false ? "FLAKY.md was not written — network observations do not establish test outcomes." : renderFlakyMdNotWritten(options)
|
|
14949
|
+
flakyMdPath !== void 0 ? renderFlakyMdHint() : report.totalTests === 0 && (report.totalNetworkObservations ?? 0) > 0 && options.writeFlakyMd !== false ? "FLAKY.md was not written — network observations do not establish test outcomes." : renderFlakyMdNotWritten(options, flakyMdWriteError)
|
|
15053
14950
|
].join("\n"),
|
|
15054
14951
|
flakyMdPath
|
|
15055
14952
|
};
|
|
@@ -15058,9 +14955,10 @@ function renderFlakyMdHint() {
|
|
|
15058
14955
|
return "Full details in FLAKY.md (committed artifact).";
|
|
15059
14956
|
}
|
|
15060
14957
|
/** Honest fallback for every case where FLAKY.md was NOT written (M2). */
|
|
15061
|
-
function renderFlakyMdNotWritten(options) {
|
|
14958
|
+
function renderFlakyMdNotWritten(options, writeError) {
|
|
15062
14959
|
if (options.writeFlakyMd === false) return "FLAKY.md not written (--no-flaky-md).";
|
|
15063
|
-
return
|
|
14960
|
+
if (writeError !== void 0) return `FLAKY.md could not be written: ${writeError}`;
|
|
14961
|
+
return "FLAKY.md was not written — nothing recognized to report.";
|
|
15064
14962
|
}
|
|
15065
14963
|
/**
|
|
15066
14964
|
* R5: the trace artifact's test identity. The per-test trace convention
|
|
@@ -15161,8 +15059,7 @@ function loadSuppressions(root, now = /* @__PURE__ */ new Date()) {
|
|
|
15161
15059
|
entries
|
|
15162
15060
|
};
|
|
15163
15061
|
}
|
|
15164
|
-
|
|
15165
|
-
//#region src/plugins/reserved-prefixes.ts
|
|
15062
|
+
// src/plugins/reserved-prefixes.ts
|
|
15166
15063
|
/**
|
|
15167
15064
|
* Core-owned rule-ID prefixes that external rule sources (npm plugins,
|
|
15168
15065
|
* JS modules, JSON manifests) may never claim — one shared law.
|
|
@@ -15192,8 +15089,7 @@ function isReservedPrefix(ruleId) {
|
|
|
15192
15089
|
const upper = ruleId.toUpperCase();
|
|
15193
15090
|
return RESERVED_PREFIXES.some((p) => upper.startsWith(p));
|
|
15194
15091
|
}
|
|
15195
|
-
|
|
15196
|
-
//#region src/plugins/load.ts
|
|
15092
|
+
// src/plugins/load.ts
|
|
15197
15093
|
/**
|
|
15198
15094
|
* Plugin API (Upgrade-Plan-v3 Phase 6, tier-2 item #1).
|
|
15199
15095
|
*
|
|
@@ -15299,8 +15195,7 @@ function loadPlugins(root, gateOpen = false) {
|
|
|
15299
15195
|
}
|
|
15300
15196
|
return result;
|
|
15301
15197
|
}
|
|
15302
|
-
|
|
15303
|
-
//#region src/plugins/local-rules.ts
|
|
15198
|
+
// src/plugins/local-rules.ts
|
|
15304
15199
|
/**
|
|
15305
15200
|
* Local plugin/rule contract (Verification Trust Evolution Plan §18 —
|
|
15306
15201
|
* Local Extensibility).
|
|
@@ -15612,8 +15507,7 @@ async function loadModuleRules(path, result) {
|
|
|
15612
15507
|
result.rules.push(rule);
|
|
15613
15508
|
}
|
|
15614
15509
|
}
|
|
15615
|
-
|
|
15616
|
-
//#region src/plugins/trust-gate.ts
|
|
15510
|
+
// src/plugins/trust-gate.ts
|
|
15617
15511
|
/**
|
|
15618
15512
|
* Plugin trust gate (audit-remediation-master-plan.md, locked decision C2).
|
|
15619
15513
|
*
|
|
@@ -15649,8 +15543,7 @@ function renderGateNotice(skipped) {
|
|
|
15649
15543
|
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.");
|
|
15650
15544
|
return lines.join("\n");
|
|
15651
15545
|
}
|
|
15652
|
-
|
|
15653
|
-
//#region src/engine/scan-pipeline.ts
|
|
15546
|
+
// src/engine/scan-pipeline.ts
|
|
15654
15547
|
/**
|
|
15655
15548
|
* Canonical scan pipeline (M6 — blueprint §9.2).
|
|
15656
15549
|
*
|
|
@@ -15666,8 +15559,13 @@ function renderGateNotice(skipped) {
|
|
|
15666
15559
|
* module moved. Re-exports in cli.ts keep the historical import surface.
|
|
15667
15560
|
*/
|
|
15668
15561
|
const UNIVERSAL_RULES = RULES.map(asUniversal);
|
|
15669
|
-
|
|
15670
|
-
|
|
15562
|
+
/**
|
|
15563
|
+
* The scan time budget: the default, and the ceiling every surface enforces.
|
|
15564
|
+
* Owned here, where the clamp is applied; `cli.ts` and the MCP server import
|
|
15565
|
+
* these rather than keeping copies that only agree by coincidence.
|
|
15566
|
+
*/
|
|
15567
|
+
const DEFAULT_MAX_DURATION_MS = 6e5;
|
|
15568
|
+
const MAX_DURATION_MS = 36e5;
|
|
15671
15569
|
/** Registered rule IDs — used to warn on unknown severityOverrides keys (M4). */
|
|
15672
15570
|
const KNOWN_RULE_IDS = new Set(RULES.map((r) => r.id));
|
|
15673
15571
|
/**
|
|
@@ -15710,9 +15608,10 @@ async function buildUniversalRules(root, strict, opts = {}) {
|
|
|
15710
15608
|
for (const p of plugins) for (const r of p.rules) if (r.tier) tierByRuleId.set(r.id, r.tier);
|
|
15711
15609
|
for (const r of local.rules) if (r.tier) tierByRuleId.set(r.id, r.tier);
|
|
15712
15610
|
let rules = [...UNIVERSAL_RULES, ...externalRules];
|
|
15713
|
-
const
|
|
15611
|
+
const quarantined = rules.filter((r) => tierByRuleId.get(r.id) === "quarantine").map((r) => r.id).sort();
|
|
15612
|
+
const withheldRuleIds = strict ? [] : quarantined;
|
|
15714
15613
|
if (!strict) rules = rules.filter((r) => tierByRuleId.get(r.id) !== "quarantine");
|
|
15715
|
-
const rulesWithheld =
|
|
15614
|
+
const rulesWithheld = withheldRuleIds.length;
|
|
15716
15615
|
const rulesApplied = rules.length;
|
|
15717
15616
|
const pluginMeta = [...plugins.map((p) => ({
|
|
15718
15617
|
name: p.name,
|
|
@@ -16109,12 +16008,28 @@ function applyPostScanProcessing(findings, workspace, args, hooks, scanRoot, dec
|
|
|
16109
16008
|
const runtimeIncomplete = discoveredReport !== void 0 && discoveredReport.report.analysisComplete !== true;
|
|
16110
16009
|
let forensicVerdicts;
|
|
16111
16010
|
let evidenceRecords = [];
|
|
16112
|
-
if (discoveredReport && discoveredReport.report.analysisComplete === true)
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
|
|
16011
|
+
if (discoveredReport && discoveredReport.report.analysisComplete === true) {
|
|
16012
|
+
const before = findings.map((f) => ({
|
|
16013
|
+
runtimeCorroboration: f.runtimeCorroboration,
|
|
16014
|
+
trustLevel: f.trustLevel
|
|
16015
|
+
}));
|
|
16016
|
+
try {
|
|
16017
|
+
const records = buildEvidenceRecords(discoveredReport.report, discoveredReport.path);
|
|
16018
|
+
stampRuntimeCorroboration(findings, discoveredReport.report, workspace.root);
|
|
16019
|
+
forensicVerdicts = summarizeForensicVerdicts(discoveredReport.report);
|
|
16020
|
+
evidenceRecords = records;
|
|
16021
|
+
} catch {
|
|
16022
|
+
findings.forEach((f, i) => {
|
|
16023
|
+
const prev = before[i];
|
|
16024
|
+
if (prev?.runtimeCorroboration === void 0) delete f.runtimeCorroboration;
|
|
16025
|
+
else f.runtimeCorroboration = prev.runtimeCorroboration;
|
|
16026
|
+
if (prev?.trustLevel === void 0) delete f.trustLevel;
|
|
16027
|
+
else f.trustLevel = prev.trustLevel;
|
|
16028
|
+
});
|
|
16029
|
+
forensicVerdicts = void 0;
|
|
16030
|
+
evidenceRecords = [];
|
|
16031
|
+
recordDegradation("runtime-corroboration-failed");
|
|
16032
|
+
}
|
|
16118
16033
|
}
|
|
16119
16034
|
return {
|
|
16120
16035
|
evidenceRecords,
|
|
@@ -16405,8 +16320,8 @@ async function runScan(args, hooks = {}) {
|
|
|
16405
16320
|
async function runScanInProject(args, hooks = {}) {
|
|
16406
16321
|
const started = Date.now();
|
|
16407
16322
|
const degradationWindow = beginDegradationWindow();
|
|
16408
|
-
const requestedDuration = Number.isFinite(args.maxDurationMs) ? args.maxDurationMs : DEFAULT_MAX_DURATION_MS
|
|
16409
|
-
const deadline = started + Math.min(Math.max(1, requestedDuration), MAX_DURATION_MS
|
|
16323
|
+
const requestedDuration = Number.isFinite(args.maxDurationMs) ? args.maxDurationMs : DEFAULT_MAX_DURATION_MS;
|
|
16324
|
+
const deadline = started + Math.min(Math.max(1, requestedDuration), MAX_DURATION_MS);
|
|
16410
16325
|
const discovered = discoverWorkspace(args.target);
|
|
16411
16326
|
const targetAbs = resolve(args.target);
|
|
16412
16327
|
const scanRoot = discovered && discovered.root !== targetAbs && targetAbs.startsWith(discovered.root + sep) ? {
|
|
@@ -16547,8 +16462,7 @@ async function runScanInProject(args, hooks = {}) {
|
|
|
16547
16462
|
resetTsMorphProject();
|
|
16548
16463
|
return result;
|
|
16549
16464
|
}
|
|
16550
|
-
|
|
16551
|
-
//#region src/cli-io.ts
|
|
16465
|
+
// src/cli-io.ts
|
|
16552
16466
|
/**
|
|
16553
16467
|
* The ONE error-to-message derivation in `src/`.
|
|
16554
16468
|
*
|
|
@@ -16707,8 +16621,7 @@ function buildMachineContract(result) {
|
|
|
16707
16621
|
...result.forensicVerdicts !== void 0 ? { forensicVerdicts: result.forensicVerdicts } : {}
|
|
16708
16622
|
};
|
|
16709
16623
|
}
|
|
16710
|
-
|
|
16711
|
-
//#region src/commands/fixture-example.ts
|
|
16624
|
+
// src/commands/fixture-example.ts
|
|
16712
16625
|
/**
|
|
16713
16626
|
* Shared example-fixture selection for `mjolnir explain` (explain.ts)
|
|
16714
16627
|
* and the generated rule docs (rule-docs.ts) — one chooser so both
|
|
@@ -16809,8 +16722,7 @@ function explainRule(ruleId, fixturesRoot) {
|
|
|
16809
16722
|
exampleFixtureRelPath: relative(fixturesRoot, fixturePath).replaceAll("\\", "/")
|
|
16810
16723
|
};
|
|
16811
16724
|
}
|
|
16812
|
-
|
|
16813
|
-
//#region src/engine/resolution.ts
|
|
16725
|
+
// src/engine/resolution.ts
|
|
16814
16726
|
/** Correlation identity (v1-compatible): ruleId\0file\0message. */
|
|
16815
16727
|
const fingerprint$1 = findingFingerprint;
|
|
16816
16728
|
/**
|
|
@@ -16961,8 +16873,7 @@ function diffAgainstBaseline(result, baseline) {
|
|
|
16961
16873
|
unchangedCount
|
|
16962
16874
|
};
|
|
16963
16875
|
}
|
|
16964
|
-
|
|
16965
|
-
//#region src/commands/verify.ts
|
|
16876
|
+
// src/commands/verify.ts
|
|
16966
16877
|
/**
|
|
16967
16878
|
* `mjolnir ci verify` — the agent-loop digest (product-gap-remediation
|
|
16968
16879
|
* master plan P7, plan 1788853205786 — flag agent, decision 6).
|
|
@@ -17109,8 +17020,7 @@ function renderTriageWorkflowJson(report) {
|
|
|
17109
17020
|
rows: workflowRows(report)
|
|
17110
17021
|
}, null, 2) + "\n";
|
|
17111
17022
|
}
|
|
17112
|
-
|
|
17113
|
-
//#region src/commands/pw-report.ts
|
|
17023
|
+
// src/commands/pw-report.ts
|
|
17114
17024
|
const ui = plainContext();
|
|
17115
17025
|
function summarizePwRun(report) {
|
|
17116
17026
|
const slowest = [...report.verdicts].sort((a, b) => b.totalDurationMs - a.totalDurationMs).slice(0, 5).map((v) => ({
|
|
@@ -17144,8 +17054,7 @@ function renderPwRunSummary(s) {
|
|
|
17144
17054
|
}
|
|
17145
17055
|
return lines.join("\n");
|
|
17146
17056
|
}
|
|
17147
|
-
|
|
17148
|
-
//#region src/mcp/server.ts
|
|
17057
|
+
// src/mcp/server.ts
|
|
17149
17058
|
/**
|
|
17150
17059
|
* MCP stdio transport (blueprint §21 — Phase 4, PURE TRANSPORT).
|
|
17151
17060
|
*
|
|
@@ -17163,22 +17072,38 @@ function renderPwRunSummary(s) {
|
|
|
17163
17072
|
* - parameter size caps; ONE scan in flight (serialized queue) reusing
|
|
17164
17073
|
* the pipeline's own budgets; zero network (no fetches anywhere); no
|
|
17165
17074
|
* shell execution beyond Mjölnir's own verbs.
|
|
17166
|
-
* - filesystem boundary
|
|
17075
|
+
* - filesystem boundary: the tools are read-only, and with no further
|
|
17076
|
+
* configuration they accept any path the parent process passes (the
|
|
17077
|
+
* parent is the trust boundary). `MJOLNIR_WORKSPACE_ROOT` confines every
|
|
17078
|
+
* path argument to that directory, symlinks resolved.
|
|
17167
17079
|
* - the plugin trust gate applies unchanged (--enable-plugins is NOT
|
|
17168
17080
|
* exposed over MCP: tools run with the gate CLOSED unless the server
|
|
17169
17081
|
* was started with MJOLNIR_ENABLE_PLUGINS=1 in its own environment).
|
|
17170
17082
|
*/
|
|
17171
17083
|
/** Hard caps (§21 threat model: parameter size + resource bounds). */
|
|
17172
17084
|
const MAX_PARAM_BYTES = 65536;
|
|
17173
|
-
const MAX_DURATION_MS = 36e5;
|
|
17174
|
-
const DEFAULT_MAX_DURATION_MS = 6e5;
|
|
17175
17085
|
const PROTOCOL_VERSION = "2025-06-18";
|
|
17176
|
-
|
|
17086
|
+
/** The real path of `p`, or its lexical resolution when it does not exist. */
|
|
17087
|
+
function realOrResolved(p) {
|
|
17088
|
+
try {
|
|
17089
|
+
return realpathSync.native(resolve(p));
|
|
17090
|
+
} catch {
|
|
17091
|
+
return resolve(p);
|
|
17092
|
+
}
|
|
17093
|
+
}
|
|
17094
|
+
/**
|
|
17095
|
+
* The optional workspace root. When set, every path argument must resolve
|
|
17096
|
+
* INSIDE it after symlinks are followed.
|
|
17097
|
+
*
|
|
17098
|
+
* Unset is open by design and is documented that way in the header: the MCP
|
|
17099
|
+
* tools are read-only and the stdio parent is the trust boundary. Setting
|
|
17100
|
+
* `MJOLNIR_WORKSPACE_ROOT` is how a host confines them.
|
|
17101
|
+
*/
|
|
17102
|
+
const CONFIGURED_ROOT = process.env.MJOLNIR_WORKSPACE_ROOT ? realOrResolved(process.env.MJOLNIR_WORKSPACE_ROOT) : null;
|
|
17177
17103
|
function isPathAllowed(path) {
|
|
17178
17104
|
if (!CONFIGURED_ROOT) return true;
|
|
17179
|
-
const
|
|
17180
|
-
|
|
17181
|
-
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
17105
|
+
const rel = relative(CONFIGURED_ROOT, realOrResolved(path));
|
|
17106
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
17182
17107
|
}
|
|
17183
17108
|
/** JSON-RPC error codes (subset used here). */
|
|
17184
17109
|
const MCP_ERRORS = {
|
|
@@ -17220,7 +17145,7 @@ const MCP_TOOLS = [
|
|
|
17220
17145
|
},
|
|
17221
17146
|
fixturesRoot: {
|
|
17222
17147
|
type: "string",
|
|
17223
|
-
description: "Root directory for fixture lookups (defaults to cwd)."
|
|
17148
|
+
description: "Root directory for fixture lookups (defaults to <cwd>/tests/fixtures, as for `mjolnir explain`)."
|
|
17224
17149
|
}
|
|
17225
17150
|
},
|
|
17226
17151
|
required: ["ruleId"]
|
|
@@ -17300,7 +17225,7 @@ function validateParams(name, args) {
|
|
|
17300
17225
|
if (typeof args["path"] !== "string" || args["path"].length === 0) return "scan requires a non-empty string `path`";
|
|
17301
17226
|
if (args["maxDurationMs"] !== void 0) {
|
|
17302
17227
|
const duration = args["maxDurationMs"];
|
|
17303
|
-
if (typeof duration !== "number" || !Number.isFinite(duration) || duration <= 0 || duration >
|
|
17228
|
+
if (typeof duration !== "number" || !Number.isFinite(duration) || duration <= 0 || duration > 36e5) return "`maxDurationMs` must be a finite positive number within the server budget";
|
|
17304
17229
|
}
|
|
17305
17230
|
}
|
|
17306
17231
|
if (name === "explain") {
|
|
@@ -17404,7 +17329,7 @@ async function handleToolCall(call) {
|
|
|
17404
17329
|
message: "fixturesRoot must be a string"
|
|
17405
17330
|
}
|
|
17406
17331
|
};
|
|
17407
|
-
const explanation = explainRule(ruleId, (typeof rawFixturesRoot === "string" ? rawFixturesRoot : void 0) ?? process.cwd());
|
|
17332
|
+
const explanation = explainRule(ruleId, (typeof rawFixturesRoot === "string" ? rawFixturesRoot : void 0) ?? join(process.cwd(), "tests", "fixtures"));
|
|
17408
17333
|
if (!explanation.ok) return {
|
|
17409
17334
|
jsonrpc: "2.0",
|
|
17410
17335
|
id: call.id,
|
|
@@ -17655,8 +17580,7 @@ async function handleMcpMessage(msg) {
|
|
|
17655
17580
|
}
|
|
17656
17581
|
};
|
|
17657
17582
|
}
|
|
17658
|
-
|
|
17659
|
-
//#region src/mcp/transport.ts
|
|
17583
|
+
// src/mcp/transport.ts
|
|
17660
17584
|
/**
|
|
17661
17585
|
* MCP stdio transport loop (blueprint §21 — the framing around the
|
|
17662
17586
|
* pure-transport server).
|
|
@@ -17753,8 +17677,7 @@ async function handleStdioLine(line, write) {
|
|
|
17753
17677
|
}
|
|
17754
17678
|
write(`${JSON.stringify(response)}\n`);
|
|
17755
17679
|
}
|
|
17756
|
-
|
|
17757
|
-
//#region src/integrations/sentry.ts
|
|
17680
|
+
// src/integrations/sentry.ts
|
|
17758
17681
|
/**
|
|
17759
17682
|
* Sentry crash reporting — opt-in, and inert unless a DSN is configured.
|
|
17760
17683
|
*
|
|
@@ -17880,8 +17803,7 @@ async function flushSentry(timeoutMs = FLUSH_TIMEOUT_MS) {
|
|
|
17880
17803
|
return false;
|
|
17881
17804
|
}
|
|
17882
17805
|
}
|
|
17883
|
-
|
|
17884
|
-
//#region src/mcp/stdio.ts
|
|
17806
|
+
// src/mcp/stdio.ts
|
|
17885
17807
|
/**
|
|
17886
17808
|
* The standalone MCP stdio binary (blueprint §21), built to
|
|
17887
17809
|
* `dist/mcp/stdio.mjs` and run by `npm run mcp`.
|
|
@@ -17906,7 +17828,6 @@ runStdioTransport(process.stdin, process.stdout).then(() => process.exit(0)).cat
|
|
|
17906
17828
|
process.stderr.write(`mjolnir mcp fatal: ${err instanceof Error ? err.message : err}\n`);
|
|
17907
17829
|
process.exitCode = 20;
|
|
17908
17830
|
});
|
|
17909
|
-
//#endregion
|
|
17910
17831
|
export {};
|
|
17911
17832
|
|
|
17912
17833
|
//# sourceMappingURL=stdio.mjs.map
|