mjolnir-qa 6.0.0 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +384 -1
- package/README.md +314 -29
- package/dist/cli.d.mts +1 -477
- package/dist/cli.mjs +664 -554
- package/dist/mcp/stdio.mjs +254 -366
- package/package.json +24 -34
- package/dist/rolldown-runtime-8H4AJuhK.mjs +0 -14
package/dist/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
|
|
@@ -262,8 +259,7 @@ function deriveTrustLevel(finding, corroboration) {
|
|
|
262
259
|
if (corroboration.level === "test") return "L4";
|
|
263
260
|
return "L3";
|
|
264
261
|
}
|
|
265
|
-
|
|
266
|
-
//#region src/rules/measured-fp.generated.ts
|
|
262
|
+
// src/rules/measured-fp.generated.ts
|
|
267
263
|
const MEASURED_FP = {
|
|
268
264
|
"QA-CI-001": {
|
|
269
265
|
fpRate: .105,
|
|
@@ -777,8 +773,7 @@ const MEASURED_FP = {
|
|
|
777
773
|
ciHigh: .4343
|
|
778
774
|
}
|
|
779
775
|
};
|
|
780
|
-
|
|
781
|
-
//#region src/engine/trust-summary.ts
|
|
776
|
+
// src/engine/trust-summary.ts
|
|
782
777
|
/**
|
|
783
778
|
* Trust level of the summary — the BEST trust level any finding in the
|
|
784
779
|
* scan reached. Static-only findings settle at L2 (deriveTrustLevel's
|
|
@@ -902,8 +897,7 @@ function buildTrustSummary(result, declarationsByFile) {
|
|
|
902
897
|
ceilingReasons: reasons
|
|
903
898
|
};
|
|
904
899
|
}
|
|
905
|
-
|
|
906
|
-
//#region src/governance/run-mode.ts
|
|
900
|
+
// src/governance/run-mode.ts
|
|
907
901
|
/**
|
|
908
902
|
* Which mode a run is in, and how the other one is reached.
|
|
909
903
|
*
|
|
@@ -954,8 +948,7 @@ function resolveRunMode(input) {
|
|
|
954
948
|
if (declared === "local-only" || declared === "hosted") return declared;
|
|
955
949
|
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
950
|
}
|
|
957
|
-
|
|
958
|
-
//#region src/engine/run-identity.ts
|
|
951
|
+
// src/engine/run-identity.ts
|
|
959
952
|
/**
|
|
960
953
|
* Run Identity (product-gap master plan §7, plan 1789009691197 R4c) —
|
|
961
954
|
* the deterministic binding that anchors every trust claim to its
|
|
@@ -1084,8 +1077,7 @@ function buildEvidenceGraph(parts) {
|
|
|
1084
1077
|
} } : {}
|
|
1085
1078
|
};
|
|
1086
1079
|
}
|
|
1087
|
-
|
|
1088
|
-
//#region src/engine/file-executor.ts
|
|
1080
|
+
// src/engine/file-executor.ts
|
|
1089
1081
|
function cacheHitOutcome(job) {
|
|
1090
1082
|
return {
|
|
1091
1083
|
path: job.path,
|
|
@@ -1176,8 +1168,7 @@ function createExecutor(name, concurrency, runOne) {
|
|
|
1176
1168
|
execute: runOne
|
|
1177
1169
|
};
|
|
1178
1170
|
}
|
|
1179
|
-
|
|
1180
|
-
//#region src/scope/git-resolve.ts
|
|
1171
|
+
// src/scope/git-resolve.ts
|
|
1181
1172
|
/**
|
|
1182
1173
|
* Absolute-path git resolution (audit S1).
|
|
1183
1174
|
*
|
|
@@ -1252,8 +1243,7 @@ function runGit(root, args) {
|
|
|
1252
1243
|
return null;
|
|
1253
1244
|
}
|
|
1254
1245
|
}
|
|
1255
|
-
|
|
1256
|
-
//#region src/lib/safe-json.ts
|
|
1246
|
+
// src/lib/safe-json.ts
|
|
1257
1247
|
/**
|
|
1258
1248
|
* Reusable safe-JSON parse-and-validate helper.
|
|
1259
1249
|
*
|
|
@@ -1290,8 +1280,7 @@ function isRecord(v) {
|
|
|
1290
1280
|
return false;
|
|
1291
1281
|
}
|
|
1292
1282
|
}
|
|
1293
|
-
|
|
1294
|
-
//#region src/engine/candidate-binding.ts
|
|
1283
|
+
// src/engine/candidate-binding.ts
|
|
1295
1284
|
/**
|
|
1296
1285
|
* Candidate binding (plan V5-010).
|
|
1297
1286
|
*
|
|
@@ -1424,18 +1413,17 @@ function readCandidateBinding(root, manifestPath = CANDIDATE_MANIFEST_PATH) {
|
|
|
1424
1413
|
return null;
|
|
1425
1414
|
}
|
|
1426
1415
|
}
|
|
1427
|
-
|
|
1428
|
-
//#region src/engine/version.ts
|
|
1416
|
+
// src/engine/version.ts
|
|
1429
1417
|
/**
|
|
1430
1418
|
* Engine version, as a leaf module: `runIdentity` (R4c) needs the engine
|
|
1431
1419
|
* version inside scan-pipeline WITHOUT importing cli.ts (a cycle — cli
|
|
1432
1420
|
* imports the pipeline). The literal follows the same discipline as
|
|
1433
1421
|
* CLI_VERSION and SARIF's driver.version: kept in sync on release by
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
1422
|
+
* `npm run version:surface:sync` (this file is an identity surface) and
|
|
1423
|
+
* guarded by the version-consistency
|
|
1436
1424
|
* spec. cli.ts re-exports this as CLI_VERSION.
|
|
1437
1425
|
*/
|
|
1438
|
-
const ENGINE_VERSION = "6.0.
|
|
1426
|
+
const ENGINE_VERSION = "6.0.1";
|
|
1439
1427
|
resolveBuildId();
|
|
1440
1428
|
/**
|
|
1441
1429
|
* The decision, with the process boundary already behind `runGit`.
|
|
@@ -1475,16 +1463,7 @@ function resolveBuildId() {
|
|
|
1475
1463
|
};
|
|
1476
1464
|
return buildIdFrom(runGit);
|
|
1477
1465
|
}
|
|
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
|
-
*/
|
|
1466
|
+
// src/engine/contract-versions.ts
|
|
1488
1467
|
const TRUST_MODEL_VERSION = "1.0.0";
|
|
1489
1468
|
/**
|
|
1490
1469
|
* 2.0.0 (ADR 0014): the core tier carries an evidence FLOOR. A core rule's
|
|
@@ -1496,8 +1475,7 @@ const TRUST_MODEL_VERSION = "1.0.0";
|
|
|
1496
1475
|
*/
|
|
1497
1476
|
const SCORING_MODEL_VERSION = "2.0.0";
|
|
1498
1477
|
const FRAMEWORK_SUPPORT_MATRIX_VERSION = "1.0.0";
|
|
1499
|
-
|
|
1500
|
-
//#region src/discovery/evidence-discovery.ts
|
|
1478
|
+
// src/discovery/evidence-discovery.ts
|
|
1501
1479
|
/**
|
|
1502
1480
|
* Zero-config evidence discovery (Mega MVP Master Plan v3.1 §26 WI-11).
|
|
1503
1481
|
*
|
|
@@ -1589,8 +1567,7 @@ function discoverEvidenceCandidates(scanRoot) {
|
|
|
1589
1567
|
}
|
|
1590
1568
|
return out.sort((a, b) => a.depth - b.depth || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
1591
1569
|
}
|
|
1592
|
-
|
|
1593
|
-
//#region src/discovery/workspace.ts
|
|
1570
|
+
// src/discovery/workspace.ts
|
|
1594
1571
|
/**
|
|
1595
1572
|
* Repository discovery (Sprint-Plan W1-03).
|
|
1596
1573
|
* Finds project root, parses package.json, detects npm/yarn/pnpm workspaces.
|
|
@@ -1637,8 +1614,7 @@ function basename$1(p) {
|
|
|
1637
1614
|
const parts = p.split(/[\\/]/);
|
|
1638
1615
|
return parts[parts.length - 1];
|
|
1639
1616
|
}
|
|
1640
|
-
|
|
1641
|
-
//#region src/lib/fs-bounded.ts
|
|
1617
|
+
// src/lib/fs-bounded.ts
|
|
1642
1618
|
function closeQuiet(fd) {
|
|
1643
1619
|
try {
|
|
1644
1620
|
closeSync(fd);
|
|
@@ -1690,8 +1666,7 @@ function readFileBounded(path, maxBytes) {
|
|
|
1690
1666
|
if (fd !== void 0) closeQuiet(fd);
|
|
1691
1667
|
}
|
|
1692
1668
|
}
|
|
1693
|
-
|
|
1694
|
-
//#region src/config/config-schema.ts
|
|
1669
|
+
// src/config/config-schema.ts
|
|
1695
1670
|
const VALID_GATES = /* @__PURE__ */ new Set([
|
|
1696
1671
|
"advisory",
|
|
1697
1672
|
"error",
|
|
@@ -1761,8 +1736,7 @@ function validateConfigSchema(config) {
|
|
|
1761
1736
|
errors
|
|
1762
1737
|
};
|
|
1763
1738
|
}
|
|
1764
|
-
|
|
1765
|
-
//#region src/config/config.ts
|
|
1739
|
+
// src/config/config.ts
|
|
1766
1740
|
/**
|
|
1767
1741
|
* Config file support (Sprint-Plan W7, Product-MVP §27 + GAP-F).
|
|
1768
1742
|
* Zero-config preserved: config presence never changes detection
|
|
@@ -1896,8 +1870,7 @@ function applySeverityOverrides(findings, cfg) {
|
|
|
1896
1870
|
if (override && SEVERITY_ORDER.includes(override)) f.severity = override;
|
|
1897
1871
|
}
|
|
1898
1872
|
}
|
|
1899
|
-
|
|
1900
|
-
//#region src/lib/compare.ts
|
|
1873
|
+
// src/lib/compare.ts
|
|
1901
1874
|
/**
|
|
1902
1875
|
* Order that does not depend on who is asking.
|
|
1903
1876
|
*
|
|
@@ -1943,8 +1916,7 @@ function compareCodePoints(a, b) {
|
|
|
1943
1916
|
function compareLocalized(locale) {
|
|
1944
1917
|
return (a, b) => a.localeCompare(b, locale);
|
|
1945
1918
|
}
|
|
1946
|
-
|
|
1947
|
-
//#region src/lib/glob.ts
|
|
1919
|
+
// src/lib/glob.ts
|
|
1948
1920
|
/**
|
|
1949
1921
|
* The shared segment compiler: a glob to a regex BODY, with no anchors.
|
|
1950
1922
|
*
|
|
@@ -1998,8 +1970,7 @@ function anyDepthGlobRegExp(glob) {
|
|
|
1998
1970
|
function pathMatchesGlob(path, glob) {
|
|
1999
1971
|
return anchoredGlobRegExp(glob).test(path.replaceAll("\\", "/"));
|
|
2000
1972
|
}
|
|
2001
|
-
|
|
2002
|
-
//#region src/discovery/ignores.ts
|
|
1973
|
+
// src/discovery/ignores.ts
|
|
2003
1974
|
/**
|
|
2004
1975
|
* Ignore resolution (Product-MVP.txt R1 — Tier-1 launch blocker).
|
|
2005
1976
|
*
|
|
@@ -2155,8 +2126,7 @@ function isLintFixtureDir(absoluteDir) {
|
|
|
2155
2126
|
return false;
|
|
2156
2127
|
}
|
|
2157
2128
|
}
|
|
2158
|
-
|
|
2159
|
-
//#region src/discovery/shared-walk.ts
|
|
2129
|
+
// src/discovery/shared-walk.ts
|
|
2160
2130
|
/**
|
|
2161
2131
|
* Shared directory walker (audit P-2).
|
|
2162
2132
|
*
|
|
@@ -2219,8 +2189,7 @@ function sharedWalk(options) {
|
|
|
2219
2189
|
};
|
|
2220
2190
|
walk(options.root);
|
|
2221
2191
|
}
|
|
2222
|
-
|
|
2223
|
-
//#region src/discovery/frameworks.ts
|
|
2192
|
+
// src/discovery/frameworks.ts
|
|
2224
2193
|
/**
|
|
2225
2194
|
* Framework detection via config resolution (Sprint-Plan W3-01, R8).
|
|
2226
2195
|
*
|
|
@@ -2303,8 +2272,7 @@ function detectFrameworks(ws) {
|
|
|
2303
2272
|
unknown: false
|
|
2304
2273
|
};
|
|
2305
2274
|
}
|
|
2306
|
-
|
|
2307
|
-
//#region src/engine/ts-ast.ts
|
|
2275
|
+
// src/engine/ts-ast.ts
|
|
2308
2276
|
/**
|
|
2309
2277
|
* AST precision layer (Upgrade-Plan-v3 Phase 3).
|
|
2310
2278
|
*
|
|
@@ -2503,8 +2471,7 @@ function getCodeOnlyText(file) {
|
|
|
2503
2471
|
function resetTsMorphProject() {
|
|
2504
2472
|
looseProject = null;
|
|
2505
2473
|
}
|
|
2506
|
-
|
|
2507
|
-
//#region src/engine/code-text.ts
|
|
2474
|
+
// src/engine/code-text.ts
|
|
2508
2475
|
/**
|
|
2509
2476
|
* Code-only text masking (Phase 1 — Tempering Plan).
|
|
2510
2477
|
*
|
|
@@ -2864,8 +2831,7 @@ function getCodeTextForCounting(text, language) {
|
|
|
2864
2831
|
function blankRange(chars, start, end) {
|
|
2865
2832
|
for (let i = start; i < end && i < chars.length; i++) if (chars[i] !== "\n" && chars[i] !== "\r") chars[i] = " ";
|
|
2866
2833
|
}
|
|
2867
|
-
|
|
2868
|
-
//#region src/engine/adapter.ts
|
|
2834
|
+
// src/engine/adapter.ts
|
|
2869
2835
|
/**
|
|
2870
2836
|
* The §15.1 framework filter, shared by every language adapter: a rule
|
|
2871
2837
|
* that declares `frameworks` runs on a file only when the file carries
|
|
@@ -2879,8 +2845,7 @@ function frameworkFilterApplies(rule, file) {
|
|
|
2879
2845
|
if (tags === void 0 || tags.length === 0) return true;
|
|
2880
2846
|
return rule.frameworks.some((f) => tags.includes(f));
|
|
2881
2847
|
}
|
|
2882
|
-
|
|
2883
|
-
//#region src/engine/shared-run-rules.ts
|
|
2848
|
+
// src/engine/shared-run-rules.ts
|
|
2884
2849
|
/**
|
|
2885
2850
|
* Run ONE rule with crash isolation, and return what it produced.
|
|
2886
2851
|
*
|
|
@@ -2917,8 +2882,7 @@ function runRuleIsolated(rule, file, onCrash) {
|
|
|
2917
2882
|
return null;
|
|
2918
2883
|
}
|
|
2919
2884
|
}
|
|
2920
|
-
|
|
2921
|
-
//#region src/adapters/typescript.ts
|
|
2885
|
+
// src/adapters/typescript.ts
|
|
2922
2886
|
/**
|
|
2923
2887
|
* TypeScript/JavaScript adapter (R1).
|
|
2924
2888
|
* Wraps the proven regex-based engine behind the LanguageAdapter
|
|
@@ -3171,8 +3135,7 @@ function loadWorkspaceShim(root) {
|
|
|
3171
3135
|
return null;
|
|
3172
3136
|
}
|
|
3173
3137
|
}
|
|
3174
|
-
|
|
3175
|
-
//#region src/engine/tree-sitter-ast.ts
|
|
3138
|
+
// src/engine/tree-sitter-ast.ts
|
|
3176
3139
|
/**
|
|
3177
3140
|
* Tree-sitter WASM AST layer for Java and C# — Sprint 8 Task 36
|
|
3178
3141
|
* (Master-Stabilization-Plan.md).
|
|
@@ -3391,8 +3354,7 @@ async function releaseTreeSitterResources() {
|
|
|
3391
3354
|
} catch {}
|
|
3392
3355
|
}
|
|
3393
3356
|
}
|
|
3394
|
-
|
|
3395
|
-
//#region src/adapters/python.ts
|
|
3357
|
+
// src/adapters/python.ts
|
|
3396
3358
|
/**
|
|
3397
3359
|
* Python/pytest adapter (Upgrade-Plan-v2 R2).
|
|
3398
3360
|
*
|
|
@@ -3550,8 +3512,7 @@ const pythonAdapter = {
|
|
|
3550
3512
|
function readText$2(p) {
|
|
3551
3513
|
return readFileSync(p, "utf8");
|
|
3552
3514
|
}
|
|
3553
|
-
|
|
3554
|
-
//#region src/adapters/java.ts
|
|
3515
|
+
// src/adapters/java.ts
|
|
3555
3516
|
/**
|
|
3556
3517
|
* Java/JUnit adapter (Upgrade-Plan-v3 Phase 4).
|
|
3557
3518
|
*
|
|
@@ -3717,8 +3678,7 @@ function javaFileTags(file) {
|
|
|
3717
3678
|
function readText$1(path) {
|
|
3718
3679
|
return readFileSync(path, "utf8");
|
|
3719
3680
|
}
|
|
3720
|
-
|
|
3721
|
-
//#region src/adapters/csharp.ts
|
|
3681
|
+
// src/adapters/csharp.ts
|
|
3722
3682
|
/**
|
|
3723
3683
|
* C#/.NET adapter (Upgrade-Plan-v3 Phase 5).
|
|
3724
3684
|
*
|
|
@@ -3874,8 +3834,7 @@ function csharpFileTags(file) {
|
|
|
3874
3834
|
function readText(path) {
|
|
3875
3835
|
return readFileSync(path, "utf8");
|
|
3876
3836
|
}
|
|
3877
|
-
|
|
3878
|
-
//#region src/discovery/yaml-guards.ts
|
|
3837
|
+
// src/discovery/yaml-guards.ts
|
|
3879
3838
|
/**
|
|
3880
3839
|
* Shared hostile-YAML guards (product-gap master plan P3 common law —
|
|
3881
3840
|
* `azure-pipelines.yml` detection rides the same YAML machinery as the
|
|
@@ -3944,8 +3903,7 @@ function enforceDepthCap(value, max, depthMessage = GENERIC_LABELS.depthMessage)
|
|
|
3944
3903
|
});
|
|
3945
3904
|
}
|
|
3946
3905
|
}
|
|
3947
|
-
|
|
3948
|
-
//#region src/discovery/workflow-parser.ts
|
|
3906
|
+
// src/discovery/workflow-parser.ts
|
|
3949
3907
|
/**
|
|
3950
3908
|
* Safe GitHub Actions workflow parser (Phase 0.3, W4-03 debt).
|
|
3951
3909
|
*
|
|
@@ -3991,8 +3949,7 @@ function parseWorkflow(text) {
|
|
|
3991
3949
|
}
|
|
3992
3950
|
return { jobs };
|
|
3993
3951
|
}
|
|
3994
|
-
|
|
3995
|
-
//#region src/adapters/github-actions.ts
|
|
3952
|
+
// src/adapters/github-actions.ts
|
|
3996
3953
|
/**
|
|
3997
3954
|
* GitHub Actions adapter (R1).
|
|
3998
3955
|
* Owns workflow discovery and safe-YAML parsing; CI rules consume the
|
|
@@ -4077,8 +4034,7 @@ const githubActionsAdapter = {
|
|
|
4077
4034
|
};
|
|
4078
4035
|
var WorkflowParseSkipped = class extends Error {};
|
|
4079
4036
|
const LIMITS_MAX_BYTES$2 = 1048576;
|
|
4080
|
-
|
|
4081
|
-
//#region src/discovery/azure-pipeline-parser.ts
|
|
4037
|
+
// src/discovery/azure-pipeline-parser.ts
|
|
4082
4038
|
/**
|
|
4083
4039
|
* Safe Azure DevOps pipeline parser (product-gap master plan P3b).
|
|
4084
4040
|
*
|
|
@@ -4312,8 +4268,7 @@ function isAzurePipelineFixture(normalizedPath) {
|
|
|
4312
4268
|
const base = normalizedPath.replaceAll("\\", "/").split("/").pop() ?? "";
|
|
4313
4269
|
return AZURE_PIPELINE_FILENAMES_SET.has(base);
|
|
4314
4270
|
}
|
|
4315
|
-
|
|
4316
|
-
//#region src/adapters/azure-pipelines.ts
|
|
4271
|
+
// src/adapters/azure-pipelines.ts
|
|
4317
4272
|
/**
|
|
4318
4273
|
* Azure DevOps pipeline adapter (product-gap master plan P3b).
|
|
4319
4274
|
* Owns `azure-pipelines.yml` discovery (repo root, the platform's
|
|
@@ -4393,8 +4348,7 @@ const azurePipelinesAdapter = {
|
|
|
4393
4348
|
};
|
|
4394
4349
|
var AzureParseSkipped = class extends Error {};
|
|
4395
4350
|
const LIMITS_MAX_BYTES$1 = 1048576;
|
|
4396
|
-
|
|
4397
|
-
//#region src/adapters/jenkins.ts
|
|
4351
|
+
// src/adapters/jenkins.ts
|
|
4398
4352
|
/**
|
|
4399
4353
|
* Jenkins adapter (product-gap master plan P3c).
|
|
4400
4354
|
* The root `Jenkinsfile` is a TEXT-target file kind: declarative and
|
|
@@ -4468,8 +4422,7 @@ const jenkinsAdapter = {
|
|
|
4468
4422
|
}
|
|
4469
4423
|
};
|
|
4470
4424
|
const LIMITS_MAX_BYTES = 1048576;
|
|
4471
|
-
|
|
4472
|
-
//#region src/discovery/scan-adapters.ts
|
|
4425
|
+
// src/discovery/scan-adapters.ts
|
|
4473
4426
|
/**
|
|
4474
4427
|
* The canonical adapter registry (audits H-6/H-9).
|
|
4475
4428
|
*
|
|
@@ -4606,8 +4559,7 @@ function isInsideSkippedDir(root, absPath, skipNames) {
|
|
|
4606
4559
|
return relative(root, absPath).replaceAll("\\", "/").split("/").slice(0, -1).some((seg) => skipNames.includes(seg));
|
|
4607
4560
|
}
|
|
4608
4561
|
typescriptAdapter.testFileGlobs, pythonAdapter.testFileGlobs, javaAdapter.testFileGlobs, csharpAdapter.testFileGlobs, githubActionsAdapter.testFileGlobs, azurePipelinesAdapter.testFileGlobs, jenkinsAdapter.testFileGlobs;
|
|
4609
|
-
|
|
4610
|
-
//#region src/scope/changed.ts
|
|
4562
|
+
// src/scope/changed.ts
|
|
4611
4563
|
/**
|
|
4612
4564
|
* Changed-scope engine (Sprint-Plan W6, Product-MVP §9 `--scope changed`).
|
|
4613
4565
|
*
|
|
@@ -4850,8 +4802,7 @@ function computeStagedFiles(root) {
|
|
|
4850
4802
|
if (raw === null) return null;
|
|
4851
4803
|
return raw.split("\0").filter((s) => s.length > 0);
|
|
4852
4804
|
}
|
|
4853
|
-
|
|
4854
|
-
//#region src/rules/rule.ts
|
|
4805
|
+
// src/rules/rule.ts
|
|
4855
4806
|
/**
|
|
4856
4807
|
* Invoke an `astQuery` hook with the mandatory-fallback contract: no
|
|
4857
4808
|
* hook or no AST on the context resolves to `undefined` (→ regex
|
|
@@ -4864,8 +4815,7 @@ function tryAstQuery(hook, ctx) {
|
|
|
4864
4815
|
function defineRule(rule) {
|
|
4865
4816
|
return rule;
|
|
4866
4817
|
}
|
|
4867
|
-
|
|
4868
|
-
//#region src/rules/shared/positions.ts
|
|
4818
|
+
// src/rules/shared/positions.ts
|
|
4869
4819
|
/**
|
|
4870
4820
|
* Shared position helpers (Phase 6 — Tempering Plan).
|
|
4871
4821
|
* One lineAt, one colAt, one matchBrace. The other 79 copies are dead.
|
|
@@ -4910,8 +4860,7 @@ function lineAt(text, index) {
|
|
|
4910
4860
|
function colAt(text, index) {
|
|
4911
4861
|
return index - (index <= 0 ? -1 : text.lastIndexOf("\n", index - 1));
|
|
4912
4862
|
}
|
|
4913
|
-
|
|
4914
|
-
//#region src/rules/test/qa-test-001-focused-test.ts
|
|
4863
|
+
// src/rules/test/qa-test-001-focused-test.ts
|
|
4915
4864
|
/**
|
|
4916
4865
|
* QA-TEST-001 — Focused test committed (`fit`/`fdescribe`/`.only`).
|
|
4917
4866
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -4977,8 +4926,7 @@ const focusedTestCommitted = defineRule({
|
|
|
4977
4926
|
return findings;
|
|
4978
4927
|
}
|
|
4979
4928
|
});
|
|
4980
|
-
|
|
4981
|
-
//#region src/rules/test/qa-test-002-skipped-test.ts
|
|
4929
|
+
// src/rules/test/qa-test-002-skipped-test.ts
|
|
4982
4930
|
/**
|
|
4983
4931
|
* QA-TEST-002 — Skipped test (`it.skip`, `xit`, `test.skip`, `describe.skip`).
|
|
4984
4932
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -5034,8 +4982,7 @@ const skippedTest = defineRule({
|
|
|
5034
4982
|
return findings;
|
|
5035
4983
|
}
|
|
5036
4984
|
});
|
|
5037
|
-
|
|
5038
|
-
//#region src/rules/shared/masking.ts
|
|
4985
|
+
// src/rules/shared/masking.ts
|
|
5039
4986
|
/** True when the offset was blanked in codeText (inside a string or comment). */
|
|
5040
4987
|
function isMasked(ctx, index) {
|
|
5041
4988
|
const mask = ctx.codeText;
|
|
@@ -5096,8 +5043,7 @@ function isInsideEmbeddedCode(ctx, index) {
|
|
|
5096
5043
|
const hasCallSyntax = /\w\s*\(/.test(inner);
|
|
5097
5044
|
return hasNestedQuote && hasCallSyntax;
|
|
5098
5045
|
}
|
|
5099
|
-
|
|
5100
|
-
//#region src/rules/test/qa-test-003-no-assertions.ts
|
|
5046
|
+
// src/rules/test/qa-test-003-no-assertions.ts
|
|
5101
5047
|
/**
|
|
5102
5048
|
* QA-TEST-003 — Test with no assertions.
|
|
5103
5049
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5175,8 +5121,7 @@ function matchBrace$2(text, open) {
|
|
|
5175
5121
|
}
|
|
5176
5122
|
return -1;
|
|
5177
5123
|
}
|
|
5178
|
-
|
|
5179
|
-
//#region src/rules/test/qa-test-004-hard-sleep.ts
|
|
5124
|
+
// src/rules/test/qa-test-004-hard-sleep.ts
|
|
5180
5125
|
/**
|
|
5181
5126
|
* QA-TEST-004 — Hard sleep (`sleep()`, `waitForTimeout()`, `setTimeout` as wait).
|
|
5182
5127
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -5241,8 +5186,7 @@ const hardSleep = defineRule({
|
|
|
5241
5186
|
return findings;
|
|
5242
5187
|
}
|
|
5243
5188
|
});
|
|
5244
|
-
|
|
5245
|
-
//#region src/rules/test/qa-test-006-retry-abuse.ts
|
|
5189
|
+
// src/rules/test/qa-test-006-retry-abuse.ts
|
|
5246
5190
|
/**
|
|
5247
5191
|
* QA-TEST-006 — Retry abuse in test config.
|
|
5248
5192
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -5308,8 +5252,7 @@ const retryAbuse = defineRule({
|
|
|
5308
5252
|
return findings;
|
|
5309
5253
|
}
|
|
5310
5254
|
});
|
|
5311
|
-
|
|
5312
|
-
//#region src/rules/test/qa-test-010-empty-body.ts
|
|
5255
|
+
// src/rules/test/qa-test-010-empty-body.ts
|
|
5313
5256
|
/**
|
|
5314
5257
|
* QA-TEST-010 — Empty test body.
|
|
5315
5258
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5364,8 +5307,7 @@ const emptyTestBody = defineRule({
|
|
|
5364
5307
|
return findings;
|
|
5365
5308
|
}
|
|
5366
5309
|
});
|
|
5367
|
-
|
|
5368
|
-
//#region src/rules/quality/qa-tqual-002-tautological.ts
|
|
5310
|
+
// src/rules/quality/qa-tqual-002-tautological.ts
|
|
5369
5311
|
/**
|
|
5370
5312
|
* QA-TQUAL-002 — Tautological assertion.
|
|
5371
5313
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5442,8 +5384,7 @@ const tautologicalAssertion = defineRule({
|
|
|
5442
5384
|
return findings;
|
|
5443
5385
|
}
|
|
5444
5386
|
});
|
|
5445
|
-
|
|
5446
|
-
//#region src/rules/quality/qa-tqual-009-promise-assertion.ts
|
|
5387
|
+
// src/rules/quality/qa-tqual-009-promise-assertion.ts
|
|
5447
5388
|
/**
|
|
5448
5389
|
* QA-TQUAL-009 — Unawaited assertion inside a promise chain.
|
|
5449
5390
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5613,8 +5554,7 @@ function matchParen$2(text, open) {
|
|
|
5613
5554
|
}
|
|
5614
5555
|
return -1;
|
|
5615
5556
|
}
|
|
5616
|
-
|
|
5617
|
-
//#region src/rules/quality/qa-tqual-011-commented-out.ts
|
|
5557
|
+
// src/rules/quality/qa-tqual-011-commented-out.ts
|
|
5618
5558
|
/**
|
|
5619
5559
|
* QA-TQUAL-011 — Commented-out tests.
|
|
5620
5560
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -5727,8 +5667,7 @@ function commentRanges(ctx) {
|
|
|
5727
5667
|
}
|
|
5728
5668
|
return ranges;
|
|
5729
5669
|
}
|
|
5730
|
-
|
|
5731
|
-
//#region src/rules/playwright/qa-pw-002-unawaited-assertion.ts
|
|
5670
|
+
// src/rules/playwright/qa-pw-002-unawaited-assertion.ts
|
|
5732
5671
|
/**
|
|
5733
5672
|
* QA-PW-002 — Missing `await` on a Playwright locator assertion.
|
|
5734
5673
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5873,8 +5812,7 @@ function runRegexFallback(text, path) {
|
|
|
5873
5812
|
});
|
|
5874
5813
|
return findings;
|
|
5875
5814
|
}
|
|
5876
|
-
|
|
5877
|
-
//#region src/rules/playwright/qa-pw-003-debug-artifacts.ts
|
|
5815
|
+
// src/rules/playwright/qa-pw-003-debug-artifacts.ts
|
|
5878
5816
|
/**
|
|
5879
5817
|
* QA-PW-003 — Committed `page.pause()` or `test.only()` in e2e specs.
|
|
5880
5818
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -5926,8 +5864,7 @@ const committedDebugArtifacts = defineRule({
|
|
|
5926
5864
|
return findings;
|
|
5927
5865
|
}
|
|
5928
5866
|
});
|
|
5929
|
-
|
|
5930
|
-
//#region src/rules/playwright/qa-pw-004-brittle-selectors.ts
|
|
5867
|
+
// src/rules/playwright/qa-pw-004-brittle-selectors.ts
|
|
5931
5868
|
/**
|
|
5932
5869
|
* QA-PW-004 — Brittle CSS/XPath selectors vs role-based locators.
|
|
5933
5870
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -6019,8 +5956,7 @@ function stripComments(ctx) {
|
|
|
6019
5956
|
} catch {}
|
|
6020
5957
|
return ctx.text.replace(/\/\/[^\n]*/g, (cm) => " ".repeat(cm.length));
|
|
6021
5958
|
}
|
|
6022
|
-
|
|
6023
|
-
//#region src/rules/ci/verification-gate.ts
|
|
5959
|
+
// src/rules/ci/verification-gate.ts
|
|
6024
5960
|
/**
|
|
6025
5961
|
* Shared "is this shell command a verification gate?" matcher for the QA-CI
|
|
6026
5962
|
* rules. A gate is a command whose failure MUST fail the job — test runners,
|
|
@@ -6060,8 +5996,7 @@ const VERIFICATION_GATE_RE = new RegExp([
|
|
|
6060
5996
|
function looksLikeVerificationGate(cmd) {
|
|
6061
5997
|
return VERIFICATION_GATE_RE.test(cmd);
|
|
6062
5998
|
}
|
|
6063
|
-
|
|
6064
|
-
//#region src/rules/ci/azure-gates.ts
|
|
5999
|
+
// src/rules/ci/azure-gates.ts
|
|
6065
6000
|
/** Discriminator: the Azure parser is the only one emitting `platform`. */
|
|
6066
6001
|
function isAzurePipelineDoc(ast) {
|
|
6067
6002
|
return typeof ast === "object" && ast !== null && ast.platform === "azure-pipelines";
|
|
@@ -6145,8 +6080,7 @@ function locateAzureJobKey(text, job, keyName) {
|
|
|
6145
6080
|
if (m) return lineAt(text, m.index);
|
|
6146
6081
|
return 1;
|
|
6147
6082
|
}
|
|
6148
|
-
|
|
6149
|
-
//#region src/rules/ci/qa-ci-001-continue-on-error.ts
|
|
6083
|
+
// src/rules/ci/qa-ci-001-continue-on-error.ts
|
|
6150
6084
|
/**
|
|
6151
6085
|
* QA-CI-001 — `continue-on-error: true` masks a failing job.
|
|
6152
6086
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -6374,8 +6308,7 @@ function describeAzureStep(step) {
|
|
|
6374
6308
|
if (step.task) return step.task;
|
|
6375
6309
|
return "step";
|
|
6376
6310
|
}
|
|
6377
|
-
|
|
6378
|
-
//#region src/rules/ci/qa-ci-002-swallowed-exit.ts
|
|
6311
|
+
// src/rules/ci/qa-ci-002-swallowed-exit.ts
|
|
6379
6312
|
/**
|
|
6380
6313
|
* QA-CI-002 — `|| true` swallows a command's exit code.
|
|
6381
6314
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -6430,8 +6363,7 @@ const swallowedExitCode = defineRule({
|
|
|
6430
6363
|
return findings;
|
|
6431
6364
|
}
|
|
6432
6365
|
});
|
|
6433
|
-
|
|
6434
|
-
//#region src/rules/ci/qa-ci-007-retry-masking.ts
|
|
6366
|
+
// src/rules/ci/qa-ci-007-retry-masking.ts
|
|
6435
6367
|
/**
|
|
6436
6368
|
* QA-CI-007 — Retry masking test failures.
|
|
6437
6369
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -6582,8 +6514,7 @@ function azureArm$1(ctx, doc, findings) {
|
|
|
6582
6514
|
});
|
|
6583
6515
|
}
|
|
6584
6516
|
}
|
|
6585
|
-
|
|
6586
|
-
//#region src/rules/ci/qa-ci-005-report-never-generated.ts
|
|
6517
|
+
// src/rules/ci/qa-ci-005-report-never-generated.ts
|
|
6587
6518
|
/**
|
|
6588
6519
|
* QA-CI-005 — Report consumed but never generated.
|
|
6589
6520
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -6741,8 +6672,7 @@ function findJobConsumerLine(text, jobName, consumerRe) {
|
|
|
6741
6672
|
const anywhere = new RegExp(consumerRe.source, "i").exec(text);
|
|
6742
6673
|
return anywhere ? lineAt(text, anywhere.index) : 1;
|
|
6743
6674
|
}
|
|
6744
|
-
|
|
6745
|
-
//#region src/rules/ci/jenkins-gates.ts
|
|
6675
|
+
// src/rules/ci/jenkins-gates.ts
|
|
6746
6676
|
/**
|
|
6747
6677
|
* Jenkinsfile block scanner for the QA-CI Jenkins arms (product-gap
|
|
6748
6678
|
* master plan P3c). Jenkinsfiles are Groovy text — "no new language
|
|
@@ -6988,8 +6918,7 @@ function textIsVerificationGate(text) {
|
|
|
6988
6918
|
function lineOfOffset(text, offset) {
|
|
6989
6919
|
return lineAt(text, offset);
|
|
6990
6920
|
}
|
|
6991
|
-
|
|
6992
|
-
//#region src/rules/ci/qa-ci-008-always-success.ts
|
|
6921
|
+
// src/rules/ci/qa-ci-008-always-success.ts
|
|
6993
6922
|
/**
|
|
6994
6923
|
* QA-CI-008 — Always-success step at the end of a job.
|
|
6995
6924
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7163,8 +7092,7 @@ function jenkinsArm$1(ctx, findings) {
|
|
|
7163
7092
|
});
|
|
7164
7093
|
}
|
|
7165
7094
|
}
|
|
7166
|
-
|
|
7167
|
-
//#region src/rules/ci/qa-ci-009-exit-code.ts
|
|
7095
|
+
// src/rules/ci/qa-ci-009-exit-code.ts
|
|
7168
7096
|
/**
|
|
7169
7097
|
* QA-CI-009 — Test command does not propagate exit code.
|
|
7170
7098
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7299,8 +7227,7 @@ function jenkinsArm(ctx, findings) {
|
|
|
7299
7227
|
});
|
|
7300
7228
|
}
|
|
7301
7229
|
}
|
|
7302
|
-
|
|
7303
|
-
//#region src/rules/ci/qa-ci-010-non-blocking.ts
|
|
7230
|
+
// src/rules/ci/qa-ci-010-non-blocking.ts
|
|
7304
7231
|
/**
|
|
7305
7232
|
* QA-CI-010 — Required test job is non-blocking for branch protection.
|
|
7306
7233
|
* Severity: error · Confidence: medium · deterministic-defect
|
|
@@ -7383,8 +7310,7 @@ function findLine(text, re) {
|
|
|
7383
7310
|
function escapeRe(s) {
|
|
7384
7311
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7385
7312
|
}
|
|
7386
|
-
|
|
7387
|
-
//#region src/rules/ci/qa-ci-013-can-never-fail.ts
|
|
7313
|
+
// src/rules/ci/qa-ci-013-can-never-fail.ts
|
|
7388
7314
|
/**
|
|
7389
7315
|
* QA-CI-013 — Verification stage/job conditioned so it can never fail the
|
|
7390
7316
|
* pipeline (product-gap master plan P3b).
|
|
@@ -7488,8 +7414,7 @@ const canNeverFailGate = defineRule({
|
|
|
7488
7414
|
function jobHasGate(job) {
|
|
7489
7415
|
return (job.steps ?? []).some((s) => s !== void 0 && stepIsVerificationGate$1(s));
|
|
7490
7416
|
}
|
|
7491
|
-
|
|
7492
|
-
//#region src/rules/ci/qa-ci-014-swallowed-verification.ts
|
|
7417
|
+
// src/rules/ci/qa-ci-014-swallowed-verification.ts
|
|
7493
7418
|
/**
|
|
7494
7419
|
* QA-CI-014 — try/catch swallowing a verification-stage failure without
|
|
7495
7420
|
* rethrow or failure-marking (product-gap master plan P3c).
|
|
@@ -7546,8 +7471,7 @@ const swallowedVerificationFailure = defineRule({
|
|
|
7546
7471
|
return findings;
|
|
7547
7472
|
}
|
|
7548
7473
|
});
|
|
7549
|
-
|
|
7550
|
-
//#region src/rules/python/qa-py-003-no-assertions.ts
|
|
7474
|
+
// src/rules/python/qa-py-003-no-assertions.ts
|
|
7551
7475
|
/**
|
|
7552
7476
|
* QA-PY-003 — Test function with no assertions.
|
|
7553
7477
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7668,8 +7592,7 @@ function extractBlock$1(text, afterColon) {
|
|
|
7668
7592
|
const lineEnd = rest.indexOf("\n", firstIdx);
|
|
7669
7593
|
return lineEnd === -1 ? rest : rest.slice(0, lineEnd);
|
|
7670
7594
|
}
|
|
7671
|
-
|
|
7672
|
-
//#region src/rules/python/qa-py-005-hard-sleep.ts
|
|
7595
|
+
// src/rules/python/qa-py-005-hard-sleep.ts
|
|
7673
7596
|
/**
|
|
7674
7597
|
* QA-PY-005 — time.sleep() in tests.
|
|
7675
7598
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -7716,8 +7639,7 @@ const pyHardSleep = defineRule({
|
|
|
7716
7639
|
return findings;
|
|
7717
7640
|
}
|
|
7718
7641
|
});
|
|
7719
|
-
|
|
7720
|
-
//#region src/rules/python/qa-py-002-skipped-test.ts
|
|
7642
|
+
// src/rules/python/qa-py-002-skipped-test.ts
|
|
7721
7643
|
/**
|
|
7722
7644
|
* QA-PY-002 — Skipped test (`@pytest.mark.skip`, `xfail` non-strict).
|
|
7723
7645
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -7777,8 +7699,7 @@ const pySkippedTest = defineRule({
|
|
|
7777
7699
|
return findings;
|
|
7778
7700
|
}
|
|
7779
7701
|
});
|
|
7780
|
-
|
|
7781
|
-
//#region src/rules/python/qa-py-012-tautological.ts
|
|
7702
|
+
// src/rules/python/qa-py-012-tautological.ts
|
|
7782
7703
|
/**
|
|
7783
7704
|
* QA-PY-012 — Tautological assertion (`assert True`, `assert x == x`).
|
|
7784
7705
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7833,8 +7754,7 @@ const pyTautological = defineRule({
|
|
|
7833
7754
|
return findings;
|
|
7834
7755
|
}
|
|
7835
7756
|
});
|
|
7836
|
-
|
|
7837
|
-
//#region src/rules/python/qa-py-001-focused-test.ts
|
|
7757
|
+
// src/rules/python/qa-py-001-focused-test.ts
|
|
7838
7758
|
/**
|
|
7839
7759
|
* QA-PY-001 — Focused test committed.
|
|
7840
7760
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -7888,8 +7808,7 @@ const pyFocusedTest = defineRule({
|
|
|
7888
7808
|
return findings;
|
|
7889
7809
|
}
|
|
7890
7810
|
});
|
|
7891
|
-
|
|
7892
|
-
//#region src/engine/jv-cs-ast.ts
|
|
7811
|
+
// src/engine/jv-cs-ast.ts
|
|
7893
7812
|
/** Narrow the loose `ast` seam back to a tree-sitter Tree. */
|
|
7894
7813
|
function getTreeSitterTree(ast) {
|
|
7895
7814
|
if (!(ast instanceof Object) || !("rootNode" in ast)) return void 0;
|
|
@@ -8093,8 +8012,7 @@ function isHelperIdiom(name) {
|
|
|
8093
8012
|
}
|
|
8094
8013
|
return false;
|
|
8095
8014
|
}
|
|
8096
|
-
|
|
8097
|
-
//#region src/engine/python-ast.ts
|
|
8015
|
+
// src/engine/python-ast.ts
|
|
8098
8016
|
function getPythonTree(ast) {
|
|
8099
8017
|
return getTreeSitterTree(ast);
|
|
8100
8018
|
}
|
|
@@ -8179,8 +8097,7 @@ function pythonWithRaisesBlocks(tree) {
|
|
|
8179
8097
|
});
|
|
8180
8098
|
return out;
|
|
8181
8099
|
}
|
|
8182
|
-
|
|
8183
|
-
//#region src/rules/python/qa-py-007-raises-without-match.ts
|
|
8100
|
+
// src/rules/python/qa-py-007-raises-without-match.ts
|
|
8184
8101
|
/**
|
|
8185
8102
|
* QA-PY-007 — pytest.raises without match.
|
|
8186
8103
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -8307,8 +8224,7 @@ function astArm(ctx, tree) {
|
|
|
8307
8224
|
}
|
|
8308
8225
|
return findings;
|
|
8309
8226
|
}
|
|
8310
|
-
|
|
8311
|
-
//#region src/rules/python/qa-py-009-commented-out.ts
|
|
8227
|
+
// src/rules/python/qa-py-009-commented-out.ts
|
|
8312
8228
|
/**
|
|
8313
8229
|
* QA-PY-009 — Commented-out tests.
|
|
8314
8230
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -8355,8 +8271,7 @@ const pyCommentedOutTest = defineRule({
|
|
|
8355
8271
|
return findings;
|
|
8356
8272
|
}
|
|
8357
8273
|
});
|
|
8358
|
-
|
|
8359
|
-
//#region src/rules/python/qa-py-004-bare-truthiness.ts
|
|
8274
|
+
// src/rules/python/qa-py-004-bare-truthiness.ts
|
|
8360
8275
|
/**
|
|
8361
8276
|
* QA-PY-004 — Bare truthiness assert on complex object.
|
|
8362
8277
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -8435,8 +8350,7 @@ const pyBareTruthinessAssert = defineRule({
|
|
|
8435
8350
|
return findings;
|
|
8436
8351
|
}
|
|
8437
8352
|
});
|
|
8438
|
-
|
|
8439
|
-
//#region src/rules/python/qa-py-011-mutable-fixture.ts
|
|
8353
|
+
// src/rules/python/qa-py-011-mutable-fixture.ts
|
|
8440
8354
|
/**
|
|
8441
8355
|
* QA-PY-011 — Mutable fixture shared across tests.
|
|
8442
8356
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -8487,8 +8401,7 @@ const pyMutableFixture = defineRule({
|
|
|
8487
8401
|
return findings;
|
|
8488
8402
|
}
|
|
8489
8403
|
});
|
|
8490
|
-
|
|
8491
|
-
//#region src/rules/playwright/qa-pw-101-wait-for-timeout.ts
|
|
8404
|
+
// src/rules/playwright/qa-pw-101-wait-for-timeout.ts
|
|
8492
8405
|
/**
|
|
8493
8406
|
* QA-PW-101 — waitForTimeout() anywhere.
|
|
8494
8407
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -8535,8 +8448,7 @@ const pwWaitForTimeout = defineRule({
|
|
|
8535
8448
|
return findings;
|
|
8536
8449
|
}
|
|
8537
8450
|
});
|
|
8538
|
-
|
|
8539
|
-
//#region src/rules/playwright/qa-pw-102-wait-load.ts
|
|
8451
|
+
// src/rules/playwright/qa-pw-102-wait-load.ts
|
|
8540
8452
|
/**
|
|
8541
8453
|
* QA-PW-102 — waitForEvent('load') / waitForLoadState('load') instead of
|
|
8542
8454
|
* web-first assertions.
|
|
@@ -8590,8 +8502,7 @@ const pwWaitForLoadEvent = defineRule({
|
|
|
8590
8502
|
return findings;
|
|
8591
8503
|
}
|
|
8592
8504
|
});
|
|
8593
|
-
|
|
8594
|
-
//#region src/rules/playwright/qa-pw-113-deep-frames.ts
|
|
8505
|
+
// src/rules/playwright/qa-pw-113-deep-frames.ts
|
|
8595
8506
|
/**
|
|
8596
8507
|
* QA-PW-113 — frameLocator chains > 2 deep.
|
|
8597
8508
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -8641,8 +8552,7 @@ const pwDeepFrameLocator = defineRule({
|
|
|
8641
8552
|
function count(fragment) {
|
|
8642
8553
|
return fragment.match(/frameLocator\s*\(/g).length;
|
|
8643
8554
|
}
|
|
8644
|
-
|
|
8645
|
-
//#region src/rules/playwright/qa-pw-117-serial.ts
|
|
8555
|
+
// src/rules/playwright/qa-pw-117-serial.ts
|
|
8646
8556
|
/**
|
|
8647
8557
|
* QA-PW-117 — test.describe.serial without justification comment.
|
|
8648
8558
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -8696,8 +8606,7 @@ const pwSerialNoJustification = defineRule({
|
|
|
8696
8606
|
return findings;
|
|
8697
8607
|
}
|
|
8698
8608
|
});
|
|
8699
|
-
|
|
8700
|
-
//#region src/rules/playwright/qa-pw-121-config-retries.ts
|
|
8609
|
+
// src/rules/playwright/qa-pw-121-config-retries.ts
|
|
8701
8610
|
/**
|
|
8702
8611
|
* QA-PW-121 — playwright.config: retries >= 3 or workers = 0.
|
|
8703
8612
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -8762,8 +8671,7 @@ const pwConfigRetryAbuse = defineRule({
|
|
|
8762
8671
|
return findings;
|
|
8763
8672
|
}
|
|
8764
8673
|
});
|
|
8765
|
-
|
|
8766
|
-
//#region src/rules/playwright/qa-pw-122-no-trace.ts
|
|
8674
|
+
// src/rules/playwright/qa-pw-122-no-trace.ts
|
|
8767
8675
|
/**
|
|
8768
8676
|
* QA-PW-122 — No trace capture on retry in playwright.config.
|
|
8769
8677
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -8812,8 +8720,7 @@ const pwNoTraceOnRetry = defineRule({
|
|
|
8812
8720
|
return findings;
|
|
8813
8721
|
}
|
|
8814
8722
|
});
|
|
8815
|
-
|
|
8816
|
-
//#region src/rules/playwright/qa-pw-124-project-split.ts
|
|
8723
|
+
// src/rules/playwright/qa-pw-124-project-split.ts
|
|
8817
8724
|
/**
|
|
8818
8725
|
* QA-PW-124 — Projects defined but no smoke/regression split.
|
|
8819
8726
|
* Severity: info · Confidence: high · deterministic-defect
|
|
@@ -8875,8 +8782,7 @@ const pwNoProjectSplit = defineRule({
|
|
|
8875
8782
|
return findings;
|
|
8876
8783
|
}
|
|
8877
8784
|
});
|
|
8878
|
-
|
|
8879
|
-
//#region src/rules/playwright/qa-pw-104-trial-click.ts
|
|
8785
|
+
// src/rules/playwright/qa-pw-104-trial-click.ts
|
|
8880
8786
|
/**
|
|
8881
8787
|
* QA-PW-104 — `{ trial: true }` misuse — check-then-act race.
|
|
8882
8788
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -8922,8 +8828,7 @@ const pwTrialMisuse = defineRule({
|
|
|
8922
8828
|
return findings;
|
|
8923
8829
|
}
|
|
8924
8830
|
});
|
|
8925
|
-
|
|
8926
|
-
//#region src/rules/playwright/qa-pw-116-storage-state.ts
|
|
8831
|
+
// src/rules/playwright/qa-pw-116-storage-state.ts
|
|
8927
8832
|
/**
|
|
8928
8833
|
* QA-PW-116 — storageState reuse without expiry strategy.
|
|
8929
8834
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -8969,8 +8874,7 @@ const pwStorageStateNoExpiry = defineRule({
|
|
|
8969
8874
|
return findings;
|
|
8970
8875
|
}
|
|
8971
8876
|
});
|
|
8972
|
-
|
|
8973
|
-
//#region src/rules/playwright/qa-pw-125-global-setup.ts
|
|
8877
|
+
// src/rules/playwright/qa-pw-125-global-setup.ts
|
|
8974
8878
|
/**
|
|
8975
8879
|
* QA-PW-125 — globalSetup mutating shared external state.
|
|
8976
8880
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9028,8 +8932,7 @@ const pwGlobalSetupSharedState = defineRule({
|
|
|
9028
8932
|
return findings;
|
|
9029
8933
|
}
|
|
9030
8934
|
});
|
|
9031
|
-
|
|
9032
|
-
//#region src/rules/playwright/qa-pw-115-shared-page.ts
|
|
8935
|
+
// src/rules/playwright/qa-pw-115-shared-page.ts
|
|
9033
8936
|
/**
|
|
9034
8937
|
* QA-PW-115 — Shared `page` across parallel tests without isolation.
|
|
9035
8938
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9077,8 +8980,7 @@ const pwSharedPage = defineRule({
|
|
|
9077
8980
|
return findings;
|
|
9078
8981
|
}
|
|
9079
8982
|
});
|
|
9080
|
-
|
|
9081
|
-
//#region src/rules/playwright/qa-pw-140.ts
|
|
8983
|
+
// src/rules/playwright/qa-pw-140.ts
|
|
9082
8984
|
/**
|
|
9083
8985
|
* QA-PW-140 — Screenshot without maxDiffPixelRatio.
|
|
9084
8986
|
*
|
|
@@ -9152,8 +9054,7 @@ function matchParen(text, open) {
|
|
|
9152
9054
|
}
|
|
9153
9055
|
return -1;
|
|
9154
9056
|
}
|
|
9155
|
-
|
|
9156
|
-
//#region src/rules/playwright/qa-pw-123-hardcoded-url.ts
|
|
9057
|
+
// src/rules/playwright/qa-pw-123-hardcoded-url.ts
|
|
9157
9058
|
/**
|
|
9158
9059
|
* QA-PW-123 — Hardcoded baseURL in specs.
|
|
9159
9060
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9201,8 +9102,7 @@ const hardcodedBaseUrl = defineRule({
|
|
|
9201
9102
|
return findings;
|
|
9202
9103
|
}
|
|
9203
9104
|
});
|
|
9204
|
-
|
|
9205
|
-
//#region src/rules/quality/qa-env-001-env-coupling.ts
|
|
9105
|
+
// src/rules/quality/qa-env-001-env-coupling.ts
|
|
9206
9106
|
/**
|
|
9207
9107
|
* QA-ENV-001 — "Works On My Machine" detector core (Tier 5 #24).
|
|
9208
9108
|
* Environment coupling in test files: fixed ports, OS paths,
|
|
@@ -9269,8 +9169,7 @@ const envCoupling = defineRule({
|
|
|
9269
9169
|
return findings;
|
|
9270
9170
|
}
|
|
9271
9171
|
});
|
|
9272
|
-
|
|
9273
|
-
//#region src/rules/playwright/qa-pw-141-retry-no-triage.ts
|
|
9172
|
+
// src/rules/playwright/qa-pw-141-retry-no-triage.ts
|
|
9274
9173
|
/**
|
|
9275
9174
|
* QA-PW-141 — playwright.config retries set high enough to mask flakes.
|
|
9276
9175
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9327,8 +9226,7 @@ const pwRetryMaskingNoForensics = defineRule({
|
|
|
9327
9226
|
return findings;
|
|
9328
9227
|
}
|
|
9329
9228
|
});
|
|
9330
|
-
|
|
9331
|
-
//#region src/rules/playwright/qa-pw-142-blanket-route.ts
|
|
9229
|
+
// src/rules/playwright/qa-pw-142-blanket-route.ts
|
|
9332
9230
|
/**
|
|
9333
9231
|
* QA-PW-142 — page.route() blanket-mocking instead of scoped intercepts.
|
|
9334
9232
|
* Severity: warning · Confidence: medium · heuristic-risk
|
|
@@ -9383,8 +9281,7 @@ const pwBlanketRouteMock = defineRule({
|
|
|
9383
9281
|
return findings;
|
|
9384
9282
|
}
|
|
9385
9283
|
});
|
|
9386
|
-
|
|
9387
|
-
//#region src/rules/playwright/qa-pw-143-no-artifacts.ts
|
|
9284
|
+
// src/rules/playwright/qa-pw-143-no-artifacts.ts
|
|
9388
9285
|
/**
|
|
9389
9286
|
* QA-PW-143 — No screenshot/video artifact capture on failure.
|
|
9390
9287
|
* Severity: info · Confidence: high · deterministic-defect
|
|
@@ -9437,8 +9334,7 @@ const pwNoFailureArtifacts = defineRule({
|
|
|
9437
9334
|
return findings;
|
|
9438
9335
|
}
|
|
9439
9336
|
});
|
|
9440
|
-
|
|
9441
|
-
//#region src/rules/playwright/qa-pw-144-single-browser.ts
|
|
9337
|
+
// src/rules/playwright/qa-pw-144-single-browser.ts
|
|
9442
9338
|
/**
|
|
9443
9339
|
* QA-PW-144 — Single-browser project matrix (no cross-browser coverage).
|
|
9444
9340
|
* Severity: info · Confidence: high · deterministic-defect
|
|
@@ -9500,8 +9396,7 @@ const pwSingleBrowserMatrix = defineRule({
|
|
|
9500
9396
|
return findings;
|
|
9501
9397
|
}
|
|
9502
9398
|
});
|
|
9503
|
-
|
|
9504
|
-
//#region src/rules/playwright/qa-pw-146-css-locator.ts
|
|
9399
|
+
// src/rules/playwright/qa-pw-146-css-locator.ts
|
|
9505
9400
|
/**
|
|
9506
9401
|
* QA-PW-146 — CSS/XPath string selector where Playwright's locator
|
|
9507
9402
|
* standard expects a normalized locator (plan §17.3).
|
|
@@ -9580,8 +9475,7 @@ const pwLocatorNormalize = defineRule({
|
|
|
9580
9475
|
return findings;
|
|
9581
9476
|
}
|
|
9582
9477
|
});
|
|
9583
|
-
|
|
9584
|
-
//#region src/rules/playwright/qa-pw-147-codegen-artifact.ts
|
|
9478
|
+
// src/rules/playwright/qa-pw-147-codegen-artifact.ts
|
|
9585
9479
|
/**
|
|
9586
9480
|
* QA-PW-147 — Playwright codegen artifact left unedited (plan §17.1).
|
|
9587
9481
|
* Severity: info · Confidence: medium · observation
|
|
@@ -9640,8 +9534,7 @@ const pwCodegenArtifact = defineRule({
|
|
|
9640
9534
|
return findings;
|
|
9641
9535
|
}
|
|
9642
9536
|
});
|
|
9643
|
-
|
|
9644
|
-
//#region src/rules/python/qa-py-101-sync-async-mix.ts
|
|
9537
|
+
// src/rules/python/qa-py-101-sync-async-mix.ts
|
|
9645
9538
|
/**
|
|
9646
9539
|
* QA-PY-101 — Playwright-Python: sync API in async test (or vice versa).
|
|
9647
9540
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9690,8 +9583,7 @@ const pyPwSyncAsyncMix = defineRule({
|
|
|
9690
9583
|
return findings;
|
|
9691
9584
|
}
|
|
9692
9585
|
});
|
|
9693
|
-
|
|
9694
|
-
//#region src/rules/python/qa-py-103-wait-for-timeout.ts
|
|
9586
|
+
// src/rules/python/qa-py-103-wait-for-timeout.ts
|
|
9695
9587
|
/**
|
|
9696
9588
|
* QA-PY-103 — Playwright-Python: wait_for_timeout() as synchronization.
|
|
9697
9589
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9738,8 +9630,7 @@ const pyPwWaitForTimeout = defineRule({
|
|
|
9738
9630
|
return findings;
|
|
9739
9631
|
}
|
|
9740
9632
|
});
|
|
9741
|
-
|
|
9742
|
-
//#region src/rules/python/qa-py-105-pw-no-assertions.ts
|
|
9633
|
+
// src/rules/python/qa-py-105-pw-no-assertions.ts
|
|
9743
9634
|
/**
|
|
9744
9635
|
* QA-PY-105 — Playwright-Python: no assertion in Playwright test.
|
|
9745
9636
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -9819,8 +9710,7 @@ function extractBlock(text, afterColon) {
|
|
|
9819
9710
|
const lineEnd = rest.indexOf("\n", firstContent.index);
|
|
9820
9711
|
return lineEnd === -1 ? rest : rest.slice(0, lineEnd);
|
|
9821
9712
|
}
|
|
9822
|
-
|
|
9823
|
-
//#region src/rules/java/qa-jv-101-disabled-test.ts
|
|
9713
|
+
// src/rules/java/qa-jv-101-disabled-test.ts
|
|
9824
9714
|
/**
|
|
9825
9715
|
* QA-JV-101 — @Disabled test without justification.
|
|
9826
9716
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -9874,8 +9764,7 @@ const jvDisabledTest = defineRule({
|
|
|
9874
9764
|
return findings;
|
|
9875
9765
|
}
|
|
9876
9766
|
});
|
|
9877
|
-
|
|
9878
|
-
//#region src/rules/java/qa-jv-103-no-assertions.ts
|
|
9767
|
+
// src/rules/java/qa-jv-103-no-assertions.ts
|
|
9879
9768
|
/**
|
|
9880
9769
|
* QA-JV-103 — Test method without assertions.
|
|
9881
9770
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -10032,8 +9921,7 @@ function matchBrace$1(text, open) {
|
|
|
10032
9921
|
}
|
|
10033
9922
|
return -1;
|
|
10034
9923
|
}
|
|
10035
|
-
|
|
10036
|
-
//#region src/engine/qa-model.ts
|
|
9924
|
+
// src/engine/qa-model.ts
|
|
10037
9925
|
/**
|
|
10038
9926
|
* Extract the semantic model for one parsed file. Resolves undefined
|
|
10039
9927
|
* when the language has no extractor or the parse stage produced no
|
|
@@ -10576,8 +10464,7 @@ function extractPythonModel(file) {
|
|
|
10576
10464
|
nodes
|
|
10577
10465
|
};
|
|
10578
10466
|
}
|
|
10579
|
-
|
|
10580
|
-
//#region src/rules/java/qa-jv-105-wait-for-timeout.ts
|
|
10467
|
+
// src/rules/java/qa-jv-105-wait-for-timeout.ts
|
|
10581
10468
|
/**
|
|
10582
10469
|
* QA-JV-105 — waitForTimeout / hardcoded navigation waits.
|
|
10583
10470
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -10662,8 +10549,7 @@ const jvWaitForTimeout = defineRule({
|
|
|
10662
10549
|
return findings;
|
|
10663
10550
|
}
|
|
10664
10551
|
});
|
|
10665
|
-
|
|
10666
|
-
//#region src/rules/csharp/qa-cs-101-skipped-test.ts
|
|
10552
|
+
// src/rules/csharp/qa-cs-101-skipped-test.ts
|
|
10667
10553
|
/**
|
|
10668
10554
|
* QA-CS-101 — [Ignore]/[Skip] test without justification.
|
|
10669
10555
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -10727,8 +10613,7 @@ const csSkippedTest = defineRule({
|
|
|
10727
10613
|
return findings;
|
|
10728
10614
|
}
|
|
10729
10615
|
});
|
|
10730
|
-
|
|
10731
|
-
//#region src/rules/csharp/qa-cs-103-no-assertions.ts
|
|
10616
|
+
// src/rules/csharp/qa-cs-103-no-assertions.ts
|
|
10732
10617
|
/**
|
|
10733
10618
|
* QA-CS-103 — Test method without assertions.
|
|
10734
10619
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -10762,7 +10647,6 @@ const csSkippedTest = defineRule({
|
|
|
10762
10647
|
* Deep-diffing those requires symbol semantics Mjölnir deliberately does
|
|
10763
10648
|
* not promise for C# (plan §13.4).
|
|
10764
10649
|
*/
|
|
10765
|
-
/** Helper/verification idiom: verify/check/assert-prefixed names. */
|
|
10766
10650
|
const WHY$5 = "Without an assertion the test can only fail by throwing — it cannot detect behavioral regressions.";
|
|
10767
10651
|
const FIX$4 = "Add an assertion on the expected outcome, or remove the test.";
|
|
10768
10652
|
function makeFinding(path, position, name) {
|
|
@@ -10926,8 +10810,7 @@ function matchBrace(text, open) {
|
|
|
10926
10810
|
}
|
|
10927
10811
|
return -1;
|
|
10928
10812
|
}
|
|
10929
|
-
|
|
10930
|
-
//#region src/rules/csharp/qa-cs-105-wait-for-timeout.ts
|
|
10813
|
+
// src/rules/csharp/qa-cs-105-wait-for-timeout.ts
|
|
10931
10814
|
/**
|
|
10932
10815
|
* QA-CS-105 — Playwright-.NET: WaitForTimeoutAsync hard sleep.
|
|
10933
10816
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -11015,8 +10898,7 @@ const csWaitForTimeout = defineRule({
|
|
|
11015
10898
|
return findings;
|
|
11016
10899
|
}
|
|
11017
10900
|
});
|
|
11018
|
-
|
|
11019
|
-
//#region src/rules/cypress/qa-cyp-001-cy-wait.ts
|
|
10901
|
+
// src/rules/cypress/qa-cyp-001-cy-wait.ts
|
|
11020
10902
|
/**
|
|
11021
10903
|
* QA-CYP-001 — Fixed `cy.wait(n)` (hard-coded wait).
|
|
11022
10904
|
* Severity: warning · Confidence: high · deterministic-defect
|
|
@@ -11095,8 +10977,7 @@ function isCypressFile(ctx) {
|
|
|
11095
10977
|
if (/\.cy\.[jt]sx?$/.test(ctx.path)) return true;
|
|
11096
10978
|
return /\bcy\s*\./.test(ctx.codeText ?? ctx.text);
|
|
11097
10979
|
}
|
|
11098
|
-
|
|
11099
|
-
//#region src/rules/cypress/qa-cyp-002-focused-test.ts
|
|
10980
|
+
// src/rules/cypress/qa-cyp-002-focused-test.ts
|
|
11100
10981
|
/**
|
|
11101
10982
|
* QA-CYP-002 — Focused test (`.only`).
|
|
11102
10983
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -11159,8 +11040,7 @@ const cypFocusedTest = defineRule({
|
|
|
11159
11040
|
return findings;
|
|
11160
11041
|
}
|
|
11161
11042
|
});
|
|
11162
|
-
|
|
11163
|
-
//#region src/rules/cypress/qa-cyp-003-config-security.ts
|
|
11043
|
+
// src/rules/cypress/qa-cyp-003-config-security.ts
|
|
11164
11044
|
/**
|
|
11165
11045
|
* QA-CYP-003 — Cypress config disables `chromeWebSecurity`.
|
|
11166
11046
|
* Severity: error · Confidence: high · deterministic-defect
|
|
@@ -11230,8 +11110,7 @@ const cypConfigSecurity = defineRule({
|
|
|
11230
11110
|
return findings;
|
|
11231
11111
|
}
|
|
11232
11112
|
});
|
|
11233
|
-
|
|
11234
|
-
//#region src/rules/selenium/qa-se-sleep-lookup.ts
|
|
11113
|
+
// src/rules/selenium/qa-se-sleep-lookup.ts
|
|
11235
11114
|
/**
|
|
11236
11115
|
* Selenium sleep-then-interact rules (Verification Trust Evolution Plan
|
|
11237
11116
|
* §15.3 — "Selenium implicit-wait abuse / missing WebDriverWait",
|
|
@@ -11382,8 +11261,7 @@ const sePythonSleepLookup = defineRule({
|
|
|
11382
11261
|
return sleepBeforeLookup(ctx, PY_SLEEP_RE, PY_LOOKUP_RE, "QA-SE-003");
|
|
11383
11262
|
}
|
|
11384
11263
|
});
|
|
11385
|
-
|
|
11386
|
-
//#region src/rules/shared/family.ts
|
|
11264
|
+
// src/rules/shared/family.ts
|
|
11387
11265
|
/**
|
|
11388
11266
|
* Cross-language pattern rule family generator (Phase 6 — Tempering Plan).
|
|
11389
11267
|
*
|
|
@@ -11449,8 +11327,7 @@ function definePatternFamily(opts) {
|
|
|
11449
11327
|
}
|
|
11450
11328
|
}));
|
|
11451
11329
|
}
|
|
11452
|
-
|
|
11453
|
-
//#region src/rules/families/hard-sleep.ts
|
|
11330
|
+
// src/rules/families/hard-sleep.ts
|
|
11454
11331
|
/**
|
|
11455
11332
|
* Hard-sleep family (Phase 6 — Tempering Plan).
|
|
11456
11333
|
* Java: Thread.sleep(), C#: Thread.Sleep() / Task.Delay()
|
|
@@ -11601,8 +11478,7 @@ const hardSleepFamily = definePatternFamily({
|
|
|
11601
11478
|
fix: "Use `await Assertions.Expect(locator).ToBeVisibleAsync()` or locator.WaitForAsync()."
|
|
11602
11479
|
}]
|
|
11603
11480
|
});
|
|
11604
|
-
|
|
11605
|
-
//#region src/rules/families/network-idle.ts
|
|
11481
|
+
// src/rules/families/network-idle.ts
|
|
11606
11482
|
/**
|
|
11607
11483
|
* Network-idle family (Phase 6 — Tempering Plan).
|
|
11608
11484
|
* All three language variants of waitForLoadState(networkidle).
|
|
@@ -11663,8 +11539,7 @@ const networkIdleFamily = definePatternFamily({
|
|
|
11663
11539
|
}
|
|
11664
11540
|
]
|
|
11665
11541
|
});
|
|
11666
|
-
|
|
11667
|
-
//#region src/rules/families/hardcoded-url.ts
|
|
11542
|
+
// src/rules/families/hardcoded-url.ts
|
|
11668
11543
|
/**
|
|
11669
11544
|
* Hardcoded-URL family (Phase 6 — Tempering Plan).
|
|
11670
11545
|
* Detects absolute URLs in navigation/request calls across languages.
|
|
@@ -11714,8 +11589,7 @@ const hardcodedUrlFamily = definePatternFamily({
|
|
|
11714
11589
|
fix: "Use `--base-url` / `base_url` fixture, or `os.environ[...]`."
|
|
11715
11590
|
}]
|
|
11716
11591
|
});
|
|
11717
|
-
|
|
11718
|
-
//#region src/rules/families/shared-page.ts
|
|
11592
|
+
// src/rules/families/shared-page.ts
|
|
11719
11593
|
/**
|
|
11720
11594
|
* Shared-page family (Phase 6 — Tempering Plan).
|
|
11721
11595
|
* Detects static/module-level Page/Browser instances shared across tests.
|
|
@@ -11776,8 +11650,7 @@ const sharedPageFamily = definePatternFamily({
|
|
|
11776
11650
|
}
|
|
11777
11651
|
]
|
|
11778
11652
|
});
|
|
11779
|
-
|
|
11780
|
-
//#region src/rules/families/brittle-selectors.ts
|
|
11653
|
+
// src/rules/families/brittle-selectors.ts
|
|
11781
11654
|
/**
|
|
11782
11655
|
* Brittle-selectors family (Phase 6 — Tempering Plan).
|
|
11783
11656
|
* Detects XPath/structural CSS selectors across Java, C#, Python.
|
|
@@ -11889,8 +11762,7 @@ const brittleSelectorsFamily = [
|
|
|
11889
11762
|
}
|
|
11890
11763
|
], "Prefer role-based locators (`get_by_role(...)`) or data-testid attributes.")
|
|
11891
11764
|
];
|
|
11892
|
-
|
|
11893
|
-
//#region src/rules/families/retry-masking.ts
|
|
11765
|
+
// src/rules/families/retry-masking.ts
|
|
11894
11766
|
/**
|
|
11895
11767
|
* Retry-masking family (Phase 6 — Tempering Plan).
|
|
11896
11768
|
* Detects retry annotations/attributes that mask flaky tests.
|
|
@@ -12010,8 +11882,7 @@ const retryMaskingFamily = [defineRule({
|
|
|
12010
11882
|
return findings;
|
|
12011
11883
|
}
|
|
12012
11884
|
})];
|
|
12013
|
-
|
|
12014
|
-
//#region src/rules/index.ts
|
|
11885
|
+
// src/rules/index.ts
|
|
12015
11886
|
const RULES = [
|
|
12016
11887
|
focusedTestCommitted,
|
|
12017
11888
|
skippedTest,
|
|
@@ -12092,8 +11963,7 @@ const RULES = [
|
|
|
12092
11963
|
function getRule(id) {
|
|
12093
11964
|
return RULES.find((r) => r.id === id);
|
|
12094
11965
|
}
|
|
12095
|
-
|
|
12096
|
-
//#region src/lib/wilson.ts
|
|
11966
|
+
// src/lib/wilson.ts
|
|
12097
11967
|
/**
|
|
12098
11968
|
* 95% Wilson score interval on a proportion (plan §20.2).
|
|
12099
11969
|
*
|
|
@@ -12203,8 +12073,7 @@ function effectiveTier(rule) {
|
|
|
12203
12073
|
if (rule.tier !== void 0) return rule.tier;
|
|
12204
12074
|
return measurementTier(rule);
|
|
12205
12075
|
}
|
|
12206
|
-
|
|
12207
|
-
//#region src/scorer/scorer.ts
|
|
12076
|
+
// src/scorer/scorer.ts
|
|
12208
12077
|
/**
|
|
12209
12078
|
* Transparent scorer (Product-MVP §8).
|
|
12210
12079
|
* score = 100 − Σ(severity deductions), floor 0.
|
|
@@ -12356,9 +12225,9 @@ function countTestDeclarations(text, codeText) {
|
|
|
12356
12225
|
const source = codeText ?? text;
|
|
12357
12226
|
const patterns = [
|
|
12358
12227
|
/\b(?:it|test)(?:\.\w+)*\s*(?:\(|`)/g,
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12228
|
+
/^[ \t]*(?:async[ \t]+)?def[ \t]+test_\w*/gm,
|
|
12229
|
+
/^[ \t]*@Test\b/gm,
|
|
12230
|
+
/^[ \t]*\[(?:Test|Fact|Theory|TestMethod)\b/gm
|
|
12362
12231
|
];
|
|
12363
12232
|
let count = 0;
|
|
12364
12233
|
for (const re of patterns) {
|
|
@@ -12367,8 +12236,7 @@ function countTestDeclarations(text, codeText) {
|
|
|
12367
12236
|
}
|
|
12368
12237
|
return count;
|
|
12369
12238
|
}
|
|
12370
|
-
|
|
12371
|
-
//#region src/engine/rule-runner.ts
|
|
12239
|
+
// src/engine/rule-runner.ts
|
|
12372
12240
|
/**
|
|
12373
12241
|
* Legacy appliesTo → adapter id mapping.
|
|
12374
12242
|
*
|
|
@@ -12407,8 +12275,7 @@ function asUniversal(rule) {
|
|
|
12407
12275
|
}
|
|
12408
12276
|
};
|
|
12409
12277
|
}
|
|
12410
|
-
|
|
12411
|
-
//#region src/engine/tier-policy.ts
|
|
12278
|
+
// src/engine/tier-policy.ts
|
|
12412
12279
|
/**
|
|
12413
12280
|
* Tier policy (audit H-1): the declared tier is authoritative over a
|
|
12414
12281
|
* rule's severity and evidence claims.
|
|
@@ -12510,8 +12377,7 @@ function enforceTierPolicy(findings, tierByRuleId) {
|
|
|
12510
12377
|
if (EVIDENCE_RANK$1[effective] < EVIDENCE_RANK$1[floor.evidenceLevel]) f.evidenceLevel = floor.evidenceLevel;
|
|
12511
12378
|
}
|
|
12512
12379
|
}
|
|
12513
|
-
|
|
12514
|
-
//#region src/engine/evidence-core.ts
|
|
12380
|
+
// src/engine/evidence-core.ts
|
|
12515
12381
|
/**
|
|
12516
12382
|
* The core's own version — stamped into every record's provenance so a
|
|
12517
12383
|
* consumer can tell which normalization contract produced a record.
|
|
@@ -12578,8 +12444,7 @@ function countEvidence(records) {
|
|
|
12578
12444
|
}
|
|
12579
12445
|
return counts;
|
|
12580
12446
|
}
|
|
12581
|
-
|
|
12582
|
-
//#region src/engine/provenance.ts
|
|
12447
|
+
// src/engine/provenance.ts
|
|
12583
12448
|
const GENERATED_HEADER_RE = /^\s*(?:\/\/|#|\/\*)\s*(?:auto[- ]?generated|generated by|do not edit)/i;
|
|
12584
12449
|
/**
|
|
12585
12450
|
* Codegen fingerprint: the recorder names its first test exactly
|
|
@@ -12620,8 +12485,7 @@ function computeAgenticProfile(files, findings) {
|
|
|
12620
12485
|
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
12486
|
};
|
|
12622
12487
|
}
|
|
12623
|
-
|
|
12624
|
-
//#region src/engine/overlap-dedup.ts
|
|
12488
|
+
// src/engine/overlap-dedup.ts
|
|
12625
12489
|
const TIER_RANK = {
|
|
12626
12490
|
core: 0,
|
|
12627
12491
|
extended: 1,
|
|
@@ -12716,8 +12580,7 @@ function dropOrderBefore(a, b, metaByRuleId) {
|
|
|
12716
12580
|
if (ranksBefore(b, a, metaByRuleId)) return true;
|
|
12717
12581
|
return (a.column ?? Number.MAX_SAFE_INTEGER) < (b.column ?? Number.MAX_SAFE_INTEGER);
|
|
12718
12582
|
}
|
|
12719
|
-
|
|
12720
|
-
//#region src/engine/finding-identity.ts
|
|
12583
|
+
// src/engine/finding-identity.ts
|
|
12721
12584
|
/**
|
|
12722
12585
|
* Correlation identity for before/after comparison: ruleId + file +
|
|
12723
12586
|
* message, deliberately EXCLUDING line — a source edit that shifts a
|
|
@@ -12726,8 +12589,7 @@ function dropOrderBefore(a, b, metaByRuleId) {
|
|
|
12726
12589
|
function findingFingerprint(f) {
|
|
12727
12590
|
return `${f.ruleId}\u0000${f.file}\u0000${f.message}`;
|
|
12728
12591
|
}
|
|
12729
|
-
|
|
12730
|
-
//#region src/engine/correlation-engine.ts
|
|
12592
|
+
// src/engine/correlation-engine.ts
|
|
12731
12593
|
const EVIDENCE_RANK = {
|
|
12732
12594
|
E0: 0,
|
|
12733
12595
|
E1: 1,
|
|
@@ -12857,8 +12719,7 @@ function correlateFindings(findings) {
|
|
|
12857
12719
|
});
|
|
12858
12720
|
return conclusions;
|
|
12859
12721
|
}
|
|
12860
|
-
|
|
12861
|
-
//#region src/engine/dependency-graph.ts
|
|
12722
|
+
// src/engine/dependency-graph.ts
|
|
12862
12723
|
/**
|
|
12863
12724
|
* Dependency Graph (ECO-005).
|
|
12864
12725
|
*
|
|
@@ -12995,8 +12856,7 @@ function parseManifest(filePath) {
|
|
|
12995
12856
|
if (filePath.endsWith("pyproject.toml")) return parsePyprojectToml(filePath);
|
|
12996
12857
|
if (filePath.endsWith("pom.xml")) return parsePomXml(filePath);
|
|
12997
12858
|
}
|
|
12998
|
-
|
|
12999
|
-
//#region src/engine/incremental-analysis.ts
|
|
12859
|
+
// src/engine/incremental-analysis.ts
|
|
13000
12860
|
/**
|
|
13001
12861
|
* Semantic inputs that affect the full scan beyond individual file content.
|
|
13002
12862
|
* If any of these changed, an incremental run is NOT safe — a full scan
|
|
@@ -13032,8 +12892,7 @@ function isIncrementalSafe(changedFiles) {
|
|
|
13032
12892
|
reasons
|
|
13033
12893
|
};
|
|
13034
12894
|
}
|
|
13035
|
-
|
|
13036
|
-
//#region src/brand/tokens.ts
|
|
12895
|
+
// src/brand/tokens.ts
|
|
13037
12896
|
/**
|
|
13038
12897
|
* The single source of brand truth.
|
|
13039
12898
|
*
|
|
@@ -13319,8 +13178,7 @@ function deriveScoreState(score) {
|
|
|
13319
13178
|
rune: RUNES.critical
|
|
13320
13179
|
};
|
|
13321
13180
|
}
|
|
13322
|
-
|
|
13323
|
-
//#region src/engine/monorepo-analysis.ts
|
|
13181
|
+
// src/engine/monorepo-analysis.ts
|
|
13324
13182
|
/**
|
|
13325
13183
|
* Package verdict, derived from the one ScoreState band model (BW-104).
|
|
13326
13184
|
* These two constants used to be a private copy of the band boundaries;
|
|
@@ -13427,8 +13285,7 @@ function configurableAggregation(results, config) {
|
|
|
13427
13285
|
strategy: config.weightingStrategy
|
|
13428
13286
|
};
|
|
13429
13287
|
}
|
|
13430
|
-
|
|
13431
|
-
//#region src/lib/fs-atomic.ts
|
|
13288
|
+
// src/lib/fs-atomic.ts
|
|
13432
13289
|
/**
|
|
13433
13290
|
* Atomic file writes (audit S9).
|
|
13434
13291
|
*
|
|
@@ -13511,8 +13368,7 @@ function renameWithWindowsRetry(from, to) {
|
|
|
13511
13368
|
throw err;
|
|
13512
13369
|
}
|
|
13513
13370
|
}
|
|
13514
|
-
|
|
13515
|
-
//#region src/engine/scan-cache.ts
|
|
13371
|
+
// src/engine/scan-cache.ts
|
|
13516
13372
|
/**
|
|
13517
13373
|
* Local incremental scan cache (Beta-to-Stable 1.0 plan, M5.2 / A-2).
|
|
13518
13374
|
*
|
|
@@ -13722,8 +13578,7 @@ function createScanCache(root) {
|
|
|
13722
13578
|
}
|
|
13723
13579
|
};
|
|
13724
13580
|
}
|
|
13725
|
-
|
|
13726
|
-
//#region src/reporter/theme.ts
|
|
13581
|
+
// src/reporter/theme.ts
|
|
13727
13582
|
/**
|
|
13728
13583
|
* Norse-forge theme system. Pure string-in → string-out helpers.
|
|
13729
13584
|
* Respects NO_COLOR and non-TTY via `palette(isTTY)` — every renderer
|
|
@@ -13823,8 +13678,7 @@ function rgb([r, g, b]) {
|
|
|
13823
13678
|
function palette(enabled) {
|
|
13824
13679
|
return enabled ? on : off;
|
|
13825
13680
|
}
|
|
13826
|
-
|
|
13827
|
-
//#region src/reporter/ui.ts
|
|
13681
|
+
// src/reporter/ui.ts
|
|
13828
13682
|
/**
|
|
13829
13683
|
* Neutral context for direct calls (tests, library use) and as the
|
|
13830
13684
|
* safe default when a command runner is invoked without wiring: no
|
|
@@ -13849,8 +13703,7 @@ function sectionHeader(title, ui) {
|
|
|
13849
13703
|
const glyph = ui.ascii ? "=" : "▍";
|
|
13850
13704
|
return ` ${ui.p.accent(`${glyph} ${title}`)}`;
|
|
13851
13705
|
}
|
|
13852
|
-
|
|
13853
|
-
//#region src/forensics/classify.ts
|
|
13706
|
+
// src/forensics/classify.ts
|
|
13854
13707
|
/**
|
|
13855
13708
|
* Strong-signal marker tables. Kept deliberately narrow: a marker here
|
|
13856
13709
|
* is a named family fact, not a vibe. Unknown text yields no signal —
|
|
@@ -13970,8 +13823,7 @@ function classifyForensicVerdict(signals) {
|
|
|
13970
13823
|
signals: signalCounts
|
|
13971
13824
|
};
|
|
13972
13825
|
}
|
|
13973
|
-
|
|
13974
|
-
//#region src/forensics/analyze.ts
|
|
13826
|
+
// src/forensics/analyze.ts
|
|
13975
13827
|
const ui$5 = plainContext();
|
|
13976
13828
|
const MAX_RECORDS = 1e5;
|
|
13977
13829
|
function analyze(records, source) {
|
|
@@ -14134,8 +13986,7 @@ function renderFlakyMd(report) {
|
|
|
14134
13986
|
lines.push("");
|
|
14135
13987
|
return lines.join("\n");
|
|
14136
13988
|
}
|
|
14137
|
-
|
|
14138
|
-
//#region src/forensics/evidence-hygiene.ts
|
|
13989
|
+
// src/forensics/evidence-hygiene.ts
|
|
14139
13990
|
/**
|
|
14140
13991
|
* Evidence hygiene — secret redaction + control-char sanitization
|
|
14141
13992
|
* (ENGINE-012).
|
|
@@ -14197,8 +14048,7 @@ function sanitizeErrorText(text, options) {
|
|
|
14197
14048
|
if (result.length > maxLength) result = result.slice(0, maxLength) + `[truncated at ${maxLength} chars]`;
|
|
14198
14049
|
return result;
|
|
14199
14050
|
}
|
|
14200
|
-
|
|
14201
|
-
//#region src/forensics/trace.ts
|
|
14051
|
+
// src/forensics/trace.ts
|
|
14202
14052
|
/**
|
|
14203
14053
|
* Playwright trace ingester (product-gap master plan §9 R5 / WI-17).
|
|
14204
14054
|
*
|
|
@@ -14391,8 +14241,7 @@ function parseTraceArtifact(bytes, artifactName) {
|
|
|
14391
14241
|
const { actions } = parseTraceNdjson(text);
|
|
14392
14242
|
return traceActionsToRecord(actions, artifactName);
|
|
14393
14243
|
}
|
|
14394
|
-
|
|
14395
|
-
//#region src/forensics/parse-jest-json.ts
|
|
14244
|
+
// src/forensics/parse-jest-json.ts
|
|
14396
14245
|
function toStatus$2(raw) {
|
|
14397
14246
|
switch (raw) {
|
|
14398
14247
|
case "passed": return "passed";
|
|
@@ -14452,8 +14301,7 @@ function parseJestJson(json) {
|
|
|
14452
14301
|
}
|
|
14453
14302
|
return out;
|
|
14454
14303
|
}
|
|
14455
|
-
|
|
14456
|
-
//#region src/forensics/parse-junit.ts
|
|
14304
|
+
// src/forensics/parse-junit.ts
|
|
14457
14305
|
const MAX_INPUT$1 = 20971520;
|
|
14458
14306
|
function decodeEntities$1(s) {
|
|
14459
14307
|
return s.replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("'", "'").replaceAll("&", "&");
|
|
@@ -14495,14 +14343,25 @@ function parseJunitXml(xml) {
|
|
|
14495
14343
|
const skipped = /<skipped\b/i.test(inner);
|
|
14496
14344
|
const errors = [];
|
|
14497
14345
|
if (failed) {
|
|
14498
|
-
const
|
|
14499
|
-
|
|
14500
|
-
|
|
14501
|
-
|
|
14346
|
+
const openRe = /<(?:failure|error)\b/gi;
|
|
14347
|
+
const endRe = /<\/(?:failure|error)\s*>/gi;
|
|
14348
|
+
let open;
|
|
14349
|
+
while ((open = openRe.exec(inner)) !== null) {
|
|
14350
|
+
const gt = inner.indexOf(">", open.index);
|
|
14351
|
+
if (gt === -1) break;
|
|
14352
|
+
const attrsStr = inner.slice(open.index, gt);
|
|
14353
|
+
if (attrsStr.endsWith("/")) {
|
|
14354
|
+
openRe.lastIndex = gt + 1;
|
|
14355
|
+
continue;
|
|
14356
|
+
}
|
|
14357
|
+
endRe.lastIndex = gt + 1;
|
|
14358
|
+
const end = endRe.exec(inner);
|
|
14359
|
+
if (end === null) break;
|
|
14502
14360
|
const msgAttr = /\smessage\s*=\s*"([^"]*)"/i.exec(attrsStr)?.[1];
|
|
14503
14361
|
if (msgAttr) errors.push(sanitizeErrorText(decodeEntities$1(msgAttr)));
|
|
14504
|
-
const body =
|
|
14362
|
+
const body = inner.slice(gt + 1, end.index).trim();
|
|
14505
14363
|
if (body && body !== msgAttr) errors.push(sanitizeErrorText(decodeEntities$1(body)));
|
|
14364
|
+
openRe.lastIndex = end.index + end[0].length;
|
|
14506
14365
|
}
|
|
14507
14366
|
const selfCloseRe = /<(?:failure|error)\b([^>]*)\/>/gi;
|
|
14508
14367
|
let scMatch;
|
|
@@ -14526,8 +14385,7 @@ function parseJunitXml(xml) {
|
|
|
14526
14385
|
}
|
|
14527
14386
|
return out;
|
|
14528
14387
|
}
|
|
14529
|
-
|
|
14530
|
-
//#region src/forensics/parse-trx.ts
|
|
14388
|
+
// src/forensics/parse-trx.ts
|
|
14531
14389
|
const MAX_INPUT = 20971520;
|
|
14532
14390
|
function decodeEntities(s) {
|
|
14533
14391
|
return s.replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("'", "'").replaceAll("&", "&");
|
|
@@ -14638,11 +14496,24 @@ function parseTrxXml(xml) {
|
|
|
14638
14496
|
if (id !== void 0 && name !== void 0) byTestId.set(id, name);
|
|
14639
14497
|
}
|
|
14640
14498
|
const records = [];
|
|
14641
|
-
const
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14499
|
+
const startRe = /<UnitTestResult\b/gi;
|
|
14500
|
+
const starts = [];
|
|
14501
|
+
let s;
|
|
14502
|
+
while ((s = startRe.exec(xml)) !== null) {
|
|
14503
|
+
starts.push(s.index);
|
|
14504
|
+
if (starts.length > 2e5) break;
|
|
14505
|
+
}
|
|
14506
|
+
const resultsEnd = xml.search(/<\/Results>/i);
|
|
14507
|
+
for (let i = 0; i < starts.length; i++) {
|
|
14508
|
+
const start = starts[i];
|
|
14509
|
+
const tagEnd = xml.indexOf(">", start);
|
|
14510
|
+
if (tagEnd === -1) break;
|
|
14511
|
+
const selfClosing = xml[tagEnd - 1] === "/";
|
|
14512
|
+
const attrs = xml.slice(start + 15, selfClosing ? tagEnd - 1 : tagEnd);
|
|
14513
|
+
const next = starts[i + 1] ?? -1;
|
|
14514
|
+
let bodyEnd = next === -1 ? xml.length : next;
|
|
14515
|
+
if (resultsEnd > tagEnd && resultsEnd < bodyEnd) bodyEnd = resultsEnd;
|
|
14516
|
+
const inner = selfClosing ? "" : xml.slice(tagEnd + 1, bodyEnd);
|
|
14646
14517
|
const testName = attr(attrs, "testName") ?? attr(attrs, "testId");
|
|
14647
14518
|
if (testName === void 0) continue;
|
|
14648
14519
|
const attempt = {
|
|
@@ -14650,7 +14521,8 @@ function parseTrxXml(xml) {
|
|
|
14650
14521
|
status: mapTrxOutcome(attr(attrs, "outcome")),
|
|
14651
14522
|
durationMs: parseDuration(attr(attrs, "duration"))
|
|
14652
14523
|
};
|
|
14653
|
-
const
|
|
14524
|
+
const messageAt = inner.search(/<Message\b/i);
|
|
14525
|
+
const error = messageAt === -1 ? void 0 : messageOf(inner, messageAt);
|
|
14654
14526
|
records.push({
|
|
14655
14527
|
file: "unknown",
|
|
14656
14528
|
title: testName,
|
|
@@ -14662,8 +14534,7 @@ function parseTrxXml(xml) {
|
|
|
14662
14534
|
}
|
|
14663
14535
|
return records;
|
|
14664
14536
|
}
|
|
14665
|
-
|
|
14666
|
-
//#region src/forensics/parse-playwright-json.ts
|
|
14537
|
+
// src/forensics/parse-playwright-json.ts
|
|
14667
14538
|
const MAX_DEPTH = 32;
|
|
14668
14539
|
function toStatus$1(raw) {
|
|
14669
14540
|
switch (raw) {
|
|
@@ -14728,8 +14599,7 @@ function parsePlaywrightJson(json) {
|
|
|
14728
14599
|
walk(root, 0);
|
|
14729
14600
|
return out;
|
|
14730
14601
|
}
|
|
14731
|
-
|
|
14732
|
-
//#region src/forensics/parse-vitest-json.ts
|
|
14602
|
+
// src/forensics/parse-vitest-json.ts
|
|
14733
14603
|
function toStatus(raw) {
|
|
14734
14604
|
switch (raw) {
|
|
14735
14605
|
case "passed": return "passed";
|
|
@@ -14783,8 +14653,7 @@ function parseVitestJson(json) {
|
|
|
14783
14653
|
}
|
|
14784
14654
|
return out;
|
|
14785
14655
|
}
|
|
14786
|
-
|
|
14787
|
-
//#region src/forensics/parse-har.ts
|
|
14656
|
+
// src/forensics/parse-har.ts
|
|
14788
14657
|
const MAX_ENTRIES = 2e4;
|
|
14789
14658
|
function sanitizeTitleUrl(url) {
|
|
14790
14659
|
try {
|
|
@@ -14869,8 +14738,7 @@ function parseHarJsonDetailed(json) {
|
|
|
14869
14738
|
truncated
|
|
14870
14739
|
};
|
|
14871
14740
|
}
|
|
14872
|
-
|
|
14873
|
-
//#region src/forensics/run.ts
|
|
14741
|
+
// src/forensics/run.ts
|
|
14874
14742
|
/**
|
|
14875
14743
|
* Runtime evidence entry point (R4) — the library behind `explain
|
|
14876
14744
|
* --evidence`. The `forensics` verb is retired in the v6 carve; this module
|
|
@@ -15161,8 +15029,7 @@ function loadSuppressions(root, now = /* @__PURE__ */ new Date()) {
|
|
|
15161
15029
|
entries
|
|
15162
15030
|
};
|
|
15163
15031
|
}
|
|
15164
|
-
|
|
15165
|
-
//#region src/plugins/reserved-prefixes.ts
|
|
15032
|
+
// src/plugins/reserved-prefixes.ts
|
|
15166
15033
|
/**
|
|
15167
15034
|
* Core-owned rule-ID prefixes that external rule sources (npm plugins,
|
|
15168
15035
|
* JS modules, JSON manifests) may never claim — one shared law.
|
|
@@ -15192,8 +15059,7 @@ function isReservedPrefix(ruleId) {
|
|
|
15192
15059
|
const upper = ruleId.toUpperCase();
|
|
15193
15060
|
return RESERVED_PREFIXES.some((p) => upper.startsWith(p));
|
|
15194
15061
|
}
|
|
15195
|
-
|
|
15196
|
-
//#region src/plugins/load.ts
|
|
15062
|
+
// src/plugins/load.ts
|
|
15197
15063
|
/**
|
|
15198
15064
|
* Plugin API (Upgrade-Plan-v3 Phase 6, tier-2 item #1).
|
|
15199
15065
|
*
|
|
@@ -15299,8 +15165,7 @@ function loadPlugins(root, gateOpen = false) {
|
|
|
15299
15165
|
}
|
|
15300
15166
|
return result;
|
|
15301
15167
|
}
|
|
15302
|
-
|
|
15303
|
-
//#region src/plugins/local-rules.ts
|
|
15168
|
+
// src/plugins/local-rules.ts
|
|
15304
15169
|
/**
|
|
15305
15170
|
* Local plugin/rule contract (Verification Trust Evolution Plan §18 —
|
|
15306
15171
|
* Local Extensibility).
|
|
@@ -15612,8 +15477,7 @@ async function loadModuleRules(path, result) {
|
|
|
15612
15477
|
result.rules.push(rule);
|
|
15613
15478
|
}
|
|
15614
15479
|
}
|
|
15615
|
-
|
|
15616
|
-
//#region src/plugins/trust-gate.ts
|
|
15480
|
+
// src/plugins/trust-gate.ts
|
|
15617
15481
|
/**
|
|
15618
15482
|
* Plugin trust gate (audit-remediation-master-plan.md, locked decision C2).
|
|
15619
15483
|
*
|
|
@@ -15649,8 +15513,7 @@ function renderGateNotice(skipped) {
|
|
|
15649
15513
|
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
15514
|
return lines.join("\n");
|
|
15651
15515
|
}
|
|
15652
|
-
|
|
15653
|
-
//#region src/engine/scan-pipeline.ts
|
|
15516
|
+
// src/engine/scan-pipeline.ts
|
|
15654
15517
|
/**
|
|
15655
15518
|
* Canonical scan pipeline (M6 — blueprint §9.2).
|
|
15656
15519
|
*
|
|
@@ -15710,9 +15573,10 @@ async function buildUniversalRules(root, strict, opts = {}) {
|
|
|
15710
15573
|
for (const p of plugins) for (const r of p.rules) if (r.tier) tierByRuleId.set(r.id, r.tier);
|
|
15711
15574
|
for (const r of local.rules) if (r.tier) tierByRuleId.set(r.id, r.tier);
|
|
15712
15575
|
let rules = [...UNIVERSAL_RULES, ...externalRules];
|
|
15713
|
-
const
|
|
15576
|
+
const quarantined = rules.filter((r) => tierByRuleId.get(r.id) === "quarantine").map((r) => r.id).sort();
|
|
15577
|
+
const withheldRuleIds = strict ? [] : quarantined;
|
|
15714
15578
|
if (!strict) rules = rules.filter((r) => tierByRuleId.get(r.id) !== "quarantine");
|
|
15715
|
-
const rulesWithheld =
|
|
15579
|
+
const rulesWithheld = withheldRuleIds.length;
|
|
15716
15580
|
const rulesApplied = rules.length;
|
|
15717
15581
|
const pluginMeta = [...plugins.map((p) => ({
|
|
15718
15582
|
name: p.name,
|
|
@@ -16109,12 +15973,28 @@ function applyPostScanProcessing(findings, workspace, args, hooks, scanRoot, dec
|
|
|
16109
15973
|
const runtimeIncomplete = discoveredReport !== void 0 && discoveredReport.report.analysisComplete !== true;
|
|
16110
15974
|
let forensicVerdicts;
|
|
16111
15975
|
let evidenceRecords = [];
|
|
16112
|
-
if (discoveredReport && discoveredReport.report.analysisComplete === true)
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
|
|
15976
|
+
if (discoveredReport && discoveredReport.report.analysisComplete === true) {
|
|
15977
|
+
const before = findings.map((f) => ({
|
|
15978
|
+
runtimeCorroboration: f.runtimeCorroboration,
|
|
15979
|
+
trustLevel: f.trustLevel
|
|
15980
|
+
}));
|
|
15981
|
+
try {
|
|
15982
|
+
const records = buildEvidenceRecords(discoveredReport.report, discoveredReport.path);
|
|
15983
|
+
stampRuntimeCorroboration(findings, discoveredReport.report, workspace.root);
|
|
15984
|
+
forensicVerdicts = summarizeForensicVerdicts(discoveredReport.report);
|
|
15985
|
+
evidenceRecords = records;
|
|
15986
|
+
} catch {
|
|
15987
|
+
findings.forEach((f, i) => {
|
|
15988
|
+
const prev = before[i];
|
|
15989
|
+
if (prev?.runtimeCorroboration === void 0) delete f.runtimeCorroboration;
|
|
15990
|
+
else f.runtimeCorroboration = prev.runtimeCorroboration;
|
|
15991
|
+
if (prev?.trustLevel === void 0) delete f.trustLevel;
|
|
15992
|
+
else f.trustLevel = prev.trustLevel;
|
|
15993
|
+
});
|
|
15994
|
+
forensicVerdicts = void 0;
|
|
15995
|
+
evidenceRecords = [];
|
|
15996
|
+
recordDegradation("runtime-corroboration-failed");
|
|
15997
|
+
}
|
|
16118
15998
|
}
|
|
16119
15999
|
return {
|
|
16120
16000
|
evidenceRecords,
|
|
@@ -16547,8 +16427,7 @@ async function runScanInProject(args, hooks = {}) {
|
|
|
16547
16427
|
resetTsMorphProject();
|
|
16548
16428
|
return result;
|
|
16549
16429
|
}
|
|
16550
|
-
|
|
16551
|
-
//#region src/cli-io.ts
|
|
16430
|
+
// src/cli-io.ts
|
|
16552
16431
|
/**
|
|
16553
16432
|
* The ONE error-to-message derivation in `src/`.
|
|
16554
16433
|
*
|
|
@@ -16707,8 +16586,7 @@ function buildMachineContract(result) {
|
|
|
16707
16586
|
...result.forensicVerdicts !== void 0 ? { forensicVerdicts: result.forensicVerdicts } : {}
|
|
16708
16587
|
};
|
|
16709
16588
|
}
|
|
16710
|
-
|
|
16711
|
-
//#region src/commands/fixture-example.ts
|
|
16589
|
+
// src/commands/fixture-example.ts
|
|
16712
16590
|
/**
|
|
16713
16591
|
* Shared example-fixture selection for `mjolnir explain` (explain.ts)
|
|
16714
16592
|
* and the generated rule docs (rule-docs.ts) — one chooser so both
|
|
@@ -16809,8 +16687,7 @@ function explainRule(ruleId, fixturesRoot) {
|
|
|
16809
16687
|
exampleFixtureRelPath: relative(fixturesRoot, fixturePath).replaceAll("\\", "/")
|
|
16810
16688
|
};
|
|
16811
16689
|
}
|
|
16812
|
-
|
|
16813
|
-
//#region src/engine/resolution.ts
|
|
16690
|
+
// src/engine/resolution.ts
|
|
16814
16691
|
/** Correlation identity (v1-compatible): ruleId\0file\0message. */
|
|
16815
16692
|
const fingerprint$1 = findingFingerprint;
|
|
16816
16693
|
/**
|
|
@@ -16961,8 +16838,7 @@ function diffAgainstBaseline(result, baseline) {
|
|
|
16961
16838
|
unchangedCount
|
|
16962
16839
|
};
|
|
16963
16840
|
}
|
|
16964
|
-
|
|
16965
|
-
//#region src/commands/verify.ts
|
|
16841
|
+
// src/commands/verify.ts
|
|
16966
16842
|
/**
|
|
16967
16843
|
* `mjolnir ci verify` — the agent-loop digest (product-gap-remediation
|
|
16968
16844
|
* master plan P7, plan 1788853205786 — flag agent, decision 6).
|
|
@@ -17109,8 +16985,7 @@ function renderTriageWorkflowJson(report) {
|
|
|
17109
16985
|
rows: workflowRows(report)
|
|
17110
16986
|
}, null, 2) + "\n";
|
|
17111
16987
|
}
|
|
17112
|
-
|
|
17113
|
-
//#region src/commands/pw-report.ts
|
|
16988
|
+
// src/commands/pw-report.ts
|
|
17114
16989
|
const ui = plainContext();
|
|
17115
16990
|
function summarizePwRun(report) {
|
|
17116
16991
|
const slowest = [...report.verdicts].sort((a, b) => b.totalDurationMs - a.totalDurationMs).slice(0, 5).map((v) => ({
|
|
@@ -17144,8 +17019,7 @@ function renderPwRunSummary(s) {
|
|
|
17144
17019
|
}
|
|
17145
17020
|
return lines.join("\n");
|
|
17146
17021
|
}
|
|
17147
|
-
|
|
17148
|
-
//#region src/mcp/server.ts
|
|
17022
|
+
// src/mcp/server.ts
|
|
17149
17023
|
/**
|
|
17150
17024
|
* MCP stdio transport (blueprint §21 — Phase 4, PURE TRANSPORT).
|
|
17151
17025
|
*
|
|
@@ -17163,7 +17037,10 @@ function renderPwRunSummary(s) {
|
|
|
17163
17037
|
* - parameter size caps; ONE scan in flight (serialized queue) reusing
|
|
17164
17038
|
* the pipeline's own budgets; zero network (no fetches anywhere); no
|
|
17165
17039
|
* shell execution beyond Mjölnir's own verbs.
|
|
17166
|
-
* - filesystem boundary
|
|
17040
|
+
* - filesystem boundary: the tools are read-only, and with no further
|
|
17041
|
+
* configuration they accept any path the parent process passes (the
|
|
17042
|
+
* parent is the trust boundary). `MJOLNIR_WORKSPACE_ROOT` confines every
|
|
17043
|
+
* path argument to that directory, symlinks resolved.
|
|
17167
17044
|
* - the plugin trust gate applies unchanged (--enable-plugins is NOT
|
|
17168
17045
|
* exposed over MCP: tools run with the gate CLOSED unless the server
|
|
17169
17046
|
* was started with MJOLNIR_ENABLE_PLUGINS=1 in its own environment).
|
|
@@ -17173,12 +17050,27 @@ const MAX_PARAM_BYTES = 65536;
|
|
|
17173
17050
|
const MAX_DURATION_MS = 36e5;
|
|
17174
17051
|
const DEFAULT_MAX_DURATION_MS = 6e5;
|
|
17175
17052
|
const PROTOCOL_VERSION = "2025-06-18";
|
|
17176
|
-
|
|
17053
|
+
/** The real path of `p`, or its lexical resolution when it does not exist. */
|
|
17054
|
+
function realOrResolved(p) {
|
|
17055
|
+
try {
|
|
17056
|
+
return realpathSync.native(resolve(p));
|
|
17057
|
+
} catch {
|
|
17058
|
+
return resolve(p);
|
|
17059
|
+
}
|
|
17060
|
+
}
|
|
17061
|
+
/**
|
|
17062
|
+
* The optional workspace root. When set, every path argument must resolve
|
|
17063
|
+
* INSIDE it after symlinks are followed.
|
|
17064
|
+
*
|
|
17065
|
+
* Unset is open by design and is documented that way in the header: the MCP
|
|
17066
|
+
* tools are read-only and the stdio parent is the trust boundary. Setting
|
|
17067
|
+
* `MJOLNIR_WORKSPACE_ROOT` is how a host confines them.
|
|
17068
|
+
*/
|
|
17069
|
+
const CONFIGURED_ROOT = process.env.MJOLNIR_WORKSPACE_ROOT ? realOrResolved(process.env.MJOLNIR_WORKSPACE_ROOT) : null;
|
|
17177
17070
|
function isPathAllowed(path) {
|
|
17178
17071
|
if (!CONFIGURED_ROOT) return true;
|
|
17179
|
-
const
|
|
17180
|
-
|
|
17181
|
-
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
17072
|
+
const rel = relative(CONFIGURED_ROOT, realOrResolved(path));
|
|
17073
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
17182
17074
|
}
|
|
17183
17075
|
/** JSON-RPC error codes (subset used here). */
|
|
17184
17076
|
const MCP_ERRORS = {
|
|
@@ -17655,8 +17547,7 @@ async function handleMcpMessage(msg) {
|
|
|
17655
17547
|
}
|
|
17656
17548
|
};
|
|
17657
17549
|
}
|
|
17658
|
-
|
|
17659
|
-
//#region src/mcp/transport.ts
|
|
17550
|
+
// src/mcp/transport.ts
|
|
17660
17551
|
/**
|
|
17661
17552
|
* MCP stdio transport loop (blueprint §21 — the framing around the
|
|
17662
17553
|
* pure-transport server).
|
|
@@ -17753,8 +17644,7 @@ async function handleStdioLine(line, write) {
|
|
|
17753
17644
|
}
|
|
17754
17645
|
write(`${JSON.stringify(response)}\n`);
|
|
17755
17646
|
}
|
|
17756
|
-
|
|
17757
|
-
//#region src/integrations/sentry.ts
|
|
17647
|
+
// src/integrations/sentry.ts
|
|
17758
17648
|
/**
|
|
17759
17649
|
* Sentry crash reporting — opt-in, and inert unless a DSN is configured.
|
|
17760
17650
|
*
|
|
@@ -17880,8 +17770,7 @@ async function flushSentry(timeoutMs = FLUSH_TIMEOUT_MS) {
|
|
|
17880
17770
|
return false;
|
|
17881
17771
|
}
|
|
17882
17772
|
}
|
|
17883
|
-
|
|
17884
|
-
//#region src/mcp/stdio.ts
|
|
17773
|
+
// src/mcp/stdio.ts
|
|
17885
17774
|
/**
|
|
17886
17775
|
* The standalone MCP stdio binary (blueprint §21), built to
|
|
17887
17776
|
* `dist/mcp/stdio.mjs` and run by `npm run mcp`.
|
|
@@ -17906,7 +17795,6 @@ runStdioTransport(process.stdin, process.stdout).then(() => process.exit(0)).cat
|
|
|
17906
17795
|
process.stderr.write(`mjolnir mcp fatal: ${err instanceof Error ? err.message : err}\n`);
|
|
17907
17796
|
process.exitCode = 20;
|
|
17908
17797
|
});
|
|
17909
|
-
//#endregion
|
|
17910
17798
|
export {};
|
|
17911
17799
|
|
|
17912
17800
|
//# sourceMappingURL=stdio.mjs.map
|