mjolnir-qa 1.0.1 → 1.0.2
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 +6 -0
- package/dist/cli.d.mts +55 -1
- package/dist/cli.mjs +287 -9
- package/dist/mcp/stdio.mjs +287 -9
- package/package.json +2 -1
package/dist/mcp/stdio.mjs
CHANGED
|
@@ -118,12 +118,13 @@ const DEDUCTIONS = {
|
|
|
118
118
|
*/
|
|
119
119
|
const EVIDENCE_CORE_VERSION = "evidence-core@1";
|
|
120
120
|
/** Deterministic canonical order: file → line (absent last) → title → source. */
|
|
121
|
-
function
|
|
121
|
+
function compareEvidenceRecords(a, b) {
|
|
122
122
|
if (a.file !== b.file) return a.file < b.file ? -1 : 1;
|
|
123
123
|
const la = a.line ?? Number.POSITIVE_INFINITY;
|
|
124
124
|
const lb = b.line ?? Number.POSITIVE_INFINITY;
|
|
125
125
|
if (la !== lb) return la - lb;
|
|
126
126
|
if (a.title !== b.title) return a.title < b.title ? -1 : 1;
|
|
127
|
+
if (a.source !== b.source) return a.source < b.source ? -1 : 1;
|
|
127
128
|
return 0;
|
|
128
129
|
}
|
|
129
130
|
function normalizeOne(report, artifact, v) {
|
|
@@ -158,7 +159,7 @@ function normalizeOne(report, artifact, v) {
|
|
|
158
159
|
* record list — report verdict order never leaks into the output.
|
|
159
160
|
*/
|
|
160
161
|
function buildEvidenceRecords(report, artifact) {
|
|
161
|
-
return report.verdicts.map((v) => normalizeOne(report, artifact, v)).sort(
|
|
162
|
+
return report.verdicts.map((v) => normalizeOne(report, artifact, v)).sort(compareEvidenceRecords);
|
|
162
163
|
}
|
|
163
164
|
/**
|
|
164
165
|
* The test whose declaration span contains `line` — the exact matching
|
|
@@ -178,8 +179,8 @@ function findTestAt(records, file, line) {
|
|
|
178
179
|
for (const r of inFile) if (r.line === void 0) return void 0;
|
|
179
180
|
let match;
|
|
180
181
|
for (const r of inFile) {
|
|
181
|
-
const
|
|
182
|
-
if (
|
|
182
|
+
const l = r.line;
|
|
183
|
+
if (l <= line && (match === void 0 || match.line <= l)) match = r;
|
|
183
184
|
}
|
|
184
185
|
return match;
|
|
185
186
|
}
|
|
@@ -3320,6 +3321,10 @@ const focusedTestCommitted = defineRule({
|
|
|
3320
3321
|
falsePositiveRisk: "low",
|
|
3321
3322
|
autofix: true,
|
|
3322
3323
|
detectionStrategy: "LEXICAL",
|
|
3324
|
+
strategyJustification: {
|
|
3325
|
+
reasonCode: "runner-semantic",
|
|
3326
|
+
detail: ".only/focus is runner skip-scheduling state; the detector matches the runner's own member-call tokens (.only/.fit/fdescribe) on the code-only text — lexical precision equals the AST call-shape here, and the §13.2 fallback keeps parity"
|
|
3327
|
+
},
|
|
3323
3328
|
introduced: "0.1.0",
|
|
3324
3329
|
tier: "quarantine",
|
|
3325
3330
|
suiteInvalidating: true,
|
|
@@ -3380,6 +3385,10 @@ const skippedTest = defineRule({
|
|
|
3380
3385
|
falsePositiveRisk: "medium",
|
|
3381
3386
|
autofix: false,
|
|
3382
3387
|
detectionStrategy: "LEXICAL",
|
|
3388
|
+
strategyJustification: {
|
|
3389
|
+
reasonCode: "runner-semantic",
|
|
3390
|
+
detail: "skip/xfail/ignore are runner skip-state annotations whose forms are runner API tokens (it.skip, xit, t.skip, @unittest.skip); the detector matches those exact tokens on the code-only text"
|
|
3391
|
+
},
|
|
3383
3392
|
introduced: "0.1.0",
|
|
3384
3393
|
tier: "quarantine",
|
|
3385
3394
|
run(ctx) {
|
|
@@ -3497,6 +3506,10 @@ const noAssertions = defineRule({
|
|
|
3497
3506
|
falsePositiveRisk: "medium",
|
|
3498
3507
|
autofix: false,
|
|
3499
3508
|
detectionStrategy: "LEXICAL",
|
|
3509
|
+
strategyJustification: {
|
|
3510
|
+
reasonCode: "runner-semantic",
|
|
3511
|
+
detail: "assertion-less test bodies are runner-outcome semantics (the runner reports a pass that proves nothing); the detector matches the test-def plus body shapes on the code-only text"
|
|
3512
|
+
},
|
|
3500
3513
|
introduced: "0.1.0",
|
|
3501
3514
|
tier: "quarantine",
|
|
3502
3515
|
run(ctx) {
|
|
@@ -3571,6 +3584,10 @@ const hardSleep = defineRule({
|
|
|
3571
3584
|
falsePositiveRisk: "low",
|
|
3572
3585
|
autofix: false,
|
|
3573
3586
|
detectionStrategy: "LEXICAL",
|
|
3587
|
+
strategyJustification: {
|
|
3588
|
+
reasonCode: "runner-semantic",
|
|
3589
|
+
detail: "hard-sleep is behavioral wait-shape matching (the wait call plus its interaction context), not a single node; the detector's pattern+wait-shape oracle is the recorded design (§12.1), and the hard-sleep family's structural path carries the depth where available"
|
|
3590
|
+
},
|
|
3574
3591
|
detectionNotes: "regex pattern + behavioral wait-shape matching",
|
|
3575
3592
|
introduced: "0.1.0",
|
|
3576
3593
|
tier: "extended",
|
|
@@ -3636,6 +3653,10 @@ const retryAbuse = defineRule({
|
|
|
3636
3653
|
falsePositiveRisk: "low",
|
|
3637
3654
|
autofix: false,
|
|
3638
3655
|
detectionStrategy: "LEXICAL",
|
|
3656
|
+
strategyJustification: {
|
|
3657
|
+
reasonCode: "runner-semantic",
|
|
3658
|
+
detail: "retry abuse is the runner's retry contract (jest.retries, vitest retry, playwright retries); the detector matches the runner's retry API tokens across runners — each an exact key"
|
|
3659
|
+
},
|
|
3639
3660
|
introduced: "0.1.0",
|
|
3640
3661
|
tier: "quarantine",
|
|
3641
3662
|
run(ctx) {
|
|
@@ -3697,6 +3718,10 @@ const emptyTestBody = defineRule({
|
|
|
3697
3718
|
falsePositiveRisk: "low",
|
|
3698
3719
|
autofix: false,
|
|
3699
3720
|
detectionStrategy: "LEXICAL",
|
|
3721
|
+
strategyJustification: {
|
|
3722
|
+
reasonCode: "runner-semantic",
|
|
3723
|
+
detail: "empty test bodies are runner-outcome semantics; the detector matches the test-def plus empty-body shapes on the code-only text — the AST body-shape is the same predicate"
|
|
3724
|
+
},
|
|
3700
3725
|
introduced: "0.1.0",
|
|
3701
3726
|
tier: "quarantine",
|
|
3702
3727
|
overlapWith: ["QA-TEST-003"],
|
|
@@ -3748,6 +3773,10 @@ const tautologicalAssertion = defineRule({
|
|
|
3748
3773
|
falsePositiveRisk: "low",
|
|
3749
3774
|
autofix: false,
|
|
3750
3775
|
detectionStrategy: "LEXICAL",
|
|
3776
|
+
strategyJustification: {
|
|
3777
|
+
reasonCode: "runner-semantic",
|
|
3778
|
+
detail: "tautological assertions (x === x, expect(true)) are assertion-semantics on the code-only text; the detector matches the tautology shapes after comment stripping — the AST call-shape is the same predicate"
|
|
3779
|
+
},
|
|
3751
3780
|
detectionNotes: "AST-stripped text pattern",
|
|
3752
3781
|
introduced: "0.1.0",
|
|
3753
3782
|
tier: "quarantine",
|
|
@@ -3827,6 +3856,10 @@ const unawaitedPromiseAssertion = defineRule({
|
|
|
3827
3856
|
falsePositiveRisk: "low",
|
|
3828
3857
|
autofix: false,
|
|
3829
3858
|
detectionStrategy: "LEXICAL",
|
|
3859
|
+
strategyJustification: {
|
|
3860
|
+
reasonCode: "runner-semantic",
|
|
3861
|
+
detail: "un-awaited promise assertions are runner async semantics; the detector matches the assertion-call shapes inside promise chains on the code-only text — the async contract is runner behavior"
|
|
3862
|
+
},
|
|
3830
3863
|
introduced: "0.2.0",
|
|
3831
3864
|
tier: "quarantine",
|
|
3832
3865
|
run(ctx) {
|
|
@@ -3926,6 +3959,10 @@ const commentedOutTest = defineRule({
|
|
|
3926
3959
|
falsePositiveRisk: "medium",
|
|
3927
3960
|
autofix: false,
|
|
3928
3961
|
detectionStrategy: "LEXICAL",
|
|
3962
|
+
strategyJustification: {
|
|
3963
|
+
reasonCode: "lexical-artifact",
|
|
3964
|
+
detail: "commented-out assertions are lexical artifacts — the comment-wrapped assertion text is the finding itself; the detector matches the shapes on the raw text"
|
|
3965
|
+
},
|
|
3929
3966
|
detectionNotes: "regex heuristic",
|
|
3930
3967
|
introduced: "0.2.0",
|
|
3931
3968
|
tier: "extended",
|
|
@@ -4171,6 +4208,10 @@ const committedDebugArtifacts = defineRule({
|
|
|
4171
4208
|
falsePositiveRisk: "low",
|
|
4172
4209
|
autofix: true,
|
|
4173
4210
|
detectionStrategy: "LEXICAL",
|
|
4211
|
+
strategyJustification: {
|
|
4212
|
+
reasonCode: "exact-key-match",
|
|
4213
|
+
detail: "page.pause() and test.only() are exact Playwright runner tokens; the detector matches the member-call identifiers on the code-only text — closed token set, unique to the defect"
|
|
4214
|
+
},
|
|
4174
4215
|
introduced: "0.1.0",
|
|
4175
4216
|
tier: "core",
|
|
4176
4217
|
run(ctx) {
|
|
@@ -4223,6 +4264,10 @@ const brittleSelectors = defineRule({
|
|
|
4223
4264
|
falsePositiveRisk: "medium",
|
|
4224
4265
|
autofix: false,
|
|
4225
4266
|
detectionStrategy: "LEXICAL",
|
|
4267
|
+
strategyJustification: {
|
|
4268
|
+
reasonCode: "string-content-defect",
|
|
4269
|
+
detail: "brittle selectors ARE string arguments (css=/xpath=/nth-child shapes) — the code-text masking that protects other rules deliberately excludes string content here; the detector reads the string shapes directly (inside-string oracle)"
|
|
4270
|
+
},
|
|
4226
4271
|
detectionNotes: "regex pattern + inside-string oracle",
|
|
4227
4272
|
introduced: "0.1.0",
|
|
4228
4273
|
tier: "quarantine",
|
|
@@ -4520,6 +4565,10 @@ const swallowedExitCode = defineRule({
|
|
|
4520
4565
|
falsePositiveRisk: "low",
|
|
4521
4566
|
autofix: false,
|
|
4522
4567
|
detectionStrategy: "LEXICAL",
|
|
4568
|
+
strategyJustification: {
|
|
4569
|
+
reasonCode: "shell-string-in-config",
|
|
4570
|
+
detail: "exit-code swallowing lives inside workflow run: strings (shell scripts embedded in YAML); the YAML statement IS a string literal — a shell syntax tree of a YAML value adds parsing without adding classification power"
|
|
4571
|
+
},
|
|
4523
4572
|
introduced: "0.1.0",
|
|
4524
4573
|
tier: "extended",
|
|
4525
4574
|
detectorRevision: 2,
|
|
@@ -4572,6 +4621,10 @@ const retryMasking = defineRule({
|
|
|
4572
4621
|
falsePositiveRisk: "low",
|
|
4573
4622
|
autofix: false,
|
|
4574
4623
|
detectionStrategy: "LEXICAL",
|
|
4624
|
+
strategyJustification: {
|
|
4625
|
+
reasonCode: "runner-semantic",
|
|
4626
|
+
detail: "retry masking is defined by the runner's retry semantics, which no language syntax tree represents; the detector matches the runner's own retry keys in workflow YAML where statements are shell strings"
|
|
4627
|
+
},
|
|
4575
4628
|
introduced: "0.1.0",
|
|
4576
4629
|
tier: "extended",
|
|
4577
4630
|
detectorRevision: 2,
|
|
@@ -4755,6 +4808,10 @@ const reportNeverGenerated = defineRule({
|
|
|
4755
4808
|
falsePositiveRisk: "low",
|
|
4756
4809
|
autofix: false,
|
|
4757
4810
|
detectionStrategy: "LEXICAL",
|
|
4811
|
+
strategyJustification: {
|
|
4812
|
+
reasonCode: "runner-semantic",
|
|
4813
|
+
detail: "report generation is a runner side effect of the workflow step sequence, not a syntax tree property; the detector reads the workflow step graph, whose statements are already literal text"
|
|
4814
|
+
},
|
|
4758
4815
|
introduced: "0.1.0",
|
|
4759
4816
|
tier: "quarantine",
|
|
4760
4817
|
detectorRevision: 2,
|
|
@@ -4855,6 +4912,10 @@ const alwaysSuccessStep = defineRule({
|
|
|
4855
4912
|
falsePositiveRisk: "low",
|
|
4856
4913
|
autofix: false,
|
|
4857
4914
|
detectionStrategy: "LEXICAL",
|
|
4915
|
+
strategyJustification: {
|
|
4916
|
+
reasonCode: "runner-semantic",
|
|
4917
|
+
detail: "always()-success is a workflow-step outcome contract, not a code construct; the detector matches the step's run/if keys, which are string fields of the YAML config surface"
|
|
4918
|
+
},
|
|
4858
4919
|
introduced: "0.1.0",
|
|
4859
4920
|
tier: "quarantine",
|
|
4860
4921
|
detectorRevision: 2,
|
|
@@ -5101,6 +5162,10 @@ const pyNoAssertions = defineRule({
|
|
|
5101
5162
|
falsePositiveRisk: "low",
|
|
5102
5163
|
autofix: false,
|
|
5103
5164
|
detectionStrategy: "LEXICAL",
|
|
5165
|
+
strategyJustification: {
|
|
5166
|
+
reasonCode: "runner-semantic",
|
|
5167
|
+
detail: "assertion-less pytest bodies are runner-outcome semantics (the runner reports a pass that proves nothing); the detector matches the test-def plus body shapes on the code-only text — pytest's pass contract is runner behavior"
|
|
5168
|
+
},
|
|
5104
5169
|
introduced: "0.3.0",
|
|
5105
5170
|
tier: "quarantine",
|
|
5106
5171
|
detectorRevision: 3,
|
|
@@ -5184,6 +5249,10 @@ const pyHardSleep = defineRule({
|
|
|
5184
5249
|
falsePositiveRisk: "low",
|
|
5185
5250
|
autofix: false,
|
|
5186
5251
|
detectionStrategy: "LEXICAL",
|
|
5252
|
+
strategyJustification: {
|
|
5253
|
+
reasonCode: "exact-key-match",
|
|
5254
|
+
detail: "time.sleep(n) is an exact stdlib token with a numeric argument; the detector matches the call identifier on the code-only text — closed token, unique to the defect"
|
|
5255
|
+
},
|
|
5187
5256
|
introduced: "0.3.0",
|
|
5188
5257
|
tier: "extended",
|
|
5189
5258
|
overlapWith: ["QA-PY-102"],
|
|
@@ -5229,6 +5298,10 @@ const pySkippedTest = defineRule({
|
|
|
5229
5298
|
falsePositiveRisk: "low",
|
|
5230
5299
|
autofix: false,
|
|
5231
5300
|
detectionStrategy: "LEXICAL",
|
|
5301
|
+
strategyJustification: {
|
|
5302
|
+
reasonCode: "runner-semantic",
|
|
5303
|
+
detail: "pytest.mark.skip/xfail are runner marker decorators — exact runner tokens; a syntax tree re-derives the same call shape with no added classification power"
|
|
5304
|
+
},
|
|
5232
5305
|
introduced: "0.3.0",
|
|
5233
5306
|
run(ctx) {
|
|
5234
5307
|
const text = ctx.codeText ?? ctx.text;
|
|
@@ -5285,6 +5358,10 @@ const pyTautological = defineRule({
|
|
|
5285
5358
|
falsePositiveRisk: "low",
|
|
5286
5359
|
autofix: false,
|
|
5287
5360
|
detectionStrategy: "LEXICAL",
|
|
5361
|
+
strategyJustification: {
|
|
5362
|
+
reasonCode: "runner-semantic",
|
|
5363
|
+
detail: "tautological assertions in Python (assert x == x) are assertion-semantics on the code-only text; the detector matches the tautology shapes — the AST re-derives the same comparison"
|
|
5364
|
+
},
|
|
5288
5365
|
introduced: "0.3.0",
|
|
5289
5366
|
tier: "quarantine",
|
|
5290
5367
|
run(ctx) {
|
|
@@ -5339,6 +5416,10 @@ const pyFocusedTest = defineRule({
|
|
|
5339
5416
|
falsePositiveRisk: "low",
|
|
5340
5417
|
autofix: false,
|
|
5341
5418
|
detectionStrategy: "LEXICAL",
|
|
5419
|
+
strategyJustification: {
|
|
5420
|
+
reasonCode: "runner-semantic",
|
|
5421
|
+
detail: "pytest.skip/xfail/parametrize marks are runner decorators and module-level calls; the detector matches those exact tokens on the code-only text — the semantics are runner skip state"
|
|
5422
|
+
},
|
|
5342
5423
|
introduced: "0.3.0",
|
|
5343
5424
|
tier: "core",
|
|
5344
5425
|
suiteInvalidating: true,
|
|
@@ -5391,6 +5472,10 @@ const pyRaisesWithoutMatch = defineRule({
|
|
|
5391
5472
|
falsePositiveRisk: "medium",
|
|
5392
5473
|
autofix: false,
|
|
5393
5474
|
detectionStrategy: "LEXICAL",
|
|
5475
|
+
strategyJustification: {
|
|
5476
|
+
reasonCode: "runner-semantic",
|
|
5477
|
+
detail: "pytest.raises without match is a runner exception-contract semantic; the detector matches the raises-call plus its argumentless form — the runner's exception contract, not a syntax property"
|
|
5478
|
+
},
|
|
5394
5479
|
introduced: "0.3.0",
|
|
5395
5480
|
tier: "quarantine",
|
|
5396
5481
|
detectorRevision: 3,
|
|
@@ -5470,6 +5555,10 @@ const pyCommentedOutTest = defineRule({
|
|
|
5470
5555
|
falsePositiveRisk: "medium",
|
|
5471
5556
|
autofix: false,
|
|
5472
5557
|
detectionStrategy: "LEXICAL",
|
|
5558
|
+
strategyJustification: {
|
|
5559
|
+
reasonCode: "lexical-artifact",
|
|
5560
|
+
detail: "commented-out test code is a lexical artifact by definition — the text IS the finding (comment-wrapped test bodies); the detector matches the commented shapes on the raw text, which is where the artifact lives"
|
|
5561
|
+
},
|
|
5473
5562
|
detectionNotes: "regex heuristic",
|
|
5474
5563
|
introduced: "0.3.0",
|
|
5475
5564
|
tier: "core",
|
|
@@ -5515,6 +5604,10 @@ const pyBareTruthinessAssert = defineRule({
|
|
|
5515
5604
|
falsePositiveRisk: "medium",
|
|
5516
5605
|
autofix: false,
|
|
5517
5606
|
detectionStrategy: "LEXICAL",
|
|
5607
|
+
strategyJustification: {
|
|
5608
|
+
reasonCode: "runner-semantic",
|
|
5609
|
+
detail: "bare truthiness asserts (assert obj) are assertion-semantics on the code-only text; the detector matches the bare-assert shapes — the AST re-derives the same call"
|
|
5610
|
+
},
|
|
5518
5611
|
introduced: "0.3.0",
|
|
5519
5612
|
tier: "quarantine",
|
|
5520
5613
|
detectorRevision: 3,
|
|
@@ -5583,6 +5676,10 @@ const pyMutableFixture = defineRule({
|
|
|
5583
5676
|
falsePositiveRisk: "medium",
|
|
5584
5677
|
autofix: false,
|
|
5585
5678
|
detectionStrategy: "LEXICAL",
|
|
5679
|
+
strategyJustification: {
|
|
5680
|
+
reasonCode: "runner-semantic",
|
|
5681
|
+
detail: "pytest fixture mutation is fixture-lifecycle semantics (autouse/scope keys plus mutation calls); the detector matches the runner's fixture decorator tokens plus the mutation shapes"
|
|
5682
|
+
},
|
|
5586
5683
|
introduced: "0.3.0",
|
|
5587
5684
|
tier: "core",
|
|
5588
5685
|
run(ctx) {
|
|
@@ -5631,6 +5728,10 @@ const pwWaitForTimeout = defineRule({
|
|
|
5631
5728
|
falsePositiveRisk: "low",
|
|
5632
5729
|
autofix: false,
|
|
5633
5730
|
detectionStrategy: "LEXICAL",
|
|
5731
|
+
strategyJustification: {
|
|
5732
|
+
reasonCode: "family-fallback-lockstep",
|
|
5733
|
+
detail: "the hard-sleep family's §13.2 structural path (AST hook) carries the depth where a tree is available; this lexical path is the mandatory deterministic fallback kept in lockstep — the family's depth is real, the regex is its degraded mode"
|
|
5734
|
+
},
|
|
5634
5735
|
introduced: "0.3.0",
|
|
5635
5736
|
overlapWith: ["QA-TEST-004"],
|
|
5636
5737
|
run(ctx) {
|
|
@@ -5674,6 +5775,10 @@ const pwWaitForLoadEvent = defineRule({
|
|
|
5674
5775
|
falsePositiveRisk: "medium",
|
|
5675
5776
|
autofix: false,
|
|
5676
5777
|
detectionStrategy: "LEXICAL",
|
|
5778
|
+
strategyJustification: {
|
|
5779
|
+
reasonCode: "exact-key-match",
|
|
5780
|
+
detail: "waitForLoadState('load') is an exact Playwright token plus a closed argument enum; the detector matches the call plus its argument — the AST re-derives the same call shape"
|
|
5781
|
+
},
|
|
5677
5782
|
introduced: "0.3.0",
|
|
5678
5783
|
tier: "quarantine",
|
|
5679
5784
|
detectorRevision: 2,
|
|
@@ -5726,6 +5831,10 @@ const pwDeepFrameLocator = defineRule({
|
|
|
5726
5831
|
falsePositiveRisk: "low",
|
|
5727
5832
|
autofix: false,
|
|
5728
5833
|
detectionStrategy: "LEXICAL",
|
|
5834
|
+
strategyJustification: {
|
|
5835
|
+
reasonCode: "exact-key-match",
|
|
5836
|
+
detail: "frameLocator chaining depth is an exact Playwright token sequence; the detector matches the frameLocator call chains — the token sequence is closed and unique to the defect"
|
|
5837
|
+
},
|
|
5729
5838
|
introduced: "0.3.0",
|
|
5730
5839
|
tier: "core",
|
|
5731
5840
|
run(ctx) {
|
|
@@ -5773,6 +5882,10 @@ const pwSerialNoJustification = defineRule({
|
|
|
5773
5882
|
falsePositiveRisk: "low",
|
|
5774
5883
|
autofix: false,
|
|
5775
5884
|
detectionStrategy: "LEXICAL",
|
|
5885
|
+
strategyJustification: {
|
|
5886
|
+
reasonCode: "runner-semantic",
|
|
5887
|
+
detail: "fullyParallel/serial are runner scheduling keys on the config and describe blocks; the detector matches the runner's exact API tokens — the semantics are scheduling, not syntax"
|
|
5888
|
+
},
|
|
5776
5889
|
introduced: "0.3.0",
|
|
5777
5890
|
tier: "core",
|
|
5778
5891
|
run(ctx) {
|
|
@@ -5826,6 +5939,10 @@ const pwConfigRetryAbuse = defineRule({
|
|
|
5826
5939
|
falsePositiveRisk: "low",
|
|
5827
5940
|
autofix: false,
|
|
5828
5941
|
detectionStrategy: "LEXICAL",
|
|
5942
|
+
strategyJustification: {
|
|
5943
|
+
reasonCode: "runner-semantic",
|
|
5944
|
+
detail: "retries in playwright.config.* is a runner top-level option, not a syntax node; the detector reads the config surface whose statements are object-literal keys — exact-key precision"
|
|
5945
|
+
},
|
|
5829
5946
|
detectionNotes: "regex heuristic",
|
|
5830
5947
|
introduced: "0.3.0",
|
|
5831
5948
|
tier: "core",
|
|
@@ -5887,6 +6004,10 @@ const pwNoTraceOnRetry = defineRule({
|
|
|
5887
6004
|
falsePositiveRisk: "low",
|
|
5888
6005
|
autofix: false,
|
|
5889
6006
|
detectionStrategy: "LEXICAL",
|
|
6007
|
+
strategyJustification: {
|
|
6008
|
+
reasonCode: "runner-semantic",
|
|
6009
|
+
detail: "trace/reporter capture is runner lifecycle state set in the config file; the detector reads the config surface's keys and enum values, which are exact matches — a syntax tree adds no semantic the config text lacks"
|
|
6010
|
+
},
|
|
5890
6011
|
detectionNotes: "regex heuristic",
|
|
5891
6012
|
introduced: "0.3.0",
|
|
5892
6013
|
tier: "extended",
|
|
@@ -5940,6 +6061,10 @@ const pwNoProjectSplit = defineRule({
|
|
|
5940
6061
|
falsePositiveRisk: "low",
|
|
5941
6062
|
autofix: false,
|
|
5942
6063
|
detectionStrategy: "LEXICAL",
|
|
6064
|
+
strategyJustification: {
|
|
6065
|
+
reasonCode: "runner-semantic",
|
|
6066
|
+
detail: "project split is a runner config concept (projects array arrangement); the detector reads playwright.config.* keys (adapter-gated), whose object-literal shape is exact-match text"
|
|
6067
|
+
},
|
|
5943
6068
|
detectionNotes: "regex heuristic over playwright.config.* (adapter-gated)",
|
|
5944
6069
|
introduced: "0.3.0",
|
|
5945
6070
|
detectorRevision: 2,
|
|
@@ -5990,6 +6115,10 @@ const pwTrialMisuse = defineRule({
|
|
|
5990
6115
|
falsePositiveRisk: "medium",
|
|
5991
6116
|
autofix: false,
|
|
5992
6117
|
detectionStrategy: "LEXICAL",
|
|
6118
|
+
strategyJustification: {
|
|
6119
|
+
reasonCode: "exact-key-match",
|
|
6120
|
+
detail: "the trial-click shape is an exact Playwright API token pair; the detector matches the call identifier on the code-only text — the token is closed and unique to the defect"
|
|
6121
|
+
},
|
|
5993
6122
|
introduced: "0.3.0",
|
|
5994
6123
|
tier: "core",
|
|
5995
6124
|
run(ctx) {
|
|
@@ -6034,6 +6163,10 @@ const pwStorageStateNoExpiry = defineRule({
|
|
|
6034
6163
|
falsePositiveRisk: "medium",
|
|
6035
6164
|
autofix: false,
|
|
6036
6165
|
detectionStrategy: "LEXICAL",
|
|
6166
|
+
strategyJustification: {
|
|
6167
|
+
reasonCode: "exact-key-match",
|
|
6168
|
+
detail: "storageState is an exact Playwright config/use option token; the detector matches the option key and its value shapes — closed config surface"
|
|
6169
|
+
},
|
|
6037
6170
|
introduced: "0.3.0",
|
|
6038
6171
|
run(ctx) {
|
|
6039
6172
|
const text = ctx.text;
|
|
@@ -6078,6 +6211,10 @@ const pwGlobalSetupSharedState = defineRule({
|
|
|
6078
6211
|
falsePositiveRisk: "medium",
|
|
6079
6212
|
autofix: false,
|
|
6080
6213
|
detectionStrategy: "LEXICAL",
|
|
6214
|
+
strategyJustification: {
|
|
6215
|
+
reasonCode: "runner-semantic",
|
|
6216
|
+
detail: "globalSetup/globalTeardown are runner lifecycle hooks declared in the config file; the detector matches those exact keys — the defect is the runner's execution order, not a code shape"
|
|
6217
|
+
},
|
|
6081
6218
|
detectionNotes: "regex heuristic",
|
|
6082
6219
|
introduced: "0.3.0",
|
|
6083
6220
|
run(ctx) {
|
|
@@ -6130,6 +6267,10 @@ const pwSharedPage = defineRule({
|
|
|
6130
6267
|
falsePositiveRisk: "medium",
|
|
6131
6268
|
autofix: false,
|
|
6132
6269
|
detectionStrategy: "LEXICAL",
|
|
6270
|
+
strategyJustification: {
|
|
6271
|
+
reasonCode: "runner-semantic",
|
|
6272
|
+
detail: "page reuse across tests is runner fixture-lifecycle semantics; the detector matches the page-consumption shapes against the test boundaries — the lifecycle is runner behavior"
|
|
6273
|
+
},
|
|
6133
6274
|
introduced: "0.3.0",
|
|
6134
6275
|
tier: "quarantine",
|
|
6135
6276
|
run(ctx) {
|
|
@@ -6177,6 +6318,10 @@ const qaPw140 = defineRule({
|
|
|
6177
6318
|
falsePositiveRisk: "medium",
|
|
6178
6319
|
autofix: false,
|
|
6179
6320
|
detectionStrategy: "LEXICAL",
|
|
6321
|
+
strategyJustification: {
|
|
6322
|
+
reasonCode: "exact-key-match",
|
|
6323
|
+
detail: "the detector matches a closed, exact runner API token on the code-only text; the token identifies the defect uniquely"
|
|
6324
|
+
},
|
|
6180
6325
|
introduced: "0.3.0",
|
|
6181
6326
|
tier: "core",
|
|
6182
6327
|
run(ctx) {
|
|
@@ -6244,6 +6389,10 @@ const hardcodedBaseUrl = defineRule({
|
|
|
6244
6389
|
falsePositiveRisk: "low",
|
|
6245
6390
|
autofix: false,
|
|
6246
6391
|
detectionStrategy: "LEXICAL",
|
|
6392
|
+
strategyJustification: {
|
|
6393
|
+
reasonCode: "string-content-defect",
|
|
6394
|
+
detail: "hardcoded environment URLs are string literals (http(s):// shapes); the detector reads the string-content shapes the code-text mask preserves for exactly this defect class"
|
|
6395
|
+
},
|
|
6247
6396
|
introduced: "0.3.0",
|
|
6248
6397
|
tier: "quarantine",
|
|
6249
6398
|
run(ctx) {
|
|
@@ -6294,6 +6443,10 @@ const envCoupling = defineRule({
|
|
|
6294
6443
|
falsePositiveRisk: "medium",
|
|
6295
6444
|
autofix: false,
|
|
6296
6445
|
detectionStrategy: "LEXICAL",
|
|
6446
|
+
strategyJustification: {
|
|
6447
|
+
reasonCode: "absence-aggregate",
|
|
6448
|
+
detail: "environment-guard absence over the suite is an aggregate property; the detector aggregates the suite's guard shapes — the defect is what the suite lacks, not a node it has"
|
|
6449
|
+
},
|
|
6297
6450
|
detectionNotes: "regex heuristic",
|
|
6298
6451
|
introduced: "0.2.0",
|
|
6299
6452
|
tier: "quarantine",
|
|
@@ -6368,6 +6521,10 @@ const pwRetryMaskingNoForensics = defineRule({
|
|
|
6368
6521
|
falsePositiveRisk: "low",
|
|
6369
6522
|
autofix: false,
|
|
6370
6523
|
detectionStrategy: "LEXICAL",
|
|
6524
|
+
strategyJustification: {
|
|
6525
|
+
reasonCode: "runner-semantic",
|
|
6526
|
+
detail: "retry triage is the runner's retry loop interacting with the reporter config; the detector reads both config keys and the triage call shape — the semantics live in runner behavior"
|
|
6527
|
+
},
|
|
6371
6528
|
detectionNotes: "regex heuristic",
|
|
6372
6529
|
introduced: "0.3.8",
|
|
6373
6530
|
tier: "extended",
|
|
@@ -6420,6 +6577,10 @@ const pwBlanketRouteMock = defineRule({
|
|
|
6420
6577
|
falsePositiveRisk: "medium",
|
|
6421
6578
|
autofix: false,
|
|
6422
6579
|
detectionStrategy: "LEXICAL",
|
|
6580
|
+
strategyJustification: {
|
|
6581
|
+
reasonCode: "absence-aggregate",
|
|
6582
|
+
detail: "blanket route interception is an aggregate of route calls across the suite; the detector aggregates the route-call shapes — the finding is the pattern's breadth, not one call"
|
|
6583
|
+
},
|
|
6423
6584
|
detectionNotes: "regex heuristic",
|
|
6424
6585
|
introduced: "0.3.8",
|
|
6425
6586
|
tier: "extended",
|
|
@@ -6473,6 +6634,10 @@ const pwNoFailureArtifacts = defineRule({
|
|
|
6473
6634
|
falsePositiveRisk: "low",
|
|
6474
6635
|
autofix: false,
|
|
6475
6636
|
detectionStrategy: "LEXICAL",
|
|
6637
|
+
strategyJustification: {
|
|
6638
|
+
reasonCode: "absence-aggregate",
|
|
6639
|
+
detail: "artifact-capture absence (no trace/video/screenshot anywhere in the suite) is a directory-level aggregate; the detector aggregates over the suite's shapes — absence, not presence"
|
|
6640
|
+
},
|
|
6476
6641
|
detectionNotes: "regex heuristic",
|
|
6477
6642
|
introduced: "0.3.8",
|
|
6478
6643
|
tier: "extended",
|
|
@@ -6523,6 +6688,10 @@ const pwSingleBrowserMatrix = defineRule({
|
|
|
6523
6688
|
falsePositiveRisk: "low",
|
|
6524
6689
|
autofix: false,
|
|
6525
6690
|
detectionStrategy: "LEXICAL",
|
|
6691
|
+
strategyJustification: {
|
|
6692
|
+
reasonCode: "absence-aggregate",
|
|
6693
|
+
detail: "single-browser coverage absence is a config/projects aggregate property; the detector reads the projects arrangement across the config — no single node constitutes the finding"
|
|
6694
|
+
},
|
|
6526
6695
|
detectionNotes: "regex heuristic",
|
|
6527
6696
|
introduced: "0.3.8",
|
|
6528
6697
|
tier: "extended",
|
|
@@ -6606,6 +6775,10 @@ const pwLocatorNormalize = defineRule({
|
|
|
6606
6775
|
falsePositiveRisk: "medium",
|
|
6607
6776
|
autofix: false,
|
|
6608
6777
|
detectionStrategy: "LEXICAL",
|
|
6778
|
+
strategyJustification: {
|
|
6779
|
+
reasonCode: "string-content-defect",
|
|
6780
|
+
detail: "CSS/XPath string selectors are string-argument shapes (css=/xpath= engines, bare id/class/attr CSS, nth-child); the detector classifies the string shapes directly"
|
|
6781
|
+
},
|
|
6609
6782
|
detectionNotes: "string-selector shapes (css=/xpath= engines, bare id/class/attr CSS, nth-child) inside .locator()/waitForSelector()/page.$ APIs, on the RAW text view (the selector text lives inside string literals, which the code-only view blanks)",
|
|
6610
6783
|
introduced: "0.6.0",
|
|
6611
6784
|
tier: "quarantine",
|
|
@@ -6665,6 +6838,10 @@ const pwCodegenArtifact = defineRule({
|
|
|
6665
6838
|
falsePositiveRisk: "medium",
|
|
6666
6839
|
autofix: false,
|
|
6667
6840
|
detectionStrategy: "LEXICAL",
|
|
6841
|
+
strategyJustification: {
|
|
6842
|
+
reasonCode: "lexical-artifact",
|
|
6843
|
+
detail: "the codegen recorder's default title ('test', 'test 1', …) committed is a recording artifact — the default-title string is the finding; the detector matches the recorder's exact title shapes"
|
|
6844
|
+
},
|
|
6668
6845
|
detectionNotes: "the codegen recorder's default test title ('test', 'test 1', 'test 2', …) committed verbatim, on the RAW text view (the title is a string literal)",
|
|
6669
6846
|
introduced: "0.6.0",
|
|
6670
6847
|
tier: "quarantine",
|
|
@@ -6711,6 +6888,10 @@ const pyPwSyncAsyncMix = defineRule({
|
|
|
6711
6888
|
falsePositiveRisk: "low",
|
|
6712
6889
|
autofix: false,
|
|
6713
6890
|
detectionStrategy: "LEXICAL",
|
|
6891
|
+
strategyJustification: {
|
|
6892
|
+
reasonCode: "family-fallback-lockstep",
|
|
6893
|
+
detail: "the hard-sleep family's Python sync/async variant: the family's structural path carries the depth; this variant's lexical path is the lockstep fallback (async-mix shapes across the sync/async boundary)"
|
|
6894
|
+
},
|
|
6714
6895
|
detectionNotes: "regex heuristic",
|
|
6715
6896
|
introduced: "0.3.8",
|
|
6716
6897
|
run(ctx) {
|
|
@@ -6756,6 +6937,10 @@ const pyPwWaitForTimeout = defineRule({
|
|
|
6756
6937
|
falsePositiveRisk: "low",
|
|
6757
6938
|
autofix: false,
|
|
6758
6939
|
detectionStrategy: "LEXICAL",
|
|
6940
|
+
strategyJustification: {
|
|
6941
|
+
reasonCode: "exact-key-match",
|
|
6942
|
+
detail: "page.waitForTimeout is an exact Playwright token in Python tests; the detector matches the call identifier — closed token, same predicate the AST would encode"
|
|
6943
|
+
},
|
|
6759
6944
|
introduced: "0.3.8",
|
|
6760
6945
|
tier: "core",
|
|
6761
6946
|
run(ctx) {
|
|
@@ -6801,6 +6986,10 @@ const pyPwNoAssertions = defineRule({
|
|
|
6801
6986
|
falsePositiveRisk: "low",
|
|
6802
6987
|
autofix: false,
|
|
6803
6988
|
detectionStrategy: "LEXICAL",
|
|
6989
|
+
strategyJustification: {
|
|
6990
|
+
reasonCode: "runner-semantic",
|
|
6991
|
+
detail: "Playwright test bodies without assertions are runner-outcome semantics; the detector matches the test-def plus body shapes on the code-only text"
|
|
6992
|
+
},
|
|
6804
6993
|
detectionNotes: "regex heuristic",
|
|
6805
6994
|
introduced: "0.3.8",
|
|
6806
6995
|
tier: "quarantine",
|
|
@@ -6877,6 +7066,10 @@ const jvDisabledTest = defineRule({
|
|
|
6877
7066
|
falsePositiveRisk: "low",
|
|
6878
7067
|
autofix: false,
|
|
6879
7068
|
detectionStrategy: "LEXICAL",
|
|
7069
|
+
strategyJustification: {
|
|
7070
|
+
reasonCode: "exact-key-match",
|
|
7071
|
+
detail: "@Disabled/@Ignore are exact JUnit/TestNG annotation tokens; the detector matches the annotation identifier — annotation shapes are closed token sets where lexical and structural match coincide"
|
|
7072
|
+
},
|
|
6880
7073
|
introduced: "0.3.8",
|
|
6881
7074
|
tier: "core",
|
|
6882
7075
|
run(ctx) {
|
|
@@ -7936,6 +8129,10 @@ const csSkippedTest = defineRule({
|
|
|
7936
8129
|
falsePositiveRisk: "low",
|
|
7937
8130
|
autofix: false,
|
|
7938
8131
|
detectionStrategy: "LEXICAL",
|
|
8132
|
+
strategyJustification: {
|
|
8133
|
+
reasonCode: "exact-key-match",
|
|
8134
|
+
detail: "[Ignore]/[Fact(Skip=…)] are exact xUnit/NUnit/MSTest attribute tokens; the detector matches the attribute identifiers — closed token sets where lexical precision equals structural"
|
|
8135
|
+
},
|
|
7939
8136
|
introduced: "0.3.8",
|
|
7940
8137
|
run(ctx) {
|
|
7941
8138
|
const text = ctx.codeText ?? ctx.text;
|
|
@@ -8289,6 +8486,10 @@ const cypCyWait = defineRule({
|
|
|
8289
8486
|
falsePositiveRisk: "medium",
|
|
8290
8487
|
autofix: false,
|
|
8291
8488
|
detectionStrategy: "LEXICAL",
|
|
8489
|
+
strategyJustification: {
|
|
8490
|
+
reasonCode: "runner-semantic",
|
|
8491
|
+
detail: "cy.wait(numeric) is a Cypress runner wait contract; the detector matches the member-call token with a numeric-literal argument on the code-only text — alias waits (cy.wait('@…')) are structurally distinct and excluded by the argument shape"
|
|
8492
|
+
},
|
|
8292
8493
|
detectionNotes: "cy.wait with a numeric-literal argument only — alias waits (cy.wait('@…')) are the legitimate routed-request form and never fire",
|
|
8293
8494
|
introduced: "0.6.0",
|
|
8294
8495
|
tier: "extended",
|
|
@@ -8361,6 +8562,10 @@ const cypFocusedTest = defineRule({
|
|
|
8361
8562
|
falsePositiveRisk: "low",
|
|
8362
8563
|
autofix: false,
|
|
8363
8564
|
detectionStrategy: "LEXICAL",
|
|
8565
|
+
strategyJustification: {
|
|
8566
|
+
reasonCode: "runner-semantic",
|
|
8567
|
+
detail: "Cypress .only is the runner's focus token; the detector matches the it/describe/context .only member-call shape on the code-only text — exact-key precision"
|
|
8568
|
+
},
|
|
8364
8569
|
detectionNotes: "it/describe/context .only member-call shape on the code-only text view",
|
|
8365
8570
|
introduced: "0.6.0",
|
|
8366
8571
|
tier: "quarantine",
|
|
@@ -8427,6 +8632,10 @@ const cypConfigSecurity = defineRule({
|
|
|
8427
8632
|
falsePositiveRisk: "low",
|
|
8428
8633
|
autofix: false,
|
|
8429
8634
|
detectionStrategy: "LEXICAL",
|
|
8635
|
+
strategyJustification: {
|
|
8636
|
+
reasonCode: "exact-key-match",
|
|
8637
|
+
detail: "chromeWebSecurity:false is an exact config key/value pair inside cypress.config.*; the detector matches the key and value literally — the config surface's statements are the finding"
|
|
8638
|
+
},
|
|
8430
8639
|
detectionNotes: "positive match on chromeWebSecurity:false inside cypress.config.* (the config is the artifact — no heuristic)",
|
|
8431
8640
|
introduced: "0.6.0",
|
|
8432
8641
|
tier: "quarantine",
|
|
@@ -8534,6 +8743,10 @@ const seJavaSleepLookup = defineRule({
|
|
|
8534
8743
|
falsePositiveRisk: "medium",
|
|
8535
8744
|
autofix: false,
|
|
8536
8745
|
detectionStrategy: "LEXICAL",
|
|
8746
|
+
strategyJustification: {
|
|
8747
|
+
reasonCode: "runner-semantic",
|
|
8748
|
+
detail: "the Selenium family's variants (QA-SE-001/002/003): the defect is the SEQUENCE sleep-then-interact — runner timing semantics, not a single node; the detector matches the sleep token followed by a lookup within the recorded window"
|
|
8749
|
+
},
|
|
8537
8750
|
detectionNotes: "sequence shape: Thread.sleep followed by a findElement/interaction call within 3 lines (code-only view)",
|
|
8538
8751
|
introduced: "0.6.0",
|
|
8539
8752
|
tier: "quarantine",
|
|
@@ -8559,6 +8772,10 @@ const seCSharpSleepLookup = defineRule({
|
|
|
8559
8772
|
falsePositiveRisk: "medium",
|
|
8560
8773
|
autofix: false,
|
|
8561
8774
|
detectionStrategy: "LEXICAL",
|
|
8775
|
+
strategyJustification: {
|
|
8776
|
+
reasonCode: "runner-semantic",
|
|
8777
|
+
detail: "the Selenium family's variants (QA-SE-001/002/003): the defect is the SEQUENCE sleep-then-interact — runner timing semantics, not a single node; the detector matches the sleep token followed by a lookup within the recorded window"
|
|
8778
|
+
},
|
|
8562
8779
|
detectionNotes: "sequence shape: Thread.Sleep/Task.Delay followed by a FindElement/interaction call within 3 lines (code-only view)",
|
|
8563
8780
|
introduced: "0.6.0",
|
|
8564
8781
|
tier: "quarantine",
|
|
@@ -8584,6 +8801,10 @@ const sePythonSleepLookup = defineRule({
|
|
|
8584
8801
|
falsePositiveRisk: "medium",
|
|
8585
8802
|
autofix: false,
|
|
8586
8803
|
detectionStrategy: "LEXICAL",
|
|
8804
|
+
strategyJustification: {
|
|
8805
|
+
reasonCode: "runner-semantic",
|
|
8806
|
+
detail: "the Selenium family's variants (QA-SE-001/002/003): the defect is the SEQUENCE sleep-then-interact — runner timing semantics, not a single node; the detector matches the sleep token followed by a lookup within the recorded window"
|
|
8807
|
+
},
|
|
8587
8808
|
detectionNotes: "sequence shape: time.sleep followed by a find_element/interaction call within 3 lines (code-only view)",
|
|
8588
8809
|
introduced: "0.6.0",
|
|
8589
8810
|
tier: "quarantine",
|
|
@@ -8622,6 +8843,7 @@ function definePatternFamily(opts) {
|
|
|
8622
8843
|
falsePositiveRisk: v.falsePositiveRisk ?? opts.falsePositiveRisk,
|
|
8623
8844
|
autofix: opts.autofix ?? false,
|
|
8624
8845
|
detectionStrategy: v.detectionStrategy ?? opts.detectionStrategy ?? "LEXICAL",
|
|
8846
|
+
...v.strategyJustification ?? opts.strategyJustification ? { strategyJustification: v.strategyJustification ?? opts.strategyJustification } : {},
|
|
8625
8847
|
...v.detectionNotes !== void 0 ? { detectionNotes: v.detectionNotes } : { ...opts.detectionNotes !== void 0 ? { detectionNotes: opts.detectionNotes } : {} },
|
|
8626
8848
|
...opts.introduced ? { introduced: opts.introduced } : {},
|
|
8627
8849
|
...v.tier !== void 0 ? { tier: v.tier } : { ...opts.tier !== void 0 ? { tier: opts.tier } : {} },
|
|
@@ -8774,6 +8996,10 @@ const hardSleepFamily = definePatternFamily({
|
|
|
8774
8996
|
why: WHY,
|
|
8775
8997
|
falsePositiveRisk: "low",
|
|
8776
8998
|
detectionStrategy: "LEXICAL",
|
|
8999
|
+
strategyJustification: {
|
|
9000
|
+
reasonCode: "family-fallback-lockstep",
|
|
9001
|
+
detail: "the hard-sleep family's Java variant (QA-JV-102): the family's structural path carries the depth where a tree is available; this lexical path is the mandatory deterministic fallback kept in lockstep (§13.2)"
|
|
9002
|
+
},
|
|
8777
9003
|
introduced: "0.3.8",
|
|
8778
9004
|
tier: "extended",
|
|
8779
9005
|
useCodeText: true,
|
|
@@ -8823,6 +9049,10 @@ const networkIdleFamily = definePatternFamily({
|
|
|
8823
9049
|
why: "Analytics, websockets, and polling make network idle never fire or fire randomly — a documented source of Playwright flakes.",
|
|
8824
9050
|
falsePositiveRisk: "low",
|
|
8825
9051
|
detectionStrategy: "LEXICAL",
|
|
9052
|
+
strategyJustification: {
|
|
9053
|
+
reasonCode: "runner-semantic",
|
|
9054
|
+
detail: "the network-idle family's variants (QA-JV-107/QA-CS-107/QA-PY-107): networkidle waits are runner timing semantics; the detector matches the runner's wait tokens — exact keys"
|
|
9055
|
+
},
|
|
8826
9056
|
introduced: "0.4.0",
|
|
8827
9057
|
useCodeText: false,
|
|
8828
9058
|
variants: [
|
|
@@ -8898,6 +9128,10 @@ const hardcodedUrlFamily = definePatternFamily({
|
|
|
8898
9128
|
why: "Absolute URLs break when environments change and can hit production by accident from a CI runner.",
|
|
8899
9129
|
falsePositiveRisk: "low",
|
|
8900
9130
|
detectionStrategy: "LEXICAL",
|
|
9131
|
+
strategyJustification: {
|
|
9132
|
+
reasonCode: "string-content-defect",
|
|
9133
|
+
detail: "the hardcoded-url family's variants (QA-JV-108/QA-CS-108/QA-PY-108): hardcoded URLs are string literals; the detector matches the http(s):// string shapes — the URL lives in the string, not the syntax tree"
|
|
9134
|
+
},
|
|
8901
9135
|
introduced: "0.4.0",
|
|
8902
9136
|
useCodeText: false,
|
|
8903
9137
|
variants: [{
|
|
@@ -8928,6 +9162,10 @@ const sharedPageFamily = definePatternFamily({
|
|
|
8928
9162
|
why: "A shared Page/Browser leaks cookies, localStorage, and navigation state between tests — failures become order-dependent and impossible to reproduce in isolation.",
|
|
8929
9163
|
falsePositiveRisk: "medium",
|
|
8930
9164
|
detectionStrategy: "LEXICAL",
|
|
9165
|
+
strategyJustification: {
|
|
9166
|
+
reasonCode: "runner-semantic",
|
|
9167
|
+
detail: "the shared-page family's variants (QA-JV-104/QA-CS-104/QA-PY-106): page/fixture reuse across tests is runner fixture-lifecycle semantics; the detector matches the consumption shapes against test boundaries"
|
|
9168
|
+
},
|
|
8931
9169
|
introduced: "0.4.0",
|
|
8932
9170
|
useCodeText: true,
|
|
8933
9171
|
variants: [
|
|
@@ -9003,6 +9241,10 @@ function makeBrittleSelectors(id, appliesTo, ext, languages, frameworks, pattern
|
|
|
9003
9241
|
falsePositiveRisk: "medium",
|
|
9004
9242
|
autofix: false,
|
|
9005
9243
|
detectionStrategy: "LEXICAL",
|
|
9244
|
+
strategyJustification: {
|
|
9245
|
+
reasonCode: "string-content-defect",
|
|
9246
|
+
detail: "the brittle-selector family's variants (QA-JV-106/QA-CS-106/QA-PY-104): selector defects are string-argument shapes (xpath=/nth-child/absolute-path); the detector classifies the string shapes the code-text mask preserves for this class"
|
|
9247
|
+
},
|
|
9006
9248
|
introduced: "0.4.0",
|
|
9007
9249
|
tier: "quarantine",
|
|
9008
9250
|
detectorRevision: 2,
|
|
@@ -9099,6 +9341,10 @@ const retryMaskingFamily = [defineRule({
|
|
|
9099
9341
|
falsePositiveRisk: "medium",
|
|
9100
9342
|
autofix: false,
|
|
9101
9343
|
detectionStrategy: "LEXICAL",
|
|
9344
|
+
strategyJustification: {
|
|
9345
|
+
reasonCode: "runner-semantic",
|
|
9346
|
+
detail: "the retry-masking family's variants (QA-JV-109/QA-CS-109): retry masking is the runner's retry contract; the detector matches the runner's retry tokens on the code-only text"
|
|
9347
|
+
},
|
|
9102
9348
|
introduced: "0.4.0",
|
|
9103
9349
|
tier: "core",
|
|
9104
9350
|
run(ctx) {
|
|
@@ -9152,6 +9398,10 @@ const retryMaskingFamily = [defineRule({
|
|
|
9152
9398
|
falsePositiveRisk: "medium",
|
|
9153
9399
|
autofix: false,
|
|
9154
9400
|
detectionStrategy: "LEXICAL",
|
|
9401
|
+
strategyJustification: {
|
|
9402
|
+
reasonCode: "runner-semantic",
|
|
9403
|
+
detail: "the retry-masking family's variants (QA-JV-109/QA-CS-109): retry masking is the runner's retry contract; the detector matches the runner's retry tokens on the code-only text"
|
|
9404
|
+
},
|
|
9155
9405
|
introduced: "0.4.0",
|
|
9156
9406
|
tier: "extended",
|
|
9157
9407
|
run(ctx) {
|
|
@@ -12752,7 +13002,7 @@ function renderSarif(result, repoRootUri) {
|
|
|
12752
13002
|
tool: { driver: {
|
|
12753
13003
|
name: "Mjölnir",
|
|
12754
13004
|
informationUri: "https://github.com/Sergey-Bar/Mjolnir",
|
|
12755
|
-
version: "1.0.
|
|
13005
|
+
version: "1.0.2",
|
|
12756
13006
|
rules: [...rules.values()].map((r) => {
|
|
12757
13007
|
const meta = RULES.find((x) => x.id === r.id);
|
|
12758
13008
|
return {
|
|
@@ -17992,6 +18242,19 @@ function loadManifest(path) {
|
|
|
17992
18242
|
*
|
|
17993
18243
|
* Exit codes reuse the frozen set: 0 healthy · 1 violations · 20 crash.
|
|
17994
18244
|
*/
|
|
18245
|
+
/** The closed reason-code set (master plan P8) — mirrored from
|
|
18246
|
+
* src/rules/rule.ts's StrategyReasonCode union via a runtime set so the
|
|
18247
|
+
* doctor check validates membership without an extra export cycle. */
|
|
18248
|
+
const VALID_REASON_CODES = /* @__PURE__ */ new Set([
|
|
18249
|
+
"shell-string-in-config",
|
|
18250
|
+
"exact-key-match",
|
|
18251
|
+
"lexical-artifact",
|
|
18252
|
+
"runner-semantic",
|
|
18253
|
+
"string-content-defect",
|
|
18254
|
+
"absence-aggregate",
|
|
18255
|
+
"family-fallback-lockstep",
|
|
18256
|
+
"migration-deferred-next-measurement"
|
|
18257
|
+
]);
|
|
17995
18258
|
/** Uniform error rendering for doctor details (Error or thrown-as-string). */
|
|
17996
18259
|
function errorText(e) {
|
|
17997
18260
|
return e instanceof Error ? e.message : String(e);
|
|
@@ -18030,7 +18293,8 @@ function checkFixtureFirewall(fixturesRoot) {
|
|
|
18030
18293
|
}
|
|
18031
18294
|
return check("fixture-firewall", ok ? "pass" : "fail", details);
|
|
18032
18295
|
}
|
|
18033
|
-
/** Check 2: registry sanity — IDs unique, well-formed, titles distinct
|
|
18296
|
+
/** Check 2: registry sanity — IDs unique, well-formed, titles distinct,
|
|
18297
|
+
* and (P8) every LEXICAL rule carries a valid depth-adjudication record. */
|
|
18034
18298
|
function checkRegistry(rules = RULES) {
|
|
18035
18299
|
const details = [];
|
|
18036
18300
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -18045,6 +18309,19 @@ function checkRegistry(rules = RULES) {
|
|
|
18045
18309
|
details.push(`${r.id}: duplicate registration`);
|
|
18046
18310
|
}
|
|
18047
18311
|
ids.add(r.id);
|
|
18312
|
+
if (r.detectionStrategy === "LEXICAL") {
|
|
18313
|
+
const j = r.strategyJustification;
|
|
18314
|
+
if (!j) {
|
|
18315
|
+
ok = false;
|
|
18316
|
+
details.push(`${r.id}: LEXICAL strategy without strategyJustification — declare the reason the lexical form ships (docs/DEPTH-ADJUDICATION.md)`);
|
|
18317
|
+
} else if (!VALID_REASON_CODES.has(j.reasonCode)) {
|
|
18318
|
+
ok = false;
|
|
18319
|
+
details.push(`${r.id}: strategyJustification.reasonCode "${String(j.reasonCode)}" is outside the closed set (${[...VALID_REASON_CODES].join(", ")})`);
|
|
18320
|
+
} else if (j.detail.trim().length < 20) {
|
|
18321
|
+
ok = false;
|
|
18322
|
+
details.push(`${r.id}: strategyJustification.detail is boilerplate — cite the detector's actual shape`);
|
|
18323
|
+
}
|
|
18324
|
+
}
|
|
18048
18325
|
const family = r.id.split("-")[1];
|
|
18049
18326
|
for (const other of rules) if (other.id !== r.id && other.title === r.title && other.id.split("-")[1] === family) {
|
|
18050
18327
|
ok = false;
|
|
@@ -18732,7 +19009,7 @@ const { runScan, buildUniversalRules, fallbackWorkspace, pathMatchesGlob, isVali
|
|
|
18732
19009
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
18733
19010
|
* `tests/version-consistency.spec.ts` locally.
|
|
18734
19011
|
*/
|
|
18735
|
-
const CLI_VERSION = "1.0.
|
|
19012
|
+
const CLI_VERSION = "1.0.2";
|
|
18736
19013
|
function parseArgs(argv, onError) {
|
|
18737
19014
|
const args = {
|
|
18738
19015
|
target: ".",
|
|
@@ -19068,8 +19345,9 @@ async function runExplainCommand(argv, io = {
|
|
|
19068
19345
|
return 10;
|
|
19069
19346
|
}
|
|
19070
19347
|
try {
|
|
19071
|
-
|
|
19072
|
-
|
|
19348
|
+
const r = explainVerdict(resolve(jsonPath));
|
|
19349
|
+
io.out(renderVerdictExplain(r));
|
|
19350
|
+
return r.ok ? 0 : 10;
|
|
19073
19351
|
} catch (err) {
|
|
19074
19352
|
internalErrorMessage(err, io.err, argv.includes("--debug"));
|
|
19075
19353
|
return 20;
|