- The reviewer now covers assertion loops over unproven collections (
#4k). It triages Playwrightlocator.all()loops and Cypress.each()callbacks whose assertions can execute zero times, while preserving guards for explicit non-empty and collection-size proofs. - The reviewer now identifies unexplained test skips (
#11c). Bareskip/fixmecalls are P2 candidates, while conditional skips, reason strings, ticket/date comments, andJUSTIFIEDmarkers remain intentional. - A public workflow for reviewing AI-generated Playwright and Cypress tests. The guide includes copyable prompts, a false-green before/after example, deterministic scanner output, the semantic review evidence fields, and direct FAQ answers.
- Playwright debugger guidance for the supported trace CLI. On trusted
projects using Playwright 1.59 or newer, the debugger can use
trace actions, snapshot evaluation, pass/fail trace comparison, and multi-trace failure clustering after the execution gate passes.
- README discovery now leads with AI-generated-test review and false-green proof. It links the public workflow, explains how the bundle complements Playwright Test Agents and Cypress AI Skills, and records the current Kimi listing and skills.sh install milestone without treating either as accuracy evidence.
- Local CI now reports stage timings and avoids duplicate validation work. The parity smoke suite no longer reruns an unchanged security gate for every ordinary mutation, and the reference-tokenizer unit suites no longer rebuild three identical standalone wrapper environments after already exercising those wrappers under adversarial conditions. Security-specific mutations and the canonical pre-push gate still execute the real security script.
- Scanner contract runs report their slowest checks. The worker ceiling is unchanged after a higher concurrency setting measured slower on the current host.
-
test-fixture-faults.pyno longer fails under CPU contention. Two process-cleanup cases gave the parent 300ms to start a Python interpreter, spawn a nested one, and flush a line before the timeout fired, so a loaded machine failed the output assertion instead of a real regression. Every interval is rescaled and the ordering the test proves is unchanged. -
Behavioral process-cleanup checks no longer race their PID fixtures. The two live process-group cases now allow enough startup time for a loaded host to launch Bash, spawn the nested process, and flush its PID before timeout; the TERM/KILL cleanup assertions and their failure conditions are unchanged.
-
manifest_phrase_contract.pyis nowreviewer_taxonomy_contract.py. It stopped holding manifest phrases when 1.15.0 moved the taxonomy out of the package descriptions; the name described what it no longer did. -
Corrected a false claim about why holdout v5 and v6 stalled. The 1.14.0 notes,
benchmarks/STATUS.md,benchmarks/reviewer-holdout-v6/README.md, andAGENTS.mdall said the pinned Claude Code builds had been pruned and the protocols were "permanently unrunnable". Only the local installer rotated those builds away. The vendor release channel still serves both2.1.220and2.1.239, and the runner accepts an explicit--runner-path, so re-fetching a pinned build completes the matrix. Theminimumversion policy is unchanged and still worth having — a protocol that needs a manual download before it can start is one nobody reruns — but its stated justification was wrong and is now accurate. -
Restored the live-exploration scope to the package descriptions. The 1.15.0 shortening dropped "live exploration only on local/disposable or externally isolated approved non-production targets" from all three manifests, and narrowed the CI guard to the generator's trigger surfaces in the same commit, so nothing caught it. A user reading the marketplace listing had no indication the generator drives a real browser. The phrase is back and the guard now covers the package descriptions again.
-
Reports no longer claim an identity policy that was never applied.
execution_identity_policywas written as"exact"even when no identity check ran — a custom runner, or a protocol withoutexecution_identity. It is nowNonein that case, so a provenance artifact stops asserting a check the harness did not make. -
The Quick Reference order check no longer hardcodes the pattern set. It derived the expected order from a literal
1..23 + "3b", so adding pattern#24in the correct position failed with a row-order error. The order is now derived from the IDs present, andAGENTS.mdrecords thatreview.shneeds no edit when a pattern is added. -
Documented the verified source of the skill-manifest limits. The 64/1024 caps in
skill_metadata_contract.pycarried no citation. They are correct, confirmed againstopenai/codexrust-v0.151.0(MAX_NAME_LEN,MAX_DESCRIPTION_LEN), and the module now records that an over-length field is dropped rather than truncated, and that Codex counts Unicode characters after collapsing whitespace. -
Corrected the v6 archive's account of
full-opus. The README said it never ran.opus.logshows it completed all 60 scheduled runs with zero infrastructure errors; only its report file was lost. The conclusion that no usablefull-arm report survives is unchanged, because the per-run records the primary metric needs are gone, but the stated fact was false and contradicted the same file's own list of lost reports.
- CI enforces the Codex directory submission rules.
validate_codex.pynow checks the manifest description against a 1024-character limit, the listing strings against their 30-character limits,longDescriptionagainst 4000, the developer name against 80, andcategoryagainst the supported thirteen-value taxonomy. The repository was violating two of these without any surface reporting it:.codex-plugin/plugin.jsondeclared the categoryTesting, which is not a supported value, and a 57-charactershortDescriptionagainst a 30-character limit. Both are fixed; the category is nowDeveloper Tools. - A balanced trigger fixture for every public skill. Each of the four
skills ships
evals/trigger-evals.jsonwith sixteen cases, eight that must trigger the skill and eight adjacent negatives that must not — a Playwright report with a TimeoutError belongs to the debugger, not the reviewer, and a request to write new coverage belongs to the generator.review.shvalidates structure, unique kebab-case ids, realistic multi-word queries, and the per-label floor. It never calls a model; measured trigger rates stay outside ordinary CI.
- Manifest descriptions stop storing the reviewer taxonomy. All 24 pattern
phrases previously had to appear in order inside three manifest descriptions,
which made every pattern rename a four-file edit and pushed the description
to 1138 characters — past the limit for directory submission. The canonical
order now lives in
scripts/ci/lib/manifest_phrase_contract.pyalone, andreview.shstill checks all 24 ids, titles, order, and severities against thee2e-reviewer/SKILL.mdQuick Reference. Nothing was dropped from enforcement; it moved from prose to data. Keyword discovery is unaffected — the 52-entrykeywordsarray and the 818-characterlongDescriptionstill carry the pattern vocabulary.
- Reviewer holdout protocol v6. v5 preregistered
Claude Code 2.1.220, that build is no longer retained, and v5 requires a complete three-host matrix, so v5 can never finish. v6 reuses the v5 corpus byte-for-byte — its case and corpus digests are identical to v5's — and changes only the frozen CLI identity, which is what v5's own freeze policy prescribes for any later edit. No v5 result had been produced, so nothing is being refitted after the fact. The runner's frozen execution identity moved from a v5-only literal to a per-protocol table, so each version stays pinned to its own builds. - A
minimumexecution-identity version policy. A protocol'sexecution_identitymay now declareversion_policy, eitherexact(default) orminimum. Underminimumthe runner accepts the preregistered build or any later one and records the resolved identity plus anexecution_identity_matchofexactorminimum-satisfiedin the report, so drift is written down rather than silent. v5 and v6 remainexact; their frozen JSON and pinned digests are untouched.
-
The four
agents/openai.yamlfiles were inert. codex-cli reads that file for every discovered skill, plugin-installed or not, but its parser accepts onlyinterface,policy, anddependencies. The repo shippedname,description,metadata, and a top-levelallow_implicit_invocation, none of which that struct has — and the loader warns then falls back to defaults instead of failing, so all four skills silently had no display name, no short description, and no default prompt in the Codex UI. They now use the real schema and pass the validator bundled with codex-cli.allow_implicit_invocationmoves underpolicy, where it is actually read; its default istrue, so behavior is unchanged either way. The trigger surface was never this file — it is SKILL.md frontmatter, which already carries longer text.playwright-test-generatorkeeps its live-exploration scope phrase, moved from the discardeddescriptionintointerface.default_promptso the V5-T2 safety contract stays on a surface that is actually read. The repo's own fail-closed parser, duplicated inreview.shandpre-push-security.sh, was rewritten for the real schema; the directory binding that the removednamefield used to provide is now the$<skill>invocation required in eachdefault_prompt. -
Exact CLI pinning stranded two protocols. v5 pinned
Claude Code 2.1.220and v6 pinned2.1.239; the installer keeps only a few recent builds, so both pins left the local install before their matrix was finished. The property an arm comparison actually needs, one identity across every cell of one matrix, is already enforced bycompare-reviewer-holdouts.py(arm_runner_identity_mismatch,arm_runner_binding_mismatch) together with the 12-hour matrix window, and never depended on the cut-time constant. New protocols can now pin a floor instead of a single build the installer will rotate away. -
Reviewer holdout v6 archived as incomplete. Five of nine cells survived, two of them execution-complete.
benchmarks/reviewer-holdout-v6/records the reports, the driver logs, why the run stalled, and which single arm contrast the partial data actually supports. No v6 accuracy or skill-lift result is claimed. -
ci-local.shdrops inherited__pycache__trees before verification runs. Python invalidates a cached.pycby source mtime, which misses an edit made in the same second as the cache, so an inherited cache could serve retired pinned digests to the test whose job is to catch exactly that. Observed: the pinned-input check reported a protocol digest that no longer existed anywhere in the repository.
- The E2E reviewer now supports PR/diff-scoped reviews. Changed Playwright and Cypress E2E artifacts are the primary review scope, unchanged files are context, and findings are classified as introduced, worsened, or pre-existing. Pre-existing findings are advisory and non-blocking. A Review Scope and Evidence header records the validation limits for each diff-scoped review. Diff mode invokes the scanner once per changed artifact, and the scanner rejects multiple roots instead of silently ignoring extra paths. Full-mode reviews remain available as the default when no PR or diff scope is supplied.
scripts/dev/install-claude-agents.shinstalls the two subagents for Claude Code at user level. The subagents ship inside the plugin, so askillsCLI install never registered them, and installing the plugin just to obtain them loads a second copy of all four skill descriptions into every session because plugin skills are namespaced rather than deduplicated against same-named personal skills. The installer mirrors the Codex one: a staged transaction with rollback, refusal to write through a symlink or over a user-authored agent of the same name, and frontmattername:discovery validation.scripts/ci/test-claude-agents.shcovers those guards and runs in local CI.
- The scanner no longer excludes
evals/files/andscripts/ci/fixtures/from every scan. Those paths hold this repository's intentional anti-patterns, and excluding them everywhere silently skipped real tests in any target project that happened to use the same directory names. Exclusion now applies only when the scanned project is itself ane2e-skillscheckout. Upgrade impact: a project with tests under either path goes from zero reported hits to full reporting. Review those findings rather than re-excluding the paths. Forks and vendored copies of this repository still match the self-repo fingerprint, so their own tests under those two paths stay exempt.
- Four shell call sites reported a successful substring match as a failure.
Under
set -o pipefail,printf ... | grep -qreturns 141 whenever grep matches early enough to close the pipe before printf finishes writing, which happens only once the output exceeds the pipe buffer. Inskills/e2e-reviewer/scripts/scan.shthis meant an ESLint run that exits 0 with warnings printed "no findings" and dropped every Tier 1 hit — the silent-always-pass class the scanner exists to catch. It also madetest-parity.sh's absence assertions pass without checking,pr-preflight.shflag an unchanged test title as renamed, andcodex-smoke.shreport a correct answer as a failed check. All four now use a native[[ ]]match with no pipe. codex-smoke.shruns each check with the operator's MCP servers disabled, and a nonzero exit now states whether the expected token was present anyway, so an unrelated MCP or environment failure is not reported as a skill failure.install-codex-agents.shrecognises the agent headers it wrote before the marker was renamed fromCodextoCodex/OMX, so an existing install upgrades instead of being reported as a user-authored conflict. The previous behaviour told users to force past the guard whose only job is to protect genuinely user-authored agents.- Security scanners ignore inherited
GIT_*overrides, disable system and global config, and neutralisecore.excludesFile, so a hostile index, a global exclude file, or repository-local config cannot hide secret or policy hits. In-tree.gitignoreand.git/info/excluderemain trusted: the former is reviewed with the tree, and the latter carries this repository's own legitimate entries. - The independent-review v5 and v6 contract tests reproduce their packet from
the frozen source snapshot each phase reviewed instead of rebuilding it from
the working tree. Both phases are closed, so an old packet budget was gating
today's product and failed permanently once a later change legitimately grew
the same surfaces. v7 and v8 already worked this way. v6 builds from the v5
snapshot its own
phase_bindingpins by digest, which also makes that binding verified rather than merely recorded. ci-local.shaccepts a version-manager Node throughE2E_SKILLS_NODE_BINinstead of failing outright when Node is not on one of four absolute paths, and it now refuses any Node that is group- or world-writable or that lives inside the repository, matching the hosted workflow's integrity check.- The B-lite evidence verifier now checks its frozen, evidence-local generator snapshot instead of the evolving current skill, and the contract runs in local CI.
- Generator failure handling, scanner target exclusions, and debugger cleanup now have regression coverage for their previously unguarded boundaries.
- The shared F11 README row now covers both Playwright async ordering and Cypress command/intercept races in all four maintained languages.
- The Playwright generator defers detailed V1–V6 rules until verification.
SKILL.mdnow requires a full read ofverification-rules.mdbefore Step 7 instead of repeating the same procedure inline, keeping the primary skill shorter without dropping the safety, verdict, or completion contracts. - Every README now shows a shortened scanner transcript and the same
Windows boundary. The English, Korean, Japanese, and Simplified Chinese
examples show the actionable lines from
scan.sh; the scope section directs native Windows users to WSL and keeps artifacts inside its filesystem. - The executable browser fault matrix now runs on every hosted
mainpush. Pull requests keep the dependency-free archive and portability checks, while the published branch also installs the pinned fixtures and Chromium and fails CI if the live 36-cell reproduction regresses. Manual dispatch remains available for an explicit rerun. - The complete 24-pattern catalog is visible by default in every README language. English, Korean, Japanese, and Simplified Chinese readers no longer need to expand a disclosure before scanning the severity-grouped table.
- The README introduction now represents the full skill bundle. Every language uses the product name alone as its title and places verified merged upstream fixes before the false-green walkthrough.
- The Playwright generator trigger and Claude plugin description are shorter without changing behavior. Its frontmatter description is 677 characters instead of 959, front-loads common requests to add or scaffold coverage, and preserves the approved non-production exploration and adjacent-skill exclusions. The Claude plugin description now leaves first-run conventions and seed scaffolding to Step 5b, which still defines both.
- The #10a positional-locator guidance now covers Page Object wrappers. Reviewer docs and evals clarify that POM encapsulation is not an exemption, while explicitly positional helper APIs remain allowed.
- The Codex plugin now publishes a dedicated privacy policy. The interface
links to
PRIVACY.mdover HTTPS, and local manifest validation rejects a missing or noncanonical policy URL to prevent contract drift. - Debugger command cleanup no longer hangs after a macOS process-group
permission race. Playwright and Cypress report publishers and downloaders
continue past a
SIGTERMEPERM, observe process-group disappearance, and escalate toSIGKILLonly while the group remains. If cleanup still cannot be proven, its bounded failure is appended to the original timeout or byte-limit diagnostic. Live-process regressions cover all four command boundaries. - Reporter cleanup tests no longer use scheduler wall time as correctness evidence. Timeout/stdout integration tests assert preserved destinations and diagnostics, while a readiness-gated descendant test proves process-group disappearance. A generous subprocess watchdog is only a hang guard.
- The post-fix verifier no longer mistakes Linux
/usr/bin/sgfor ast-grep. The short name also belongs to the system shadow/group command; ambientPATHdiscovery now accepts onlyast-grep, while an explicitly installed verifier-relativesgor absolute override remains supported. - Hosted CI report capture no longer changes the source snapshot being verified. The report is written to the runner's temporary directory rather than the checkout, so the disposable parity proof does not mistake its own growing log artifact for a source-tree mutation.
- HOL plugin scanning no longer disables low-analyzability checks across the
repository. The workflow loads
.plugin-scanner.toml, keeps the rule set enabled, and excludes only the reviewed files that trigger known scanner category errors.
-
The upstream roadmap separates false-green fixes from reviewer-informed maintenance. ToolJet #17492 is tracked as an open Cypress cleanup without inflating the 14 merged or six in-review test-fix counts.
-
A
// JUSTIFIED:comment above adescribeblock no longer silences the tests inside it. The callback-scope walk collected every line between the marker and the hit and asked whether.evaluate(or.waitForFunction(appeared anywhere in that span, so anevaluatecall in an earlier sibling test satisfied it. A marker whose rationale described a canvas read in one test suppressed a#4fP0 and a#9P1 in another, andscan.shexited 0 where it should have exited 1. Only the construct that opens the scope carries the rationale now. Expect findings to appear in repositories that were relying on a block-level marker — they were always there. -
The mandatory opening-token sweep grew from 7 rows to 21. Fifteen rules were confirmed unable to detect the defect they name: their candidate regexes assume a statement fits on one physical line, and formatters wrap at 80 columns.
scan.shis a surface in a frozen review packet with eighteen tokens of headroom and cannot grow, so the recovery moved to the sweep, each row naming the blind spot it covers for. Reviews take longer. -
#11YAGNI no longer recommends deleting live code. Its documented grep says "specs, POMs, and other utility modules" while the glob beside it matched specs only, so a member called from another POM returned zero hits and was classified UNUSED. The glob covers the E2E root now and discounts only the member's own declaration line. -
#21covers Cypress. Detection keyed onstorageState:, a Playwright-only token, inside a two-framework skill. Cypress session JSON restored throughcy.fixtureor acy.session()callback is now swept. -
The lint-overlap map in the scanner report was wrong, and wrong in the direction that flattered this project.
#17,#4c-#4eand#10awere printed as having no ESLint equivalent whileprefer-locatorandprefer-web-first-assertionsship enabled ineslint-plugin-playwright@2.11.0, and#5a,#5b,#6were labelled opt-in while all three are in the recommended preset.#16pointed atmissing-playwright-await, which only sees matchers. The map is corrected against the published preset andSKILL.md's "~19 patterns" claim is now ~11. -
Both debuggers decide F1 versus F7 with an isolation probe. A timeout message alone cannot separate a non-deterministic test from one that only breaks beside its neighbours. The failing test now runs alone and repeated, then at the suite's real parallelism, and the pair of outcomes selects the code. When the suite cannot be run the finding is
CANNOT_VERIFYbetween the two rather than a guess. -
README states the ESLint boundary and leads with the fault-injection proof. The hero example is a shape
no-unnecessary-assertionsnow flags for free, so the README says so and points readers at the rule. A new section covers what no lint rule can do: V2 assertion inversion and V3 fault injection on an approved scratch copy, requiring the test to fail at the predicted line. -
#21names the credential leak. A committedstorageStatefile was described only as unreproducible; it also carries live session cookies for whatever account captured it, and#14does not reach it.
-
A public cross-host holdout run is recorded, and kept fail-closed. Codex missed the repeated-precision threshold, Opus met every preregistered threshold, and one Fable response was unscoreable, so the matrix is
INCONCLUSIVEandrelease_eligiblestays false. The reports are committed as evidence of what was measured, not as a claim about accuracy; they are marked stale against the current skill, because the skill changed after the run. -
docs/rule-self-audit.md— an adversarial audit of this project's own 24 patterns, 15 failure codes, and operational rules. Every finding was reproduced against the shipped scanner, and the claims that did not survive checking are recorded alongside them. -
Machine-readable skill classification metadata, and a definition of false-green that separates it from flaky.
-
Public artifacts now fail closed on real absolute user-home paths. The security gate scans tracked and unignored artifacts regardless of file type, including binary evidence and its own scanner source, while preserving explicit placeholders and hash-bound normalized provenance.
-
Taxonomy rows keep their Markdown structure. CI requires all four non-empty cells and the closing row delimiter, so an automatic hard-wrap cannot silently remove the user-facing rationale or fix guidance.
-
Behavioral-runner cleanup no longer races or strands same-group children. Cleanup observes process-group disappearance after
SIGTERMand escalates toSIGKILLonly when the group remains alive. -
Parity smoke tests use a publishable, disposable Git snapshot. Ignored machine-local directories no longer enter the copy, source symlinks fail closed, inherited Git, shard, and shell-startup variables cannot redirect or truncate the run, and the source digest includes file modes as well as paths and contents.
-
#4inames hallucinated locators. An inventeddata-testidthat never matched anything is indistinguishable from a selector that rotted, and an absence assertion keeps both green forever. -
Tier 2 no longer collapses on hosts carrying ast-grep 0.40 or newer.
capture_bounded_commandmerged stderr into the stream the strict NDJSON parser reads. Newer ast-grep writesError: N error(s) found in code.to stderr on a findings run, so the stream stopped parsing and the whole tier reportedINCOMPLETE. The stderr sink is now a separate positional argument; callers that read the merged text as human diagnostics keep the old behavior. The crash branch moved ahead of the empty-capture guard, because with stderr separated an empty capture no longer distinguishes "never started" from "started and crashed loudly". -
AST
#4ffindings were being dropped without a word. The Tier 2 post filter calledlocator_assertion_hit_matches, which is defined about 1,400 lines below the call site, so bash exited 127 and|| continueswallowed it. Every AST#4fhit disappeared silently — the failure class this scanner exists to catch. A self-contained confirmation now runs above Tier 2, and an unconfirmed hit becomes LLM triage instead of vanishing. -
Tier 2
#15no longer fires on a shadowedexpect.const { expect } = helpersandcatch (expect)were reported as firm findings because the provenance check only recognised a directconst expectredeclaration. A binding the file imports from Playwright and then redeclares is now dropped; an unknown customexpectstill goes to triage, unchanged.
- Three stages of the repository CI mirror got faster, each measured on its own:
the parity suite shards across disposable copies (629s to 217s), shell-file
enumeration stopped forking a grep per discovered file (15.3s to 1.0s), and
oracle text normalization is memoized. The scanner checks now run
concurrently, at half the cores — each worker spawns
scan.sh, so filling every core starved the checks that build large trees. No end-to-end figure is quoted: the suite also gained stages over this release, and wall-clock on a loaded machine moves more than any of these changes do.
- The independent-review packet stopped excluding this project's own case.
README_EXCLUDED_HEADINGSnamed headings a later README rewrite had renamed or deleted, so the exclusion silently became a no-op and the adoption, benchmark and competitor sections shipped to reviewers who are supposed to judge without them. The names are repaired and the runner now refuses to build a packet when a configured heading no longer resolves. Seebenchmarks/STATUS.mdfor the resulting packet discontinuity.
-
README now leads with the user decision instead of the evidence chronology. The English and localized READMEs move from a concrete false-green example to installation and first prompts, keep the complete 24-pattern catalog and 14 merged-PR table behind a shorter narrative, consolidate the shared F1–F15 taxonomy, and send benchmark audit detail to
benchmarks/STATUS.md. -
Benchmark conclusions now come before the audit trail. The README keeps only the current result and limits;
benchmarks/STATUS.mdpreserves detailed scores, failed gates, superseded runs, and evidence boundaries. -
Closure remediation and preregistered Codex robustness evidence. Seven independently confirmed v3 defects now have executable regressions: canonical scanner tool binding, Tier 3 continuation after Tier 2 failure, isolated pinned
npxexecution, framed preflight URL input, causal assertion-mutant attribution, full-root Phase 0 provenance tracing, and minimal debugger publisher environments. Protocol v4 was frozen before those edits and then ran three zero-tool Codex reviews on one 28-surface packet. The individual scores were 90.50 (FAIL, one high), 92.50 (PASS), and 91.50 (PASS); the fixed all-three gate therefore failed. This is curated-subset robustness evidence, not skill accuracy or a cross-model result; Opus and Fable were not run. -
The first remediation confirmation failed and remains archived. The v5 Codex-only phase scored 87.33, 88.00, and 88.00; all three attempts repeated one High finding because the raw-ARIA fallback accepted
localhostwithout a browser-transport DNS pin. The fallback now accepts only canonical numeric loopback literals and rejectslocalhostbefore project Playwright loads. Four confirmed Medium findings also gained contract regressions: qualified live-exploration scope, actionable snapshot sanitization, physical target-cwd interpreter exclusion, and sanitized debugger error excerpts. The other v5 r1 High is retained as an evidence-backed false-positive disposition because unresolved importedtest.onlyremains non-gating LLM triage in the executable scanner regression. -
V6 was superseded before packet freeze or model calls. Its immutable archive binds the failed v5 evidence and selected-remediation ledger, but an independent pre-call audit found that the 850,000-byte cap measured transformed source rather than the larger line-annotated representation actually sent in the prompt. V6 is therefore preserved as
SUPERSEDED_BEFORE_FREEZE/NOT_RUN, with zero packets, reservations, attempts, or model calls. The archive and runner now both fail closed against any later v6 freeze, ingest, prepare, synthetic, or live execution. -
Budget-corrected v7 successor defined before calls. The successor keeps the same 30 product surfaces, six dimensions, 90 overall / 85 per-dimension minimums, zero-Critical / zero-High limits, and all-three-attempt rule. It adds separately enforced transformed-source, annotated-content, canonical-packet, rendered-prompt, prompt-token, context-window, effective context, and output-reserve gates with a pinned tokenizer contract. V4 and v5 remain failed and v6 remains not run; v7 is post-hoc Codex-only selected remediation confirmation, not unbiased, cross-model, full-product, or accuracy evidence.
-
V8 completed and failed, and its five findings are remediated. Three preregistered Codex attempts ran against one frozen packet.
r1was consumed asINCONCLUSIVEwhen the local runner exited non-zero before a verdict,r2scored 87.67 andFAILon two High findings, andr3scored 92.67 andPASSwith three Medium findings. Under the FAIL-first aggregate rule the archive isCOMPLETE/FAIL; neither scored attempt reopened a bound remediation target. The five findings now have executable regressions: a bundled launcher that binds a validated absolute non-project interpreter for both debuggers, independent residual-credential detection, UTF-8 byte-length framing through one shared writer for preflight and raw-ARIA, foreign-test-module exclusion applied before Cypress basename admission, and exact pinned versions for every opt-in downloaded ESLint package. -
Adversarial re-review of those remediations found twelve bypasses; the two silent classes are closed. The reviewer scanner dropped every finding in a genuine Cypress spec when one runtime-erased type-only import was present, and degraded a gating finding to non-gating triage when a module specifier used a JavaScript escape. Both are closed: the Cypress provenance check is now multi-line aware, the foreign-module check distinguishes value imports from erased type imports, and one lexer now evaluates escape sequences for every detector. Separately, the debugger artifact readers emitted credentials whose keyword sat inside a longer identifier, whose separator was any comparison or compound operator, or whose value ran past the first space.
-
The residual-credential gate no longer parses values. Mirroring the redactor's value extent could not converge: too narrow certified a half-redacted line as safe, too wide failed closed on genuine error text such as a Chrome
credentials: 'include'message. The gate now asserts a marker invariant instead. For every sensitivity keyword followed by an assignment separator, the next token must be the redaction marker. The gate's separator alphabet is every ECMAScript punctuator containing=plus->,:=and:, and a test enforces that it stays a superset of the redactor's, so a separator the redactor does not handle fails closed rather than leaking. The accepted cost is stated in the module: values next to a sensitivity keyword are redacted whether or not they are secret. Known and still open: the value extent crosses exactly one continuation line, so the second and later lines of a multi-line value are emitted, and a value that begins on the separator's own line and then wraps is claimed only as far as that line. Both are recorded in the module and pinned by a boundary test so widening the extent stays a deliberate change. -
Superseded-phase evidence checks no longer assert a live tree. V6 froze source digests for a phase that was superseded before any model call, and v7 and v8 rebuilt their frozen packets from whatever the working tree currently held. Both broke permanently once later phases legitimately grew the same product surfaces past the earlier phases' frozen caps. V6 digests are now read as provenance, with the target files still required to exist unmodified in kind and each remediation still required to name a wired verification; v7 and v8 rebuild from their archived source snapshots, which reproduce the archived packets byte-for-byte. No frozen evidence, ledger, protocol, runner or validator digest was altered.
-
V9 is superseded before freeze; v10 is preregistered and frozen on Claude. The v9 Codex-only host matrix is no longer executable by this operator, and amending a pinned protocol after the fact would destroy the point of pinning it, so v9 is recorded as
SUPERSEDED_BEFORE_FREEZE/NOT_RUNinscripts/evals/independent-review-v9-supersession.json, following the v6 precedent. Unlike v6 that record preserves the absence of any measurement, because v9 never built a packet. The v10 successor binds the same completed v8 archive as its predecessor, since v9 produced nothing, and runs three attempts acrossclaude-opus-5twice andclaude-fable-5once: cross-model within one provider family, deliberately unbalanced, and never cross-provider evidence. Its ledger binds nine targets, the five v8 findings plus the four classes closed after that archive by adversarial re-review, and both the runner and the evidence validator now fail closed if any bound target cites a file outside the frozen packet, because such a target could never be reopened. -
V10 is re-preregistered on a bound-target-only packet, because the first frozen packet did not fit
claude-opus-5. Two independent pre-call audits measured the frozen prompt at 877,407 UTF-8 bytes through the pinned local CLI and foundclaude-opus-5rejecting it withPrompt is too long(exit 1), accepting 505,000 bytes and rejecting 530,000, whileclaude-fable-5accepted the full 877,407. The protocol maps a length rejection torunner_nonzero_exitand thereforeINCONCLUSIVE, and two of the three preregistered attempts areclaude-opus-5, so the phase could not have produced evidence. Rather than quietly dropping a model, the packet is reduced before freeze to exactly the seven surfaces the nine bound targets name: 433,862 line-annotated bytes and a 440,800-byte rendered prompt that both models accept with a well-formed strict-JSON review. The caps in the protocol drop with it. The reduction is declared rather than buried: the protocol carries apacket.surface_scopeblock naming the seven surfaces, the reason, and the narrowed claim, the ledger's scope policy and claim boundary say the same, and the README repeats it. The existing gate that every bound target'saffected_filesmust be a packet surface is preserved, and its converse is added, so no surface can enter the packet that no bound target cites and the reviewed scope can never exceed the declared scope. The schedule seed and version change with the scope and the digest is re-derived; the three attempt IDs are unchanged and none has been consumed. -
The v7 and v10 unit suites no longer skip their own adversarial checks. Four checks across those suites (frozen fake-attestation rejection, the public
--prepare-only/freeze integration, and the measurer's exact end-to-end contract) returned early whentiktokenwas missing. The ci-local Python runner has never had it, so in CI those checks were permanent no-ops. They now fail closed, andci-local.shruns both suites throughscripts/ci/run-reference-tokenizer-suites.sh, one hash-locked replay venv shared by both. The same escape hatch is removed from the v8 suite, which no CI gate invokes. -
The v10 prompt budget is measured in bytes, not in the model's tokens. V9 called its tiktoken
o200k_basecount an exact token replay. Against an Anthropic model that is false, so v10 renames the artifact to a prompt-size attestation: the exact quantities are UTF-8 byte sizes and their SHA-256 digests, and theo200k_basecount is labelled a deterministic size proxy in the protocol, the measurer, the attestation payload and the validator. No local source on the release machine publishes a context window for the two declared models, so the catalog recordscontext_window_provenance: unavailable-locally, declares no window, and v10 drops the context-window, effective-context and output-reserve gates entirely rather than asserting numbers it cannot verify. A prompt the model rejects for length is thereforeINCONCLUSIVE, not a silent pass. The local Claude Code CLI is pinned by SHA-256 and--versionunder the same provenance boundary v9 used for Codex: local hash and version only, never remote model attestation. -
The debugger helpers no longer choose their own interpreter. The publisher and downloader were prescribed as
/usr/bin/env -i PATH="$PATH" python3and as a barepython3. The first clears the environment and then resolves the bare name through the forwardedPATH, and the publisher executes the bundled reader in-process, so a project virtualenv or any writable directory ahead onPATHcontrolled the interpreter the reader ran under; the second inherited the environment outright, soPYTHONPATHcould run a checkout-suppliedsitecustomizebefore the helper's first statement. Both now go through the bundled launcher, which selects a root-owned absolute system interpreter outside the project and forwards only a per-script allowlist: nothing for the readers,PATHfor the publishers,HOMEand the twoghtoken variables for the downloaders.PYTHON*never crosses. The bundled scripts also declare a Python 3.9 floor, matching the oldest interpreter the launcher can select, and a Cypress downloader that crashed on that interpreter was fixed. -
The opt-in download tiers no longer trust the audited repository. Both npx invocations in the ESLint tier ran with the reviewed repository as their working directory, so that repository's
.npmrcchose the registry for the pinned packages, and a scoped registry key defeated a plain registry variable. The resolve step ran before the hardened environment existed at all, so the step that downloads and first executes third-party code inherited the operator's realHOMEand npm credentials. Every npx call now goes through one helper that runs from a private work directory with its ownpackage.jsonanchor and scanner-owned.npmrc, pins registry, cache, prefix, both config paths andignore-scripts, and refuses to run before that environment exists. Only the direct versions are pinned; the transitive closure still re-resolves at scan time, which the security and reference docs now state rather than imply otherwise, with lifecycle scripts disabled to bound it. -
Tier 2 had been dead on every npm 9 or newer host. The ast-grep launcher set both the user and global npm config paths to
/dev/null, which npm rejects as a double load before resolving any config, so the tier produced no AST findings at all. The failure was loud only by accident: the tier's own exit-code guard never fired, and what surfaced the problem was npm's diagnostic landing on a stream the JSON parser then rejected. Since that parser exits zero on empty input, a launcher failing quietly would have reported a clean tier. The config is repaired, the tier now shares the hardened npm environment, and a guard fails closed when the launcher exits non-zero with an empty capture. -
The Claude host's prompt limit is recorded as a measurement, not an assumption. Probing the pinned CLI with the frozen v10 prompt showed
claude-fable-5accepting all 877,407 bytes and returning a well-formed review, whileclaude-opus-5refused it and every payload down to 530,000 bytes, accepting 505,000. Two of the three preregistered v10 attempts areclaude-opus-5, so the schedule cannot complete at the current packet size. This was found before any scheduled call, so no preregistered attempt id was consumed and the archive remainsFROZENat zero attempts. -
The contract suites run on Linux again. Seventeen call sites created their temporary trees under a hardcoded
/private/tmp. That path is macOS-only: it exists because macOS/tmpis a symlink and the bundled launchers reject symlinked roots, but hardcoding it broke every non-macOS runner withFileNotFoundError. The suites now resolve the platform temp root once, which keeps the no-symlink property on macOS and works everywhere else. -
CI binds ast-grep the way the scanner expects.
scan.shresolves ast-grep only from a fixed absolute candidate list, deliberately, because PATH is attacker-controllable for a tool that runs across a reviewed repository. A hosted runner's global npm install lands outside that list, so the scanner fell through to the opt-in download path, whose progress output is not JSON, and Tier 2 reported an infrastructure failure. The workflow now points the documentedE2E_SMELL_AST_GREP_BINoverride at the version it just pinned, and the job that intentionally has no ast-grep disables the download instead of attempting it. -
Public evidence docs refreshed to the 2026-07-31 state. README and benchmark docs now describe the 59-source ledger (21 verified / 14 qualified / 24 not cleared), separate WebTestPilot's 100 injected-bug benchmark from its 22/23 real-bug replication study, and qualify WEFix and AutoE2E as peer-reviewed evidence for narrower questions rather than this skill's accuracy.
-
Reviewer validation docs now point to v5/v3.
reviewer-holdout-v5is the current pre-live public corpus (20 cases, 50 source files, 24 findings, 24 guards, 12 positive cases, 8 globally clean cases, 10/10 Playwright/Cypress split) with independent positive and clean source audits passing.v4is historical and invalid for performance claims after oracle audit, with only three diagnostic calls.reviewer-fault-causal-v3is current;causal-v2is historical/invalid for current claims because answer-leading comments leaked expected verdicts. -
Benchmark protocol language tightened. Public docs now require the full 9-report
full/catalog-only/no-skillmodel/arm matrix before any skill-lift comparison, forbid partial lift claims, and state that no live v5 reviewer result or generator v2 result is claimed. Generator validation is described as prompt-complete planning accuracy against stated acceptance criteria, not source generation or autonomous oracle discovery. Debugger v1 is described as synthetic author-created labels. -
Debugger benchmark docs updated for the hardened schema-v2 surface. Public docs now describe strict-majority stable unique-case metrics, Wilson intervals, repeated accuracy and macro precision, framework/category worst slices, and the raw-output re-deriving comparator over the fixed Codex
gpt-5.6-sol, Claude Opus, and Claude Fable provider-family matrix. The limitation remains explicit: the corpus is 30 short public author-created synthetic excerpts, not full reports/traces, and it has no independent oracle audit or live result claim.
- Playwright and Cypress media readers now verify that the requested path still names the descriptor they opened, so a same-read rename or symlink replacement fails closed instead of returning a snapshot from a path that changed underneath the read.
- Portable scanner contracts can explicitly disable Tier 2 so preinstalled
host
ast-grepbinaries cannot make no-tool checks nondeterministic. - Artifact-reader launchers now select GNU
statformats before BSD formats, preserving the root-owned system-Python boundary on Linux as well as macOS.
- Neutral all-family reviewer holdout v4. Twenty repository-shaped cases and 50 source files separate 24 exact findings from 24 matched false-positive guards. Playwright/Cypress and positive/clean cases are balanced, every base pattern family appears once on each side, and clean-case specificity is gated independently. The labels remain synthetic public development evidence rather than an independently adjudicated oracle.
- Three-model strict-majority evaluation. The v4 protocol fixes a seeded
60-call schedule per configuration, three repetitions, per-model
precision/recall and
guard/clean-specificity thresholds, all-pairs prediction agreement, and
provider-family-balanced recall-gap checks before live execution. CI
re-parses raw model outputs rather than trusting serialized scores; a
legitimate performance
FAILremains valid evidence, while incomplete or provenance-mismatched reports fail the integrity test. The declared matrix is Codex, Claude Opus, and Claude Fable across two provider/runtime families; three configurations are not presented as three independent hosts. - Recoverable live benchmark artifacts. Runner timeouts now terminate pipe-holding descendants even after their CLI leader exits, Claude live runs ignore user/project/local settings and plugin sync, and per-configuration/comparison reports are replaced atomically so infrastructure failures cannot truncate the last valid partial report.
- V1–V6 verification doctrine for generated and reviewed E2E tests. The skills now require outcome-first assertions, falsification, behavior fault injection, write-contract checks, repeat/isolation evidence, and independent re-review rather than treating a green run as proof of correctness.
- Cypress-specific reliability checks. The reviewer now triages async
callbacks containing queued commands (
#10d), assigned Cypress commands (#10e), and unsafe continued action chains (#10f) alongside the existing Playwright coverage. - Cross-host subagent fallback and packaging. For the reviewer and
debuggers, Claude named agents, Codex native verifier/debugger roles, and
inline fallbacks share one taxonomy and verdict contract. The generator does
not claim that equivalence: V6 requires an independent fresh-context,
read-only reviewer and returns
CANNOT_VERIFY/PARTIAL/BLOCKEDwhen that separation is unavailable. Optional Codex agent installation includes a conflict guard and parity test without requiring Python 3.11 or a package install. - Paired behavioral evaluation harness.
scripts/evals/run-behavioral-evals.pycompares repeatedwith_skillandwithout_skillruns, records runner/version and repository provenance, and reports per-case lift and saturated baselines. The live runner is opt-in; ordinary CI uses a deterministic fake runner. - Historical v2/r3 machine-labeled reviewer holdout. A hard public development corpus covers eight Playwright/Cypress multi-file cases with 30 unique findings and 31 explicit false-positive guards. The isolated runner computes exact TP/FP/FN across repeated Claude/Codex runs while keeping unique majority-stable precision/recall separate from repeated-run stability evidence and recording model, CLI, Git dirty state, evaluated-skill/corpus digests, timing, and raw-output provenance. Public runs use fresh temporary workspaces, read-only host controls, and pre/post staged-workspace digests; mutations are infrastructure errors. Non-public corpora require an explicit external isolation wrapper because the harness does not claim a built-in sealed sandbox. Infrastructure failures keep the report incomplete and never become false negatives.
- Executable behavior-fault fixtures. Real pinned Playwright and Cypress
apps now exercise twelve fault operators across 36 browser cells and prove the
falsification chain: strong tests pass on correct behavior, fail after
behavior fault injection, and assertion-mutated weak tests stay green against
the same fault. The added
#4joperator proves Playwright's documented unnamed-ARIA-snapshot false green against an accessible-label fault. CI validates fixture contracts without downloading browsers; full browser runs remain explicit release evidence. - Audited LLM-test evidence ledger. A 55-source primary-source review now separates verified, qualified, and not-cleared claims; corrects misleading denominators and model rankings; adds the peer-reviewed WebTestPilot, WEFix, and GenIA-E2ETest results; and replaces the former “no direct study” claim with a narrower evidence boundary.
- Exact operator-linked causal reviewer benchmark v2. Twelve weak-test cases are mechanically derived byte-for-byte from executable fixture operators and paired with twelve separate clean guards. A linkage ledger pins transformation spans and hashes, while CI reconstructs every mapping. This measures whether the reviewer detects proven false-green mechanisms without presenting public exact-match evidence as sealed generation quality.
- Debugger holdout v1. Thirty sanitized report excerpts cover F1–F15 once per framework and score category, confidence, product/test diagnosis, impact, urgency, and confirmed-test-defect severity as separate axes.
- Generator fault-kill planning v1. A closed declarative plan language compiles into trusted Playwright templates for behavior, label, auth, and write faults. Case, fault-mode macro, and worst-case metrics are reported; Cypress inputs are explicit unscored scope controls, and model-generated code is never executed.
- Unanchored independent product-review runner. A fixed packet of 25 product files excludes holdouts, evals, results, scorecards, prior reviews, and Git history before Codex, Claude Opus, or Claude Fable sees it. The zero-tool runner freezes source/representation digests, requires strict file-and-line evidence, and scores six fixed dimensions while disclosing that model-family review is neither human nor sealed adjudication.
- Playwright 1.62 floating-Promise semantic probe. A separate six-cell
browser probe covers #15 assertions and #16 Locator actions. Awaited clean
calls exit 0, awaited faults exit 1, and the same faulting calls still exit 1
after deleting only their leading
await. This negative result is archived separately from the 11-operator/33-cell weak-green fixture matrix; CI validates the report and contracts without rerunning the opt-in browser work. - Preregistered v2 decision protocol. A seeded schedule, three-run majority
rule, unique-unit Wilson intervals, per-host release thresholds, and
fail-closed protocol/workspace integrity checks are frozen before live calls.
compare-reviewer-holdouts.pyadditionally requires identical provenance, both individual reports to pass, a Claude/Codex recall gap no greater than 10 percentage points, and stable-prediction Jaccard agreement of at least 0.80. - Immutable benchmark evidence bundle.
benchmarks/reviewer-holdout-v2/now freezes the initial oracle, full raw Claude/Codex reports, catalog-only controls, ablation outputs, oracle revision ledger, hardened rerun, and post-run adjudications. CI pins every artifact by SHA-256 and re-derives the documented aggregates.
- Adversarial boundaries now fail closed across scanning, generation,
debugging, evaluation, and installation. Unresolved custom-fixture imports
keep every affected P0 family visible as non-gating LLM triage instead of
suppressing findings; framework-unproven
.e2e.*files cannot create gating P0 output; computed focused-test aliases are detected without matching arrow callbacks; and credential-like scanner evidence is location-only. Generator exploration installs a pre-dispatch navigation guard and requires an exact, redirect-disabled target preflight. Debugger and malformed-model projections redact credentials before truncation. The contributor reinstaller now pins and hashes the completeskills@1.5.21dependency tree, verifies requested receiving surfaces, and restores the prior four-skill state on ordinary failure orHUP/INT/TERM. - Blind product-review blockers were fixed without changing benchmark
thresholds or labels. The scanner now applies one eight-extension JS/TS
source contract across ESLint and bundled checks; lexically excludes
focused-test strings, comments, and unrelated receivers; detects formatted
multi-line #4f Locator assertions; routes raw #4a and #14 candidates through
semantic triage; and rejects
ast-grep/sganywhere inside the containing target project when scanning a subdirectory. The holdout runner now validates corpus severities against the evaluated--skill-dirand its frozen copy, and live children receive runner-specific environment allowlists instead of the ambient caller environment. - Second blind-review boundary defects now fail closed. Tier 3 rejects
missing PCRE2 support and ripgrep scan errors instead of reporting a false
clean result; framework-content scoping covers custom
testMatchbasenames;JUSTIFIEDsuppression requires a lexical line comment with a nonempty rationale; nested test-directory scans resolve opted-in ESLint from the containing project while retaining the requested file boundary; and #8 candidates require semantic confirmation that the discarded read was the scenario's only verification. Holdout corpus paths cannot overwrite runner-controlled surfaces, and the staged skill digest is verified against the frozen skill before and after every call. Non-public wrapper runs remainINCONCLUSIVEbecause executable presence does not attest source isolation. - The standalone scanner no longer executes target-project code by default.
Local ESLint, plugins, parsers, and flat config now require the explicit
E2E_SMELL_ALLOW_PROJECT_ESLINT=1trust capability. Opted-in Tier 1 receives only E2E-proven files and a minimized environment with a temporary home/config/cache; documentation states that this reduces ambient credential exposure but is not a sandbox. Project-rootast-grep/sgshims are rejected, while bundled Tier 3 remains load-bearing. - Scanner scope and context boundaries are stricter. Tier 1 no longer lets
neighboring Vitest/Jest files become Playwright/Cypress P0 findings,
multiline conditional boolean reads are routed to #5a triage instead of
misclassified as discarded #8b values, and Playwright-proven POM/support
.catchpaths now receive mechanical or triage coverage. - Pattern #4g now matches Playwright 1.62 runtime semantics. A zero matcher timeout removes the assertion-local deadline and can consume the enclosing test/hook timeout; it does not collapse a web-first assertion into a one-shot read. The P1 severity is unchanged. A separate browser probe and archived validator lock this behavior without altering the 11/33 weak-green matrix.
- Independent product-review defects are fixed instead of scoring around
them. Pattern #18 now states the real Playwright contract: soft assertion
failures still fail the test, while the review concern is dependent work
continuing before a hard scenario gate. Pattern #16 now covers the broader
Locator action surface and formatted multiline chains. README lint and
page.click()guidance no longer overstates plugin completeness or invents an auto-wait distinction. The prior 24/24 Codex report is retained as pre-product-review evidence and all three configurations must rerun against the corrected snapshot. - Fixture evidence is independently inspectable. Schema v3 archives sanitized bounded stdout/stderr for every successful and failing browser cell, includes the fixture evaluator's own SHA-256, and fails closed on missing output, provenance drift, unsafe redaction, or an over-limit archive. The expanded report was regenerated from a fresh 33/33 browser run.
- Framework semantics and severity boundaries were tightened. Standalone
Cypress
cy.get()queries are no longer treated as discarded Playwright locators; one-shot but fallible Playwright reads are P1 retry weaknesses rather than P0 always-pass defects; and missing-auth #12 is P0 only when the login/wrong surface can actually satisfy the test. - Missing-await retry and receiver boundaries were corrected.
toPass()andexpect.poll()no longer exempt floating #15/#16 Promises that their callback neither awaits nor returns. The scanner now routes Locator variables and POM properties to semantic triage while keepingPromise.allactions and action-only visibility branches outside its mechanical P0 exit gate. - Scanner tiers now fail closed without policy leaks. A project's disabled
ESLint rule can no longer suppress the bundled Tier 2/3 checks. Exact
file/line/rule-class deduplication prevents duplicate counts, AST-only P0
findings affect the default exit gate, non-E2E AST hits are filtered, the
explicit ast-grep download path invokes the correct binary, and formatted,
commented, nested
Promise.all/Promise.raceactions retain their exclusion. - Debugger report correlation was hardened. Cypress JUnit extraction now
uses a bundled standard-library XML parser that keeps each failure paired
with its testcase, classname, suite file, and report. The Cypress debugger
never auto-installs
mochawesome-merge; the Playwright debugger deduplicates the same file/title across browser projects and aggregates project names. - Cross-host comparisons require the full corpus. Reports produced with
--caseremain valid development artifacts, but the release comparator now rejects any schedule whose case set differs from the selected corpus. - Playwright worker-retry semantics were corrected. Pattern #19 now focuses on cross-test persistence and cross-worker collisions; it no longer claims a failed Playwright test retries in the same worker.
- Lint and AST tooling are no longer auto-downloaded by default. The
reviewer reuses project-native tools and keeps the dependency-free fallback;
offline execution no longer requires
npx, ESLint, or ast-grep downloads. - Local ESLint plugins are invoked directly. When a target project already
has ESLint and
eslint-plugin-playwright/eslint-plugin-cypress, the scanner runs the project'snode_modules/.bin/eslintand resolves its local rules without usingnpxas a wrapper. The legacy npx path is limited to explicit opt-in downloads when local tooling is absent. - Eval and contract parity are now CI-checked. Verification-rule parity,
result-schema parity, subagent fallback parity, and behavioral-harness
regression checks run from
ci-local.sh. - The parity drift smoke is portable to macOS Bash 3.2. Expected-error
matching no longer uses a
grep -qpipeline whose early exit can trippipefailwith a false SIGPIPE failure. - The reviewer evaluator now fails closed against measurement tampering. Live runs copy the complete skill and corpus once, use only that snapshot, verify source and snapshot digests after execution, preregister the exact model matrix, and gate repeated precision as well as majority-stable metrics. The cross-host comparator re-parses raw output and re-derives the schedule, every run score, aggregate, and status. Deterministic regressions cover rotating false positives, report/status/metric/schedule tampering, oracle-token leakage, and skill/corpus drift. Host/model fields remain declared local provenance rather than signed execution attestation, and the benchmark docs state that boundary explicitly.
- Fixture evidence records executable provenance. Runtime reports now include fixture/operator/lockfile digests, normalized commands, output hashes, Python/Node/framework versions, and platform identity. A browser-free CI test exercises all 33 classification cells plus missing-marker and unexpected-exit paths.
- Contributor documentation now distinguishes deterministic CI evidence from optional live model evidence. A passing smoke case is not presented as a general precision/recall or cross-model claim.
- Public benchmark claims are sample-scoped. README translations and the benchmark page now say “no judged false positives in this sample” instead of implying perfect precision, distinguish re-derived aggregates from a full raw replay, and separate merged, in-review, and queued upstream counts.
bash scripts/ci/ci-local.sh— all non-v3-evidence stages passed; drift smoke 49/49. The v3 evidence stage remains intentionally blocked until both full Claude reports exist.bash scripts/ci/pre-push-security.sh— passed; 10 checks, 0 warnings, 0 blockers.- Codex behavioral pilot —
with_skillandwithout_skillboth passed the easy reviewer case, producing 0% measured lift and correctly marking the case as saturated rather than overstating the result. - Executable Playwright/Cypress fault matrix — 33/33 expected outcomes matched across eleven fault operators on a fresh fixture dependency install.
- Playwright 1.62 floating-Promise semantic probe — 6/6 expected exits matched across #15 and #16, including two nonzero fault-unawaited runs; the canonical weak-green fixture report remains 11 operators / 33 cells.
- Labeled holdout fake-runner regression — exact repeated scoring produced the expected 6 TP / 4 FP / 2 FN, including explicit FP-guard attribution.
- Preregistered full public-holdout baseline — Codex
gpt-5.6-solpassed with 25 stable TP / 0 FP / 0 FN; Claudeclaude-opus-5completed all 24 calls but failed the precision gate with 24 TP / 2 FP / 1 FN. Cross-host recall gap (0.04) and stable-prediction Jaccard (0.889) passed, but the combined gate failed because both host reports must pass. - Catalog-only ablation — both controls failed the precision gate: Codex 24 TP / 13 FP / 1 FN, Claude 24 TP / 4 FP / 1 FN. The full workflow improved stable F1 by 0.226 on Codex and 0.036 on Claude; the Claude lift was below the separately preregistered 0.05 ablation threshold.
- Targeted post-fix public regression — after clarifying acceptance targets and
causal source-line anchors, both hosts completed the two affected cases three
times with 7 stable TP / 0 FP / 0 FN. This is contaminated development
evidence, not a sealed-corpus or universal-accuracy claim. A later full Claude
rerun hit the local CLI session limit and was correctly kept
INCONCLUSIVEinstead of being scored as false negatives. - Oracle re-adjudication — a reviewer challenge to unexpected predictions found
that the initial 25-label corpus had omitted two invariantly true
#4aassertions, one single-use#11POM method, and two name/assertion contract failures under#1. The corrected corpus now has 30 findings and 31 guards. Initial live scores remain identified as results against the old oracle and are not relabeled as preregistered evidence. - Hardened current-oracle Codex rerun — all 24/24 calls completed with zero infrastructure errors, but the frozen r3 score failed: stable 30 TP / 4 FP / 0 FN (precision 0.882) and repeated 86 TP / 14 FP / 4 FN (precision 0.860). The failure was preserved rather than rerun away.
- Post-run oracle challenge — four isolated finding verifiers independently
confirmed all four majority-stable “false positives” were real
#4adefects omitted by r3. The report was not rescored; it is marked oracle-invalidated for performance claims. This is evidence that the public synthetic corpus still needs a new blind/human-adjudicated replacement, not evidence for a 100% precision claim. - The matching current-oracle Claude run could not start because Claude Code reported its session limit and a reset at 18:30 Asia/Seoul. No partial run or substitute model was reported as cross-host evidence.
-
README ×4 now state that Tier 1 honors the project's own ESLint config. The eslint-plugins FAQ said only that the scanner "runs them first (Tier 1)", which was accurate before this release and misleading after it — a reader had no way to know their
offis respected there. All four languages now say the project's flat config is layered over the plugin'srecommendedpreset. -
The scan summary now separates what a lint rule can enforce from what only a review catches. A report of
[P0] #4fand[P0] #1gave no hint that the first is a one-line ESLint config change and the second is a judgement no rule can express, so every finding read as equally manual. The closing summary now splits the pattern IDs that fired into "enforceable by a lint rule" — naming the specific rule, and flagging when it is opt-in rather than part ofrecommended— and "no ESLint rule expresses these". The mapping was checked against the published rule lists rather than assumed:#5a,#5b,#6,#8a,#9cand#17all have upstream rules (several outsiderecommended) and had been miscounted as review-only in the first cut, which would have told users to keep reviewing something they could enforce once. Of the 24 patterns, most still have no ESLint equivalent at all — which is the point the split makes visible. -
Tier 1 now follows the project's own ESLint config; Tier 2/3 deliberately do not. The scanner generated a config from the plugin's
recommendedpreset and passed it with-c, which makes ESLint ignore the project's config entirely — so a team that had deliberately set'playwright/no-focused-test': 'off'still saw it reported, and the only escape hatch wasE2E_SMELL_NO_ESLINT_DOWNLOAD=1, which drops Tier 1 wholesale. A project's flat config (eslint.config.mjs|js|cjs) is now appended after the baseline; flat config is an array and later entries win, so a disable genuinely takes effect. Severity edits (error↔warn) are still ignored — severity here is P0/P1, assigned by this skill. Legacy.eslintrccannot be imported from an ESM flat config and keeps the previous behavior, which the scanner now states outright instead of leaving the reader to assume. Tiers 2 and 3 are unchanged and keep reporting regardless: they ask whether a test can fail, not whether a lint policy permits it, which is what keeps the finding count reproducible across hosts. The output note explains which tier obeyed what. In practice this affects a handful of patterns —#7,#9,#15/#16and Cypress#7,#9bmap 1:1 onto an ESLint rule;#4fis only partly covered upstream; the remaining ~19 have no ESLint equivalent, so no project config can disable them. -
Cypress counterpart submitted upstream. The same defect exists in Cypress —
cy.get()returns a chainable that exists whether or not the selector matches, soexpect(cy.get('.badge')).to.existpasses on a blank page — andeslint-plugin-cypresshas no rule for it. Proposed there asno-unnecessary-assertions, matching the Playwright rule's name, with the same autofix discipline (rewrites to.should('exist'), never to a stronger claim; skips chains ending in.its()/.invoke(), where an element assertion would fail at runtime). Left opt-in rather than added to that plugin'srecommendedconfig, since an error-level addition there would break existing consumers' CI on a minor release. Until it lands,eslint-plugin-cypress-silent-passremains the Tier-1 path for#4fon the Cypress side. -
#4fis now covered by the official ESLint plugin; the Playwright companion package is retired.no-unnecessary-assertions— contributed upstream from this project in mskelton/eslint-plugin-playwright#470 — shipped in v2.11.0 and is enabled by that plugin'srecommendedconfig, so Tier 1 catches this class with no extra setup oneslint-plugin-playwright@>=2.11.0. Verified end to end: the scanner reports the rule's own diagnostic, and Tier 2 cedes itssg-4frule to Tier 1 as designed.eslint-plugin-playwright-silent-passis therefore deprecated on npm andscan.shno longer downloads or resolves it for Playwright — doing so would pull a deprecated package and double-report the same finding.eslint-plugin-cypress-silent-passis unaffected and still carries the Cypress side, which has no official equivalent. Docs updated accordingly: SKILL.md and README ×4 now say the rule shipped in v2.11.0 rather than "ships in the next release", and the deprecated package's npm badge is removed from all four READMEs (the Cypress badge stays).
-
New anti-pattern
#4i— Absence assertion never proven able to match. An absence assertion is satisfied by a locator that matches nothing: Playwright definestoBeHiddenas "either does not resolve to any DOM node, or resolves to a non-visible one", andnot.toBeVisible()is the inverse oftoBeVisible("attached and visible").toHaveCount(0)and Cypress.should('not.exist')behave the same way. A selector that rots — a renamed class, a framework migration, a component rewrite — therefore keeps the test green forever while proving nothing. This is the failure mode that survives longest: a rotted positive assertion fails on the next run and gets fixed, while a rotted negative assertion is indistinguishable from a passing test, so suites accumulate coverage they do not have. Grouped under#4(Always-Passing Assertions) as a P1 sub-pattern. Detection is grep + LLM-TRIAGE, kept outside the P0 exit gate because grep cannot see the rest of the test: Phase 2 skips a hit when the same locator is asserted present or acted on earlier in the test or itsbeforeEach, and when an empty-state test asserts a positive counterpart (the dominant legitimate shape); it flags only when the locator appears nowhere else and nothing positive is asserted alongside. The scanner regex excludes the inverted forms (.not.toHaveCount(0),.not.toBeHidden()), which assert presence. Added acrossSKILL.md,references/pattern-reference.md,references/grep-patterns.md, andscripts/scan.sh. -
New anti-pattern
#10c— Unscoped accessible-name substring match. A page-scopedgetByRole/getByLabel/getByPlaceholderwith anameand noexact: truematches the accessible name as a case-insensitive substring (per Playwright docs). When the page also renders user- or data-controlled text containing that word, the locator resolves to 2+ elements and Playwright throws a strict-mode violation — a hard failure, or an intermittent flake when the colliding content only sometimes appears. Grouped under#10(Flaky Test Patterns, P1). Detection is grep + LLM: the scanner flags page-scoped non-exactcalls and Phase 2 confirms both that the accessor is page-scoped and that the suite renders colliding dynamic text before flagging — skipping already-container-scoped calls,exact: true, anchored regex names, distinctive multi-word names, and static-only surfaces. Cypresscy.findByRole(...)shares the same substring default. Added acrossSKILL.md,references/pattern-reference.md,references/grep-patterns.md, andscripts/scan.sh. -
CI: skill
descriptionbudget check, and the review gate moved ontopre-push.descriptionis the cross-host trigger surface and is pre-loaded for every installed skill, so it is budgeted rather than free — Claude Code rejects anything over 1,024 characters, while Codex caps the whole skill list at 2% of the context window (8,000 characters when unknown) and, over budget, shortens descriptions first and may drop skills entirely. A long description therefore loses its tail silently on Codex, taking any trigger phrases parked at the end with it.review.shgains aSkill description budgetsection that fails over the 1,024 cap and warns past 900, so a description cannot drift into the cap unnoticed and there is visible headroom to add triggers later. Measured at the time of writing: the four skills use ~3.4k of the 8k Codex list budget (43%), and all four front-load their trigger phrases. Separately,scripts/hooks/pre-pushnow runsreview.sh --quietbefore reinstalling the local skills copy, so the parity, link-integrity, language, subagent, and description-budget checks fail locally in ~6s instead of after the push. Warnings never block; only errors do, andE2E_SKILLS_SKIP_REVIEW=1bypasses it in an emergency. This does not replace CI — hooks are local, skippable, and absent from fresh clones, and the eval/drift-smoke/scanner suites still run only inci-local.
#2accept-criteria no longer whitelists absence checks unconditionally. The "Non-standard negative assertion" accept-criterion listedtoHaveCount(0),toBeEmpty(),toBeNull(), and a capturedisVisible()compared tofalseas valid absence checks with no further condition, which steered reviewers past the exact shape#4idescribes. It now requires that the locator was proven able to match, and points at#4ifor the case where it was not.#23detection generalized beyond list/card fixtures. The render-guard check was scoped to items inside a list or card component, so a guard on a container — a results panel wrapped in@if (result.type === TABLE), for example — did not map onto it, even though every control inside that panel disappears when the seeded value takes a different branch. Detection now covers any conditionally-rendered component, including whole-subtree template guards (@if/v-if/{cond && …}), with a scope note telling the reviewer to walk up the template to the nearest guard before suspecting the selector.- Skill
descriptionrewritten to triggering conditions only. It previously opened with "Static review of …" and closed with "Flags 24 anti-patterns grouped P0/P1/P2" — a summary of what the skill does. Per skill-authoring guidance, a description that summarizes the workflow becomes a shortcut agents take instead of reading the skill body; here the summary also understated the work, framing the review as a static scan of a fixed pattern count. The description now states only when to load the skill and when not to, and drops the hard-coded pattern count that had to be maintained on every addition. #12now requires reading the framework config before assigning severity. Path (b) of the#12rule —storageStatesupplied by Playwright config — is invisible from the spec file: asetup/global.setupproject plus a project-levelstorageStateauthenticates every spec in that project with no auth line in the spec at all. Nothing in the skill told the reviewer to open the config, so whether they noticed was luck, and the same suite scored 0, 1, or 2 Real P0s across repeated reviews — a direct breach of the counting contract, which requires two hosts reviewing the same commit to arrive at the same N. Both the Phase 2 dispatch row and the#12contract now make the config read mandatory, name the Cypress equivalents (cy.session()in a support file,cypress.config.*login tasks), and require the finding to say so explicitly when the config cannot be located rather than assuming either way.- Phase 4's canonical-replacement table is now enforced structurally instead of by reminder.
SKILL.mdalready said, in bold, to readreferences/applying-fixes.mdbefore writing any fix — and reviewers emitted fix snippets without opening it anyway. That is the documented failure mode for prose reminders placed near a template: the right form for "omits a required element from something they already produce" is a REQUIRED slot in the template itself. The finding template therefore gains a**§4.1 row:**field, required whenever a**Code:**block is present, which must quote the AVOID → USE row verbatim fromapplying-fixes.md(or stateno row (judgement call)). The field cannot be filled without opening the file; a fix emitted with it blank or paraphrased was written without the table and must be redone against it. - Upstream tooling contribution:
#4flanded in the official ESLint plugin. The always-passing-Locator-assertion smell (expect(locator).toBeDefined()/.toBeTruthy()/.not.toBeNull()) was contributed to the officialeslint-plugin-playwrightas theno-unnecessary-assertionsrule (#470, merged; ships in the nexteslint-plugin-playwrightrelease). Repositioned the docs accordingly: for Playwright,#4fis now recommended via the official rule rather than the standaloneeslint-plugin-playwright-silent-pass;eslint-plugin-cypress-silent-passstill covers the Cypress side. Updatede2e-reviewer/SKILL.mdcompanion-plugin guidance, README ×4 (the "what a linter cannot catch" section, the eslint-plugins FAQ example, and the roadmap deterministic-detection item), anddocs/roadmap.md(new "Upstream tooling" line, tracked separately from the merged test-fix count, which stays 14). Follow-up (separate repo): archivevoidmatcha/eslint-plugin-playwright-silent-passand deprecate its npm package now that the official rule supersedes it.
-
README i18n drift: the eslint-plugins FAQ correction (removing
expect(locator).toBeTruthy()from the "a linter structurally cannot decide" list, since it is now lint-covered by the upstreamedno-unnecessary-assertionsrule) was only applied to the English README in 1.8.0's follow-up; mirrored it intoREADME.ko.md/README.ja.md/README.zh-cn.md, which had otherwise contradicted their own "#4f is lint-covered" line. -
README ×4 "24 Patterns" table now lists the new
#4and#10sub-patterns.SKILL.md's Quick Reference gained4iand10cin the signal/fix columns, but the public README table — the same rows, in all four languages — still showed the pre-1.9.0 examples. The parity check compares IDs and severities, not the example prose, so nothing failed; the reader-facing table was simply stale. Both rows now carry the new signal and its fix (assert the element present once before asserting it gone; scope the accessor or passexact: true). Sub-IDs remain absent from the top-level pattern count, which stays 24, matching how#4g/#10a/#8aare already handled. -
The description-budget check now fails closed. It parses the helper's stdout (it has to distinguish FAIL from WARN), and the first cut swallowed the exit code with
|| true— so a crashed interpreter produced empty output, both branches read as empty, and the check printed[OK] all skill descriptions within the 1024-char cap. That is the same false-pass shape as the undefined-$ROOTbug caught earlier in this work: a guard that reports success precisely when it did not run. It now requires a zero exit and the expectedBAD:/WARN:envelope before trusting the result, errors otherwise, and the helper itself reports when it walkedskills/without finding a singleSKILL.md. -
#4inow ships with an eval, and two examples stopped being repo-specific. Per the contributor rule that every new smell lands with a true positive and a false-positive guard, eval 22 addsevals/files/absence-assertion.spec.ts: the scanner tags all four absence assertions[LLM-TRIAGE], and Phase 2 must keep exactly one — the unproven.job-controls .spinner— while skipping the proven-present locator, the empty-state case with a positive counterpart, and the locator used as an action target. Those three skips are the FP surface that decides whether#4iis usable, so each is asserted by line. Separately, two examples carried selectors from the repo the pattern was found in rather than generic ones:#10c's "GOOD" line scoped through[nz-menu-item](an ng-zorro attribute, meaningless outside Angular + Ant Design, and inconsistent with the.headerexample in its own Rule paragraph) and#4iused.paragraph-control. They are nowgetByRole('navigation')and.job-controls. Scanner regexes were checked for the same coupling and carry none. -
pattern-reference.md's own index had not learned about#4i. The navigation block said sub-IDs#4a–#4hlive in#### 4.and that only#5/#10span two severities, and the P1 row listed no#4ientry — so a reader looking4iup in the index was told the#4block ends at4h, while the per-pattern section 60 lines down documented it. The index is explicitly not CI-validated (see the comment above it), which is why it drifted silently. Index and P1 row now both carry#4i, following the existing#5b force:true (in #5)convention. -
Drift-smoke Case 11 no longer depends on the description's wording. Its
mutatestep anchored on the literal stringdescription: 'Static review— the opening words of the e2e-reviewer description. Rewriting that description to triggering conditions (see Changed) removed the anchor, so the case aborted withsubstring not foundandci-localfailed, leaving the guard it protects (unquoted plain scalar containing": ", the v0.7.3 YAML regression) untested in the meantime. Anchoring on the opening quote alone was not sufficient either: the rewritten description contains no colon-space, so an unquoted version of it would not trip the guard at all. The case now injects the whole bad shape (description: Static review: unquoted with colon-space '…), reproducing both conditions regardless of what the real description says.
- Localized READMEs at the repo root:
README.ko.md,README.ja.md,README.zh-cn.md(EnglishREADME.mdstays canonical), with a language switcher in all four and a README i18n structural-parity guard (newreview.shcheck, pre-push hook guard, and drift-smoke case). CONTRIBUTING.md(setup, verification gate, frozen-ID and parity conventions) anddocs/case-studies.md(merged upstream PR write-ups).scripts/ci/codex-smoke.sh— manual, reproducible Codex cross-host smoke with committed fixtures (self-skips whencodexis absent).- Scanner: Phase-0 e2e-file scope filter with skipped-file reporting;
[LLM-TRIAGE]tagging for positivetoBeAttached(#4b) outside the P0 exit count. - Evals: coverage for #4g, #18, #20–#23, six documented false-positive exclusions, and debugger failure codes F6/F13 in both debuggers.
review.sh"Subagent parity" check (SP1–SP4) plus three drift-smoke cases: the dual-mode subagents (e2e-finding-verifier,e2e-failure-classifier) and their inline fallbacks are held to one verdict vocabulary (CONFIRMED/FALSE-POSITIVE/NEEDS-CONTEXT) and one frozenF1–F15taxonomy, and each delegating skill must hand the subagent an absolute source-of-truth path.playwright-test-generatorevals: four new cases (Step 4 approval gate, Step 5b conventions/seed, Step 3 reachability probe, Step 7 debugger handoff), each with a true-positive assertion and a false-positive guard.- Codex-native subagent ports under
.codex/agents/(e2e-finding-verifier.toml,e2e-failure-classifier.toml) — optional TOML equivalents of the Claudeagents/*.md, discovered by Codex only from~/.codex/agents/or a repo checkout (never viacodex plugin add/ theskillsCLI), so the inline fallback stays load-bearing. A newreview.shSP5 parity check plus drift-smoke Case 21 hold these ports to the same absolute-path contract, verdict vocabulary, and frozenF1–F15taxonomy as the.mdagents and inline fallback. pattern-reference.mdgains a Pattern index navigation table (P0/P1/P2 → pattern IDs) as a reading aid; the SKILL.md Quick Reference table and the per-pattern sections stay authoritative for severity (CI 3b/3c does not validate the index block).
- README reworked: merged-PR proof hook and the code-server war story in the lead, "See it run" section with real scanner output, install section generalized to per-host + one global
skillsCLI path, scanner network-behavior note, contribution pipeline stated as explicit counts. - e2e-reviewer Phase 2 now executes the LLM-only patterns #20–#23; sync-matcher
expect(await …)reads rerouted from #15 to #4c-4e; Tier-1 ESLint findings now count into the scanner's exit gates. - Cypress debugger retry extraction rewritten to real mochawesome output (with a
mochawesome-mergestep for multi-spec runs); debugger instruction blocks brought to structural parity. - Plugin manifest descriptions trimmed of the trailing keyword sentence (24-pattern parity catalog unchanged);
marketing/gitignored. - Public proof copy now uses the exact merged-PR count (14, from the roadmap tables) instead of the
10+floor, and the proven-in-open-source table lists all merged fixes instead of a representative subset; counts are bumped together on every merge. - Korean README adopts English developer terminology (assertion, merge, suite, fixture, and the pattern/F-code names) while examples and fixes stay Korean.
- Richer trigger descriptions for
playwright-test-generator,playwright-debugger, andcypress-debugger— a "reach for it when… / do not use it for…" shape (matchinge2e-reviewer) that names the concrete evidence and disambiguates the adjacent skills, reducing undertriggering and cross-skill misfires. Applied to both theSKILL.mdfrontmatter and theagents/openai.yamlsurface (the implicit-invocation trigger on OpenAI-manifest hosts) so the disambiguation lands on every host. - Cross-host portability polish:
<skill-base>and subagent-delegation paths are now host-agnostic (naming both~/.claude/skills/and~/.agents/skills/instead of a Claude-only example); the three delegating skills carry a host-neutral dispatch clause (Claude plugin subagent, Codex-native.codex/agents/, or inline fallback — identical verdict).playwright-test-generatornow recommends setting up a browser tool (Playwright MCP, with the official getting-started link) and states plainly that the no-MCP ARIA-snapshot fallback only sees the initial static state; the four READMEs replace the vague "agent-browser tools" wording with "browser automation tools (Playwright MCP / webapp-testing)" and add an MCP-recommended note. e2e-reviewerSKILL.md: the// JUSTIFIED:suppression rules are now defined once (in the Phase 1 section) and referenced from the reference-table section instead of being restated, removing a duplicated spec that could drift.playwright-test-generatorStep 6: when theSkilltool cannot invokee2e-reviewerbut the bundle files are present on the host (e.g. a Codex install), the generator now reads<e2e-reviewer skill-base>/SKILL.mdand runs the full Phase 1–2 review inline against the spec and POM instead of silently downgrading to a scanner-only pass; the manual-P0 fallback is reserved for when the e2e-reviewer files are absent entirely.- README.ko readability pass: naturalized machine-translated phrasing (e.g. bare "deterministic" now reads as "항상 같은 방식으로"/"재현 가능한") while keeping the adopted English developer terminology, code identifiers, and pattern/F-code names unchanged.
// JUSTIFIED:comments can no longer suppress #7 (focused tests), matching the documented no-exemption contract.- Scanner Tier-1 npx invocation pins
typescript@^5so a target repo's.npmrc(legacy-peer-deps) cannot poison the shared npx cache. playwright-test-generatorwrong cross-reference (4.1→#4b); exploration-time auth/seed-data stop-and-ask gate defined.- The debugger note in all four READMEs wrongly claimed automatic CI-artifact fetching is unsupported; both debuggers fetch artifacts via
gh run downloadwith a user-confirmed run ID, and the note now matches that behavior. - Scanner coverage: the suffix-less-Cypress-layout include (
$CYI) now coverscypress/e2e/(Cypress 10+), not only the legacycypress/integration/. Cypress rules that rely on it (#3,#7,#5a,#8b,#9b,#10a) previously missed suites named*_spec.js/ plain*.jsundercypress/e2e/— e.g. hard-codedcy.wait(ms)sleeps went unreported.#9b's basename glob is also widened to the standard spec/test set for parity with sibling Cypress checks. Guarded by a new drift-smoke case (Scanner S5). - Dual-mode subagents (
e2e-finding-verifier,e2e-failure-classifier) were told to read their source of truth from a repo-relativeskills/...path, which does not resolve — a subagent runs with the target project as its working directory, not this repo. Delegating skills now pass the resolved absolute path (pattern-reference.mdfor the verifier, the debuggerSKILL.mdfor the classifier), and the agent files document the contract.
- Updated the public install docs to include the Codex plugin marketplace flow alongside the cross-agent
skillsCLI path. - Refreshed the OSS proof points to say 10+ merged upstream PRs and keep the representative examples aligned across README, roadmap, and sibling lint-plugin READMEs.
- FiftyOne voxel51/fiftyone#7851 merged (duplicate-name error asserted via locator definition instead of visible UI state): moved In review -> Merged in the roadmap and bumped the coordinated counts (13 merged / 15 in review or queued) across all four READMEs; the pilot benchmark summary paragraph now says "pilot benchmark" to match the linked doc's indicative-numbers status.
- Rancher Desktop rancher-sandbox/rancher-desktop#10557 merged (WSL integration-name
getByText(...).not.toBeNull()checks replaced with web-first visibility assertions): moved In review -> Merged in the roadmap and bumped the coordinated counts (14 merged / 14 in review or queued) across all four READMEs, and added it to the proven-in-open-source tables anddocs/case-studies.md.
- Companion ESLint plugins — published and dogfooded. Shipped
eslint-plugin-playwright-silent-passandeslint-plugin-cypress-silent-pass(Apache-2.0, on npm): autofixable rules for the mechanical always-pass class (#4f—expect(locator).toBeDefined()/.toBeTruthy()/.not.toBeNull()) that the officialeslint-plugin-playwright/-cypressdo not cover. The scanner's Tier 1 now runs them via npx when available — best-effort, so a missing/offline package never breaks Tier 1 and Tier 2/3 still cover#4f.e2e-reviewerSKILL.md (Phase 1) positions them as the commit/CI-time companion to this agent-time review and recommends installing the matching plugin on#4fhits.
- README: cross-link the two plugins; add a sourced AI-evidence note (LLM-generated tests reproduce human anti-patterns, and the "iterate until the suite is green" agent loop weakens assertions to vacuity); correct PR-status claims (Qwik#8727 closed → current in-review PRs across module-federation, qwik#8777, mui-x, Supabase, Expo, TanStack Router); remove the demo GIF from the README.
docs/roadmap.md: synced the In-review table to current PR states (Qwik#8727 closed → #8777 in review; module-federation approved) and restored newline-merged rows; queued candidates gated to ≥1000 stars.scripts/ci/test-parity.sh: the Case 7 docs-orphan drift check now strips both README →docs/roadmap.mdlinks so the orphan assertion still fires after the README gained a second reference.e2e-reviewer/SKILL.md: shortened the triggerdescription(927 → 457 chars) to a lean WHAT + triggers form per Anthropic/Codex skill-description guidance; the 24-pattern P0/P1/P2 catalog now lives in the three manifests and the SKILL body Quick Reference, not the frontmatter.scripts/ci/review.shCheck 5 now sources phrase parity from.claude-plugin/plugin.json(manifest↔manifest) instead of the SKILL frontmatter;AGENTS.mdandtest-parity.shCase 11 updated to match.
- All three generation/debugging skills hardened after a cross-skill audit, then empirically verified on both Claude and Codex (no pattern ID or failure-category change). A cross-host run confirmed Claude and Codex reach the same diagnosis on the same input, and the cypress parser fix below was confirmed real (the old query returned nothing on a realistic report; the new one returns the screenshot path).
cypress-debugger: Phase-3 screenshot extraction now parses mochawesome's JSON-stringifiedcontext(fromjson) instead of treating it as a nested object. Phase-1 now carries the specfile/fullFileand the retryattempts[]so "passes on retry" is verifiable from the data, and the skill gained acypress.configretries/testIsolationreference, a Cypress "heal by intent" note, amochawesome-mergestep for splitmochawesome*.json, and acy.interceptalias-ordering example. New eval fixtures:mochawesome-screenshot-context.json,mochawesome-retries.json.playwright-debugger: report handling now detects the reporter in use (HTML report directory vsresults.jsonvsblob-report/) instead of assumingresults.json; the Phase-1 projection surfaceserrorLocation(the failing call's file:line, distinct from the spec line); added first-class signals for fixture/beforeEachsetup failures and unmerged-shard artifacts, a "real product bug vs test bug" decision gate before any test fix, andplaywright show-traceas the primary trace path. Output format aligned withcypress-debuggerfor cross-host parity.playwright-test-generator: the live-exploration step now names its tool source and adds a deterministic ARIA-snapshot fallback plus an app-reachability probe (read and await the configuredwebServer) so the pipeline no longer stalls when the dev server is down; the verification run emits--trace on-first-retry --reporter=htmlfor the debugger handoff; selector priority raisesgetByTestIdwhen a project standardizes on it;best-practices.mdandcode-rules.mdwere refreshed (storageState/projects/test-id/ARIA snapshot) with a top-of-file hard-rules checklist.
e2e-reviewer#2 (Missing Then) accept-criteria guards.pattern-reference.mddocuments the contexts that must not be flagged (API-404 negative checks, cleanup/teardown, success-toast-only flows, helper-embedded assertions, non-entity removes). New evals: a delete-verification case (one true positive plus false-positive guards) and an always-true-locator fixture; minor scanner and grep-pattern touch-ups.- AI-reviewer benchmark cross-model re-judge. The contestable unique catches were re-judged by an independent cross-model judge (OpenAI gpt-5.5 via Codex), which agreed on 13/15 (87%); recorded in
docs/ai-reviewer-benchmark.mdand surfaced in the README. - Hero banner (
docs/assets/hero.png). A liquid-glass hero image for the README.
- README: hero banner, flat status badges (with a "merged PRs" and "55+ agents" badge), a "Why I built this" section, an intent-vs-assertion thesis leading "What a linter structurally cannot catch", a top-of-readme before/after impact diff, a "Roadmap" (planned, not shipped) section, and a simpler
--allinstall one-liner. docs/roadmap.md: retitled to "Upstream Contributions: Track Record and Roadmap"; added a star-count column to every table; added new queued candidates (DefGuard, valor-software/ngx-bootstrap, perses) and three backlog entries; moved voxel51/fiftyone to In review; removed verbose drop notes.
- README improved for OSS conventions and SEO/GEO discoverability. Added a status-badge row, a one-line value proposition, and an eight-question FAQ (definitional, answer-shaped entries that AI search engines and LLMs can cite). Updated the "Proven in Open Source" section to reflect validation against 100+ open-source Playwright and Cypress test suites in the local testbed (zero GitHub side effects), and the rule changes those scans drove.
- Added
docs/roadmap.md(contribution roadmap) and removeddocs/case-studies.md. The roadmap folds in the merged-PR lessons and adds the live contribution plan: merged, in review, 21 prepared candidates, and 29 backlog candidates (50 total), plus the cadence (about 10 PRs open at a time, replenished as each merges) and the goal of at least 25 merged PRs as real-world validation. README and the drift smoke test (test-parity.shCase 7 orphan check) repointed from case-studies to roadmap.
e2e-reviewerscanner — two detection-coverage fixes surfaced during an upstream-PR campaign (no pattern ID or severity change). (1) Legacy Cypress layoutcypress/integration/**/*.js(plain.js, no.cy./.spec./.test.suffix) was invisible to the suffix-globbed checks, so a committedit.onlyand other smells in that classic layout were silently missed.run_checknow supports a;-separated multi-glob include, and the Cypress-intended checks (#3, #7, #9b, #5a, #8b, #10a, #14) additionally scan**/cypress/integration/**/*.{js,ts}. (2) Misplaced-await variant of #15:expect(await locator).toBeVisible()placed theawaiton the locator (a no-op, since a Locator is not thenable) instead of onexpect, so the web-first matcher promise still floated and the base #15 check skipped it by design. A second #15 detection now catches the awaited-locator form, bounded to web-first matchers so value-resolving one-shot reads (expect(await x.isVisible()).toBe(true), which is #4c-4e) are not double-flagged. New evals: id 11 (legacycypress/integrationit.onlytrue positive) and id 12 (awaited-locator true positives plus valid-await expectand#4c-4efalse-positive guards).
playwright-test-generator/code-rules.md— two harness patterns distilled from production E2E practice (no pattern ID or reviewer change).## Network Determinismgains a "the mock layer is decided by where the call originates" rule:page.route()only intercepts browser-issued requests, so server-side traffic (Next.js SSR/RSC, route handlers, a BFF,getServerSideProps) silently bypasses it and hits the real backend — mock those at a server-side seam (an E2E-only env var that returns canned payloads) and detect the origin by whether the data appears in the initial SSR HTML. New## Branch State Seedingsection: for multi-step funnels, seed the user to the branch's starting state through a test-only API instead of re-driving the shared prefix (consent → phone-auth → …) through the UI in every spec — faster, and one prefix change no longer breaks every downstream test; drive the prefix via UI only in the single spec that verifies it, and record seeding endpoints in the conventions doc (Step 5b).
- Documentation and eval-precision polish (no detection-behavior change). README F14 "Animation Race" signal cells (both Playwright and Cypress rows) updated to the bidirectional wording shipped in 1.5.3.
sg-4f-locator-as-truthy.ymldropsgetByNamefrom its method whitelist — it is neither a Playwright Locator method nor an RTL query (it was the documented n8n ~2200-FP culprit). Eval id 2's false-positive-guard line citations corrected (unit-helpers.test.ts 10/15 to 11/16). cypress-debugger eval id 2 severity assertion reworded to match the SKILL.md P0 rubric (P0 is reserved for silent-pass F6/F13, not a loud uncaught TypeError).scan.shdedup comment clarified: the Tier-2/Tier-3 skip is eslint-conditional and LINT_COVERS-scoped, and the ast-grep rules are TypeScript-only by design with.js/.jsx/.tsxdelegated to the always-on Tier-3 regex net.
- playwright-debugger Phase 1 extraction was broken. The documented
jqselected at result-level, which dropped the flaky-vs-unexpected signal the classification step depends on and returnedtitle/fileas null on a standard Playwright JSON report. Rewritten to select at the spec level (preservingtitle/file/line, the per-testoutcome, thefinalresult, andretries); validated against all five debugger fixtures. Also addedmkdir -p playwright-reportbefore the reporter redirect (prerequisites case 2), and broadened the F14 ("Animation Race") signal to cover both the not-yet-rendered and removed-before-observed directions, reconciled with the F14-vs-F15 distinguisher and mirrored into cypress-debugger for cross-skill parity. - cypress-debugger JUnit extraction captured the wrong test name. The regex matched
name="..."insideclassname="..."; it now requires an attribute boundary (\sname=/\smessage=). Eval id 3 relabeled (it leaked the playwright debugger's F11 name "Async Order"; cypress F11 is "Command Queue / Intercept Race", and the postMessage/DOM race is F1 with F14 as the secondary). scripts/pr-preflight.sh— committed the previously-uncommitted stage-7 authoring-hygiene block (AI-tell punctuation guard, added-comment cap, test-rename guard) and added aPREFLIGHT_ALLOW_SLOP=1override so the punctuation check does not false-positive on intended string-literal content.
- Scanner false positives in three Tier-3 rules (no pattern ID or severity change).
#15(missing-await on expect) no longer flags non-Locator expects:getBy[A-Za-z]+is tightened togetBy[A-Z](sogetByteLength/getBytesare excluded) and the barepage)alternative is anchored with a negative lookbehind, so a dotted.pagemember such asexpect(response.body.page).toBe(2)orexpect(config.page).toEqual(x)is no longer a P0.#5a(conditional bypass) now requires the.isVisible(call form, so a bare boolean variableif (isVisible)is not flagged.#8a(dangling locator) gains the trailing-line-comment tolerance#8balready had, sopage.locator('.x'); // noteis detected. New evalid 10(evals/files/fp-guards.spec.ts) locks in a true-positive and a false-positive guard for each;references/grep-patterns.mdsynced.
skills/e2e-reviewer/scripts/scan.sh— widened Tier-3 detection for two existing P0 patterns (IDs and severity unchanged).#8b(discarded boolean) now matches the selector-argument shorthand (await page.isVisible('sel');) and the no-semicolon form, with an end-of-statement anchor so handled reads likeawait x.isVisible().catch(() => false)and chained expressions are not flagged.#4c-4e(one-shot read) addsallTextContentsto its method list, soexpect(await locator.allTextContents()).toContain(v)is caught in the deterministic Tier-3 baseline. (countwas intentionally NOT added to Tier-3: a bare regex.count()over-flags ORM/array idioms such asexpect(await prisma.user.count()).toBe(n); one-shot locatorcountdetection stays with the Tier-2 ast-grepsg-4ce-countrule.)references/grep-patterns.mdsynced to match. New evalid 9(evals/files/widened-reads.spec.ts) adds true-positive assertions plus two false-positive guards (.catch()chain, assigned-and-used read). Out of scope by design: two-statement read-then-assert (const t = await x.textContent(); expect(t)...) andnew URL(page.url()).searchParams.get()reads remain Phase 2 manual findings — unsafe for line-based Tier-3 regex.
Progressive-disclosure restructure of the flagship skill, a new failure category, and the PR-preflight harness that gates the upstream-contribution campaign.
- F15 Hydration Race — new failure category in both debuggers (
playwright-debugger,cypress-debugger): action reported success but had no effect because the first interaction aftergoto/cy.visit()landed on a server-rendered page before the framework attached event listeners. Distinguished explicitly from F14 (F14: element not rendered yet; F15: rendered but inert). Classification step, fix guidance (hydration marker gate → self-verifying click; never a blind sleep), README tables, and one new eval per debugger with committed fixtures (results-hydration-race.json,mochawesome-hydration-race.json) including a false-positive guard case each. playwright-test-generator/code-rules.md— SSR & Hydration section. Gate the first interaction on hydration for SSR apps (marker first, self-verifying.toPass()action second, neverwaitForTimeoutaftergoto); Qwik resumability and Astro per-island nuances noted.scripts/pr-preflight.sh— six-stage preflight for upstream E2E-fix PRs prepared intestbed/clones: smell delta (baseline-vs-working-tree scan), sed-artifact AST check, nearest-tsconfig targetedtsc, the repo's own lint on changed files, best-effort headless run of changed specs (env failures classified as SKIP, not FAIL), and diff hygiene (stray tracked-file changes, whitespace-only churn). Every SKIP is reported so the PR body can disclose what was not verified locally — adding the previously missing "run the affected specs before submitting upstream" verification step.skills/e2e-reviewer/references/pattern-reference.mdandreferences/applying-fixes.md— the 522-line Pattern Reference and the 266-line Phase 4 contract moved out of the SKILL.md body verbatim, read on demand.
e2e-reviewer/SKILL.mdbody cut from 1,066 to 291 lines (~22k → ~7k tokens per invocation). The body now holds the executable workflow (Phases 0–3, output format, Quick Reference, suppression); per-pattern contracts and the Phase 4 fix tables live inreferences/with explicit read-on-demand pointers (the three regression-prone Phase 4 rules stay inline). CI parity Checks 3b/3c and drift-smoke Case 4 now validatereferences/pattern-reference.md.scripts/verify-fixes.sh— accepts an explicit changed-file list (verify-fixes.sh <repo> -- <file>...) so the postfix AST rules no longer fail on pre-existing upstream artifacts outside the diff;VERIFY_FIXES_SKIP_TSC=1lets callers that own typechecking (pr-preflight) skip the root-leveltsc.e2e-reviewer/references/grep-patterns.md— retitled to "Pattern ID Reference" and stripped of the retired 6-batch Grep dispatch imperatives that contradicted the scanner-based Phase 1 since 1.3.0; ID → regex → meaning tables unchanged.
- README documented the wrong Codex install location (
~/.codex/skills/→~/.agents/skills/), completing the v1.4.2 correction that had only been applied to AGENTS.md. - AGENTS.md drift: nonexistent
scripts/ci/validate-evals.shpath, "20 pattern phrases" (actual: 24), "10 drift smoke checks" (actual: 14), failure-code range F1–F14 → F1–F15.
Follow-up patch to v1.4.4 from the same production suite (71 tests): the inverse of "prove the call".
playwright-test-generator/code-rules.md— Prove the call HAPPENS before asserting it. Companion rule to "prove the call, not just the pixels": call-proof assertions only apply to calls the app actually makes at runtime. Unmount-cleanup API calls are the canonical trap — an empty-deps effect's cleanup captures guard variables as a stale closure from mount time, so a guard that's empty at mount (e.g. an id that arrives with the fetch response) makes the cleanup call a dead path forever, and awaitForRequestassertion times out against correct test code. Verify the request fires at least once before shipping the assertion; otherwise assert the user-visible outcome, file the stale closure as an app defect, and leave a file:line comment for when it's fixed.
- Version metadata bumped to 1.4.5 across plugin manifests and all four skill frontmatters.
Production-feedback patch from extending the same Pages-Router functional suite to 68 tests (quiz solve loops, logout, password change, modal-gated entry flows): two failure modes that survived a clean v1.4.3 review and only surfaced at runtime, fed back into the debugger playbook and generator rules.
playwright-debugger/SKILL.md— Visible-but-unmatchable elements (aria-hiddenancestor). New F2-family diagnosis: a role+name locator stuck at "waiting for" while the screenshot plainly shows the element means an ancestoraria-hidden="true"removed the subtree from the accessibility tree —getByRolecan never match,getByTextstill can. Covers the nastier same-name variant where the role query silently resolves to a control outside the hidden subtree and the click is then blocked by the modal overlay. Fix pattern: text locator scoped to a stable container inside the hidden subtree (e.g.locator('#modalBox').getByText(...)) + report the root upstream as an app accessibility defect.playwright-test-generator/code-rules.md— Wire evidence beforewhen.paramsnarrowing. Third hard rule for request-aware mocks: prove the app actually sends a param at that point in time before keying a rule on it. Arouter.queryread in a first-render initializer fires the initial fetch during hydration (beforerouter.isReady), silently dropping the param from the wire — a param-narrowed rule then never matches and a previously-green test fails for a contract the app never honors. If the param is best-effort in practice, keep the broad rule and record the WHY with a file:line citation.
- Version metadata bumped to 1.4.4 across plugin manifests and all four skill frontmatters.
Production-feedback update: patterns proven during a 38-test Wave 1–4 functional suite build on a Next.js Pages-Router app (proxy-cmd API, member-area mocking, write interactions), fed back into the generator rules, reviewer catalog, and debugger playbook.
code-rules.md— Request-aware mock rules. OrderedMockRule[]per endpoint (when: { method, params }, first match wins; params compare only listed keys — URL query for GET/DELETE, urlencoded body for POST with body precedence). Two hard rules: a registered-but-unmatched rule array must answer empty-success + loud warning, never fall through to the network (param typos must not become real-backend writes); pagination contracts via per-pagestart/offsetrules — seed page 1 at exactly the page size so the client's "loaded end" flag doesn't suppress the page-2 request, then assert append-not-replace.code-rules.md— "Prove the call, not just the pixels." Write interactions with optimistic UI must pair the UI assertion with request proof (waitForRequestarmed before the click).code-rules.mdAuth & Session — login-success hybrid. Route mocks can't mint server-issued session cookies; mock the login POST for form behavior, seed cookies through the project's sanctioned test seam, then assert the full post-login redirect chain — with a WHY comment in the spec.e2e-reviewer#22 — Optimistic UI Without Call Proof (P1, LLM). Interaction test asserts only optimistically-updated UI state; passes even with the API wiring deleted. RequirewaitForRequest/route-hit proof for every write interaction.e2e-reviewer#23 — Fixture Ignores Conditional Render Guards (P2, LLM). Type-correct fixtures aren't render-correct: components self-hide on field+view-state combos (e.g.liked: falseitems in a Liked view returnnull), producing empty-UI "flake" or wrong-reason negative passes. Read the item component's early returns before seeding.playwright-debugger— accessible-name collision guidance. On role+name strict-mode violations between semantically different controls (tab vs toggle sharing a name), disambiguate by the semantic attribute ([aria-pressed]/:not([aria-pressed])) instead of downgrading to.nth().
- Catalog count 22 → 24 across SKILL.md Quick Reference, README, and plugin description.
- Cross-host convergence contract (motivated by a 10-repo Claude-vs-Codex comparison where verdicts agreed 9/10 but
Real P0counts varied by host discretion): Phase 1 "Deterministic mode" — canonical scanner invocation with download tiers pinned off and a mandatory tier-coverage statement in the report; Phase 2 "Counting contract" —Real P0 = Nis the number of distinct survivingfile:lineentries after FP elimination and consolidation; Phase 2 "Bounded opening-token sweep" — the scanner-missed-shape hunt is a fixed 5-family token checklist instead of open-ended exploration.
scan.sh#3bmatches everyuncaught:exceptionhandler opening (single- or multi-line body). The old.*falsesuffix only caught one-line() => falseand missed 51 multi-line blanket suppressors in one OSS Cypress suite — Phase 1 reported 0 P0 on the corpus's richest #3b surface. Blanket-vs-scoped classification stays with Phase 2 (handler containingexpect()is exempt). Cross-host verification (Codex/omx, 10 repos) surfaced the gap: a host that trusted the mechanical zero stopped early and returned the only verdict mismatch of the run.e2e-reviewer/SKILL.mdPhase 2 zero-P0 floor. Explicit MANDATORY rule: Phase 1 reporting 0 P0 does not end the review — LLM-only checks and a scanner-missed-shape sweep always run.AGENTS.mdCodex install path corrected to~/.agents/skills/(empirically verified discovery path; was documented as~/.codex/skills/).
Research-driven update: folds 2025–2026 community/official findings on AI-generated E2E tests (Playwright Agents planner/generator/healer model, seed-spec + conventions-doc leverage, network determinism, storageState auth) into the generator pipeline and reviewer catalog.
playwright-test-generatorStep 5b — Conventions & Seed Artifacts. On first run in a project (no testing-conventions doc detected in Step 1), the pipeline now scaffolds a project-adapted E2E section into rootAGENTS.md(+CLAUDE.mdpointer) and designates the best generated spec as the seed to copy. Rationale: across practitioner reports, a conventions doc + seed spec is the highest-leverage artifact for keeping AI-generated tests consistent across sessions and agents — without one, every session re-derives locator/auth/mocking decisions and drifts. Newconventions-template.mdcarries the fill-from-observed-reality template.playwright-test-generator—playwright-agents.mdinterop reference. When to keep this pipeline vs hand off to Playwright ≥ 1.56 first-party agents (npx playwright init-agents --loop=claude), how Step 5b artifacts feed the planner/generator, and why projects pinned below 1.56 (e.g. pixel-perfect visual baselines that an upgrade would invalidate) should not upgrade just for agents.code-rules.md— Network Determinism section. Per-endpoint strategy table: write/credential paths must be stubbed (page.route()) — generated tests must never create real accounts or hit real payment providers; stable first-party reads may stay real; at most one designated real-backend smoke spec. Includes the shared proxy route-mock helper pattern (match on decoded routing param, e.g./api/request?cmd=) and the fall-through write-leak caveat.code-rules.md— Auth & Session section. Authenticate once programmatically +storageStatereuse; UI login only in login-flow specs; manually captured session files forbidden as hard dependencies.e2e-reviewer#20 — Unmocked Real-Backend Writes (P1, LLM). Spec drives signup/login/checkout/mutation with no route stub in spec or fixtures — data pollution, rate-limit flakiness, PII exposure, backend-state-dependent results. Exemption: one// JUSTIFIED:designated real-backend smoke spec.e2e-reviewer#21 — Manually-Captured Session-File Dependency (P2, LLM).storageStateJSON produced only by a manual capture script — absent on fresh clones/CI, silently expires. Session state must be reproducible from code.
playwright-test-generatorselector priority — addedgetByPlaceholderas tier 3 and a label-existence precondition ongetByLabel: label-less inputs (placeholder/title only) are common in real apps andgetByLabelon them matches nothing. Step 3 now includes an accessible-name reality check against the live snapshot before locators enter the mapping table.playwright-test-generatorStep 3 — programmatic-auth guidance for generated tests (API login / setup project +storageState), aligned with reviewer #21.playwright-test-generatorStep 7 /playwright-debuggerF2·F12 — heal by intent. Selector-failure fixes now re-resolve the element a step semantically targets from a fresh snapshot at the highest stable locator tier, instead of patching the old selector string (the approach Playwright's healer agent uses).- Catalog count 20 → 22 across SKILL.md, README, and plugin description.
scan.sh— E2E content scoping for FP-prone patterns. Tier 3 hits for#3,#4a,#4b,#4f,#4g,#15(P0) and#9,#6,#5b,#19(P1) are now kept only when the containing file carries a real Playwright/Cypress marker (@playwright/testimport,async ({ pagefixture destructure, directpage.<api>usage, orcy.<cmd>(). Eliminates Vitest/Jest/RTL unit-test bleed-through — the dominant false-positive root cause observed across a 110-repo OSS validation corpus (52/77 review reports flagged it). Measured: one design-system repo's#4fdropped 107 → 0, a component library's#4gtoken collisions (<Tooltip timeout={0}>props) 3 → 0, with zero loss of confirmed real P0 signals on control repos.scan.sh— ESLint watchdog. The Tier 1npx eslintinvocation now runs under a kill-after-timeout guard (E2E_SMELL_ESLINT_TIMEOUT_SECS, default 300s; pure bash — macOS has notimeout(1)). The npx auto-download could previously hang indefinitely on large repos or constrained networks, stalling the whole scan.- ast-grep rules — vendored/build-artifact
ignores. All five detection rules now skipnode_modules,dist,build,.next,out,coverage,public, minified bundles, andevals/files— mirroring Tier 3's rg glob excludes (previously Tier 2 had no such scoping and could report hits in vendoreddist/bundles). e2e-reviewer/SKILL.md4.2 — two new PR-culture points. (9) Framework self-test gray zone: hits in framework/component-library test fixtures are mechanically real but carry different PR-worthiness — lead with the smallest high-signal subset. (10) PR-worthiness triage: a finding justifies an upstream PR iff at least one real P0 is a silent-always-pass/race defect in a user-facing E2E spec; fixture-only, unit-scope, or cosmetic findings do not — the empirical KEEP/DELETE bar from the 110-repo validation.e2e-reviewer/SKILL.mdPhase 2 — three codified FP rules.#4bstatic-shell vs dynamically-injected decision rule;#5aaction-gate vs assertion-gate rule (if-body withoutexpect()gates setup, not verification);#8anote that the scanner now pre-filters continuation lines.- Committed eval fixtures — all four skills.
skills/*/evals/files/**(28 files: 12 e2e-reviewer specs/POMs, 4 Playwright-reporter JSON and 4 mochawesome JSON debugger reports, 8 generator project-scaffold files) now exist in-repo so everyevals.jsonassertion's exactfile:linereference is reproducible from a fresh clone. The previous.gitignorerule treating fixtures as local-only mock data is reversed; the scanner excludes**/evals/files/**in all three tiers so the intentional anti-patterns cannot fail the repo's own smell-scan CI gate.
scan.sh— Tier 1 ESLint never worked via npx and its failure silently disabled Tier 2/3 coverage. Two stacked bugs: (1) the invocation used--no-eslintrc/--ext/inline-JSON-c, all removed in ESLint v9+, so the npx path always exited 2; (2) the success check only grepped stdout forerror|warningand set the*_lint_donecoverage flags unconditionally — a crashed Tier 1 therefore reported "no findings" AND made Tier 2/3 skip#7 #9 #15 #16(#7 #9bCypress). Rewritten: generated flateslint.config.mjswith absolute plugin paths resolved inside the npx env (npm ≥9 sets no NODE_PATH),@typescript-eslint/parserfor TS specs, and an exit-code gate — coverage flags are only set when eslint exits 0/1; any rc ≥ 2 falls through loudly to Tier 2/3. Verified end-to-end on Playwright (no-focused-test,missing-playwright-await,no-wait-for-timeout) and Cypress (mocha/no-exclusive-tests,cypress/no-unnecessary-waiting).scan.sh—// JUSTIFIED:suppression implemented in Tier 3 (was documented in SKILL.md/README but never implemented). A hit is dropped when the marker is on the hit line or anywhere in the contiguous//-comment block immediately above it (≤5 lines). Block-level placements remain Phase 2 responsibility.scan.sh— Cypress framework detection over-match. The barecy\.alternation matchedagency./privacy.etc., triggering spurious eslint-plugin-cypress downloads on non-Cypress repos; now anchored to real Cypress commands (cy.visit|get|contains|…().scripts/validate-evals.sh— fixture-existence gate. Every path in an eval'sfiles[]must now resolve on disk, CI-enforcing the committed-fixtures contract.- Re-adjudication round (previously-rejected findings re-verified): e2e marker set extended with Playwright-only idioms (
test.describe(family, fixtures-module imports, column-0page.) after a probe proved POM-only specs were being dropped;#9ctightened to the real API shapes (waitForLoadState('networkidle')/waitUntil:) and e2e-scoped;#4c-4eregex now matches state reads with arguments (getAttribute('src')was unmatchable with the empty-parens form); watchdog now kills the npx/node descendant tree, not just the subshell;#9b/#9csub-variants documented in the SKILL.md/README pattern rows; Phase 2 skip-list includes#19;#3btrue-positive + scoped-handler guard added to the Cypress eval fixture. scan.sh— rg glob precedence bug. ripgrep gives precedence to later globs; the basename include glob was declared last, silently re-including files inside the!dist/**-style excluded directories whenever the target repo did not gitignore its build output (the long-standing "vendoreddist/cli.jshits" symptom). The include glob now comes first so every negation wins.
scan.sh#8a— previous-line continuation filter. A hit is dropped at scan time when the preceding non-blank line ends with(or,— the matched line is an argument inside a multi-lineawait expect(\n page.locator(...)\n).toBeVisible();, not a dangling statement. Kills the single biggest FP source (measured: 520 → 3, 323 → 0, 71 → 0 on three monorepos) while still catching semicolonless dangling locators. The argument matcher also accepts one level of nested parentheses (getByRole('button', { name: 'Save (draft)' })). The Phase 2 previous-line backstop remains for residual shapes.scan.sh#15regex excludesexpect(await …)— those are one-shot reads (#4c-4eterritory), not missing-await-on-expect; previously both patterns could claim the same line. Uses a possessive\s*+before the negative lookahead so whitespace variants (expect( await …)) cannot backtrack around it.scan.sh— eval-fixture exclusion is conditional.**/evals/files/**is excluded from normal scans but re-included automatically when the scan root itself is inside anevals/filestree, so the bundled fixtures can be self-tested directly.evals.json— +4 assertions covering the two scanner behavior changes (one true-positive and one false-positive guard each for the#8atrailing-;rule and for E2E content scoping).- All four skill
SKILL.mdfiles and the three manifests bumped to1.4.0in lock-step;scripts/ci/test-parity.shversion constants updated.
- No security-relevant changes; scanner still performs no writes to the target repo.
e2e-reviewer/SKILL.md— new anti-pattern#19Module-Level Mutable State In Test Utilities (P1, grep + LLM). Catches top-levellet X = …declarations in test utilities, helpers, and POMs — state that survives across tests within a long-lived worker, while independent worker copies can generate the same supposedly unique counter-based identifiers. Playwright replaces the worker before retrying a failed test, so retry survival is not part of this rule. Surfaced by the Zeppelinfix/e2e-flaky-finalreview wherelet testNotebookNameSequence = 0;collided underworkers > 1. Phase 1 grep (^let\s+, glob*.{ts,js,tsx,jsx,cy.ts,cy.js}) flags every column-0let; Phase 2 LLM filter SKIPS pure type declarations (let page: Page;reassigned inbeforeEach— idiomatic Playwright fixture) and only confirms hits that carry an initializer. Suppress with// JUSTIFIED: [reason]for intentionally shared worker-scoped state. Fix pattern documented inline: derive uniqueness fromDate.now()+Math.random().toString(36).slice(2, 8), usetestInfo.workerIndex, or move state intotest.beforeEach.
e2e-reviewer/SKILL.md— extended#11YAGNI scope from Page Objects to Page Objects + Utility Modules. The procedure now lists exported symbols ofutils.ts/helpers.ts/fixtures.tsalongside POM members and applies the 2+ call-site threshold uniformly. The rationalization for the extension is the same one as the original POM scope — single-use indirection adds maintenance cost without reuse benefit. Common new patterns explicitly enumerated: single-use auth helpers, single-use REST helpers, single-use waits. TheRulerow clarifies that utility-module helpers used only inside their own module should drop theexportkeyword.- Pattern count
19 → 20across all parity surfaces — frontmatter description ("Reviews 20 anti-patterns"), Quick Reference table (now 20 rows), Pattern Reference text,docs/e2e-test-smells.mdP1 table,README.mdSkill 2 description + Standalone Scanner blurb + P1 quick-reference table,AGENTS.md(pattern IDs#1–#19plus#3b;20 Patterns tablein the editor guidance),scripts/ci/review.shCheck 3c QR row-count guard,scripts/ci/review.shCheck 5 frontmatter phrase-count guard,scripts/ci/test-parity.shCase 3c expected substring,skills/e2e-reviewer/scripts/scan.shTier 3 banner. Themodule-level mutable state in test utilitiesphrase is appended to the P1 section of every manifest description (.claude-plugin/plugin.json,.claude-plugin/marketplace.json,.codex-plugin/plugin.json) so the severity-grouped parity check stays green. evals.json— added eval id8covering#19true-positive flagging plus four false-positive guards: pure type declarations, JUSTIFIED-marked worker-scoped state, indented locals inside function bodies, and the canonical fix suggestion. Eval count7 → 8.- All four skill
SKILL.mdfiles and the three manifests (.claude-plugin/plugin.json,.claude-plugin/marketplace.json,.codex-plugin/plugin.json) bumped to1.3.4in lock-step per the cross-host parity contract.scripts/ci/test-parity.shCases 8 and 12 updated to mutate the new version string.
#19Debug Console Output In Test Code was prototyped as a third addition and rejected before commit.console.log/console.info/console.warn/console.debugin spec, POM, or utility files do pollute the framework reporter output, but the harm is purely cosmetic — they cannot turn a real failure into a silent pass, which is the rule-set's defining concern. ESLint'sno-consolerule covers the same ground in a single line of config, and adding the pattern would have widened the scope ofe2e-reviewerinto general TypeScript cleanliness territory and diluted its identity. The Zeppelin PR cleanup that motivated the proposal was PR hygiene, not a test-correctness fix. Decision recorded here so the pattern is not re-proposed without new evidence of silent-pass harm specific to Playwright/Cypress.
bash scripts/ci/ci-local.sh— Review parity 10/10, Drift smoke 12/12, Security clean, E2E smell scan 0 hits against the repo itself.- Bundled scanner verified against a synthetic fixture: 1 true-positive flagged (
let testNotebookSequence = 0;in a utility module), 3 expected false-positive candidates surfaced for Phase 2 LLM to filter (two pure type declarations + one JUSTIFIED-marked worker-scoped state).
playwright-debugger/SKILL.md— tightened external-command guidance to reduce shell-injection and unintended-download risk surface flagged by SAST scanners:gh run downloadnow uses an explicit$RUN_IDvariable plus-D playwright-reportdestination, with a documented prohibition on downloading artifacts from forked-PR runs or arbitrary URLs.findis restricted to regular files (-type f) underplaywright-report/;unziparguments are always quoted; trace-derived strings must never be used unquoted as filenames or shell arguments.npx playwright testinvocations switched tonpx --no-install playwright testso the agent uses the project-pinned Playwright instead of auto-installing.
playwright-test-generator/SKILL.md— narrowed browser navigation and disablednpxauto-install:browser_navigatecalls must stay under the detected/approvedbaseURL; off-origin links discovered in page content, error messages, or test data must not be followed.npx playwright codegen,npx playwright test, andnpx tsc --noEmitall switched tonpx --no-install …so missing packages are surfaced to the user rather than silently fetched from npm.
e2e-reviewer/SKILL.md— removed external-action suggestion in the cycle-decision section ("file an issue against the upstream repo" → "document them in the review report"); avoids encouraging out-of-band state changes during a review pass.
- All four skill
SKILL.mdfiles and the three manifests (.claude-plugin/plugin.json,.claude-plugin/marketplace.json,.codex-plugin/plugin.json) bumped to1.3.3in lock-step per the cross-host parity contract.
- Discovery metadata refresh — README, Claude plugin, Codex plugin, and Claude marketplace surfaces now describe the bundle as an AI agent testing toolkit for E2E test automation, Playwright test generation, Cypress test review, flaky test root-cause analysis, false-positive detection, and test smell scanning. Added matching discovery keywords to the Codex and Claude marketplace manifests.
e2e-reviewerfrontmatter description length — shortened theSKILL.mddescription to stay under the 1024-character skill-loader limit while preserving the P0/P1/P2 pattern phrase order required by CI.- All four skill
SKILL.mdfiles and the three manifests (.claude-plugin/plugin.json,.claude-plugin/marketplace.json,.codex-plugin/plugin.json) bumped to1.3.2in lock-step per the cross-host parity contract.
- Installed skill loader warning —
e2e-reviewerno longer exceeds the loader description limit after reinstalling from this release.
- Codex plugin
interfaceschema validation —scripts/ci/review.shandscripts/ci/pre-push-security.shnow enforce the Codex-specific block in.codex-plugin/plugin.json:displayName,shortDescription,longDescription,developerName,category,websiteURL,brandColormust be non-empty strings;capabilitiesmust be a non-empty string array;defaultPromptmust contain 1–3 prompts of 1–128 characters each (matches Codex's display limits).skillsmust be a relative path starting with./and must expose exactly the fourskills/<name>directories. Catches drift where one host's manifest is bumped without the other. The validator lives inscripts/ci/lib/validate_codex.pyand is imported by both shells (single source of truth — no copy-paste drift between the two CI gates). - SKILL.md ↔ manifest version parity check —
scripts/ci/review.shnow reads each skill'smetadata.versionfrom its YAML frontmatter and fails if it does not match.claude-plugin/plugin.jsonversion. Closes the gap that letskills/playwright-test-generator/SKILL.mdstay on1.3.0while the other three SKILL.md files and all three manifests were bumped to1.3.1. Without this check, lock-step bumps could silently miss individual SKILL.md files. - Drift smoke Case 10 (defaultPrompt) and Case 12 (SKILL.md version) —
scripts/ci/test-parity.shnow mutates the CodexdefaultPromptarray to 4 entries (confirms the≤3guard fires) and bumps a SKILL.md frontmatter version out of sync with the manifest (confirms the new parity check above fires). E2E_SMELL_NO_AST_GREP_DOWNLOAD=1— opt-out env var that disables thenpx --yes @ast-grep/clifallback inskills/e2e-reviewer/scripts/scan.shTier 2 (matches the existingE2E_SMELL_NO_ESLINT_DOWNLOAD=1escape hatch). Documented inREADME.mdStandalone Scanner section.- Prompt-injection safety section in all three browsing/reporting skills —
playwright-debugger/SKILL.md,cypress-debugger/SKILL.md, andplaywright-test-generator/SKILL.mdadd an explicit "untrusted data" section. Test titles, error messages, DOM snapshots, AT-SPI trees, console output, network responses, screenshots, videos, and target-project source code may contain attacker-controlled text and must not be executed or followed as instructions. The generator's block covers Step 3 (browser exploration via agent-browser) and Step 6 (e2e-reviewer + YAGNI audit reading target source code) in addition to the report-reading surface the two debuggers already had.
cypress-debuggerF11 README drift —README.mdSkill 4 table still listed F11 as "Async Order Assumption" with aPromise.all-flavored signal, contradicting v1.3.0's redefinition to "Command Queue / Intercept Race" (cy.interceptregistered after the request fires,.then()chain order swap, parallelcy.request()race against an unfinishedcy.visit()). README updated to matchcypress-debugger/SKILL.md:74.e2e-reviewer/SKILL.mdSuppression section consistency — the standalone## Suppressionblock at the end of the file said "Each individual flagged line needs its own// JUSTIFIED:— a comment higher up in the block does not count", contradicting Phase 1 (which explicitly allows JUSTIFIED above the enclosing call/block or above a multi-line chain's starting expression). Rewrote the standalone block to enumerate the same three positions Phase 1 uses, with the wording "enclosing call/block" aligned word-for-word with Phase 1.skills/playwright-test-generator/SKILL.mdleft behind on v1.3.0 — the lock-step bump claim in v1.3.1's### Changedsection was previously false for this file. Now1.3.1like the other three SKILL.md files and the three manifests. Caught by the new parity check above; absent the check, this would have shipped as a silent CHANGELOG lie.scripts/ci/pre-push-security.shhardcoded-path scope blind spot — initial v1.3.1 narrowing toscripts/+skills/removed.claude-plugin/,.codex-plugin/, anddocs/from the scan. A leaked/Users/...path in any plugin manifest would have shipped directly to every plugin user. Scan roots now include the two plugin manifest directories. (Manifests are small enough that there is no FP risk; docs remain excluded because~/andexampleplaceholders are intentional there.)scripts/ci/test-parity.shcleanup-trap brittleness underset -euo pipefail— the cleanup loop ranmv "$b" "$f"without|| true, so a single failingmvwould abort the loop and leave the remaining.parity-backupfiles on disk. Restoration is now best-effort.- Codex
interfacevalidator duplication — the ~30-line schema-check block previously existed verbatim in bothscripts/ci/review.shandscripts/ci/pre-push-security.sh. Without a CI guard, the two copies could drift when the Codex display spec changed (prompt limit, required keys). Extracted toscripts/ci/lib/validate_codex.py; both shells now import it. See### Added. scripts/ci/test-parity.shrestore()unbound-variable risk — underset -euo pipefail, the rebuild loop iterated"${BACKUPS[@]}"without the:-default. Aligned with the cleanup trap at line 22 ("${BACKUPS[@]:-}").scripts/validate-evals.shrewritten in Python — was Ruby while every other CI script usespython3. The previous Ruby version only worked becauseubuntu-latestGitHub Actions runners happen to preinstall Ruby; the new version uses the samepython3already required bypre-push-security.shandreview.sh.skills/e2e-reviewer/scripts/scan.shCypress eslint args — thetry_eslintcypress branch built theeslint-plugin-mochaflag via an inline$(... && printf '-p\neslint-plugin-mocha\n')that depended on bash word-splitting\n-delimited subshell output into array fields. Replaced with an explicitif [[ "$plugin" == "cypress" ]]; then npx_args+=(-p eslint-plugin-mocha); fiso the array build is unambiguous..github/workflows/e2e-smell-scan.ymlast-grep version pin —npm i -g @ast-grep/cliwas unversioned and would silently break Tier 2 on a major release. Pinned to^0.39(the 0.x line ast-grep is currently on).
- All four skill
SKILL.mdfiles and the three manifests (.claude-plugin/plugin.json,.claude-plugin/marketplace.json,.codex-plugin/plugin.json) bumped to1.3.1in lock-step per the cross-host parity contract.
- Bundled scanner inside the skill — moved
e2e-smell-scan.shand theast-grep-rules/directory fromscripts/toskills/e2e-reviewer/scripts/. Now installed vianpx skills addautomatically — no separate clone needed for users to invoke the scanner from the agent runtime. - 3-tier scanner integration —
skills/e2e-reviewer/scripts/scan.shnow runs in priority order: (1)eslint-plugin-playwright/eslint-plugin-cypresswhen locally installed in the target project (AST-based, lowest FP rate), (2)ast-grepTree-sitter rules for FP-prone patterns (#15,#4ce-state-bool/text/count,#4f), (3) bundledripgrepregex as universal fallback covering all 19 patterns including gaps the eslint plugins miss (#3bCypressuncaught:exceptionblanket,#4g{timeout:0}.should("not.exist")). Output groups results per tier with framework auto-detection. - Phase 4 (Applying Fixes) in
e2e-reviewer/SKILL.md—4.1Canonical Replacements table (Playwright + Cypress + RTL/Vitest sub-tables with jest-dom prereq check),4.2Band-Aid Awareness with HIGH/MEDIUM/LOW likelihood per pattern + mandatory pre-removal procedure forforce:trueafter readiness check,4.2PR-culture cross-check section (8 numbered points: when to invoke, CI execution check, canonical form discipline, one mental migration per PR, attribution verification, etc.),4.3Cascade cleanups,4.4Empirical cycle-count rule (default 2, condition-based STOP),4.5Avoid scope creep with budget interpretation rule. scripts/verify-fixes.sh— post-bulk-fix verification (TypeScript static check + ast-grep detection of sed artifacts: double await, emptyexpect(), orphan.then()after web-first migration). Catches the failure modes regex-class fixes can introduce.- Phase 0 framework-detection guards — explicit warnings against false signals:
nx.jsone2eTestRunnerfield is a generator default (real case observed where Cypress was deleted in a merged PR but the field remained),package-lock.jsoncached transitive deps,.spec.tsfilename alone (could be Jest unit). Inspect imports first. - Phase 2 retry-wrapper skip rule — originally documented
#15/#16as exempt insidetoPass()/expect.poll()callbacks. Corrected in 1.10.0: only one-shot#4c-4e/#4hreads are exempt; floating assertion/action Promises remain P0 because the wrapper cannot observe a Promise its callback neither awaits nor returns. #3bCypressuncaught:exceptionPhase 2 distinction — handlers that contain anexpect(err.message.includes(...))call are scoped negative-regression tests (asserting on error properties), not blanket suppression. NOT P0.- Phase 3 Coverage Gap categories expanded — added Race/concurrent (optimistic-update rollback, double-click submit, in-flight request when navigating away) and Responsive/device (mobile viewport, touch vs hover, locale formatting/RTL) on top of error paths / edge cases / accessibility / auth boundaries. Triage rule: gaps interacting with a P0 finding are highest value.
cypress-debuggerF11 redefinition — was "Async Order Assumption" (Playwright-stylePromise.allmodel), redefined as "Command Queue / Intercept Race" matching Cypress's actual chain semantics:cy.interceptregistered after the request fires,.then()chain order swap, parallelcy.request()race against an unfinishedcy.visit().cypress-debuggerF13 expansion — covers blanketcy.on('uncaught:exception', () => false),.catch(() => {})/.catch(() => false)on POM helpers, and explicitly excludes scoped negative-regression tests (handler asserting on error properties).playwright-debuggerPrereq #3 — CI artifact local repro — concretegh run download+--trace=retain-on-failure --video=retain-on-failurerecipe,PLAYWRIGHT_BASE_URLenv mirroring, and short-circuit hypothesis (locally pass + CI fail → F7/F8) so debuggers don't waste cycles trying to repro environment-specific failures.- README — "Quick Example" section — real findings from a typebot.io scan showing exact
[P0] file:line — # ruleoutput shape so first-time visitors see what the skill produces.
e2e-reviewer/SKILL.mdPhase 1 simplified — replaced the 50-lineGrep tool5-batch parallel dispatch instruction (referencingreferences/grep-patterns.md) with a 12-linebash <skill-base>/scripts/scan.sh <test-dir>invocation. Scanner is now the runtime source of truth;references/grep-patterns.mdbecomes an ID-meaning reference for Phase 2 / debugger lookup. Reduces dispatch errors and makes scanner improvements (eslint integration, ast-grep tier) automatic from the agent's perspective.- README structure — Install moved up to right under the 4-skill bullet (was 4th major section); FAQ and Compatibility sections removed (FAQ content was redundant with the rest of the doc; Compatibility duplicated the per-skill descriptions). 437 → ~310 lines.
e2e-reviewer/SKILL.mdanonymized internal references — replaced specific repo names (posthog, typebot, rocket-chat, affine, etc.) with generic descriptors ("a SQL editor scene in an analytics product", "an OSS Playwright suite", "two large monorepos"). The empirical numbers (cycle convergence %, before/after counts) preserved as evidence; only the provenance labels (v1/v3/v4 trial markers) anonymized.scripts/ci/review.sh— markdown link check excludestestbed/,node_modules/; pattern-parity Check 1scan_textsource path updated toskills/e2e-reviewer/scripts/scan.sh; orphan check kept README-only (CONTRIBUTING.md ended up not shipping after the audit pass).scripts/ci/pre-push-security.sh— all grep/find calls now excludetestbed/so external OSS clones don't trigger false-positive blockers (eval(), /tmp paths, hardcoded user-home paths) on this repo's CI..github/workflows/e2e-smell-scan.yml— collapsed to a single scanner step (was eslint + ripgrep + ast companion in 3 separate steps); the bundled scanner now invokes all 3 tiers internally.
- README FAQ section — 7 questions, mostly redundant with the body content. ESLint-complement positioning absorbed into Standalone Scanner; suppression guidance also moved there; framework-scope answer moved to
docs/framework-scope.md. - README Compatibility section — duplicated the per-skill "When to Use" / "Usage" sections and the framework-detection details.
docs/agent-compatibility.md— duplicated the README Install section after the cleanup; the Compatibility Rule sentence (host-agnostic skill phrasing) moved into the Skills Conventions block inAGENTS.md.docs/evals.md— eval-running rules folded intoAGENTS.md"When You Edit Skills" #3 (one true positive + one false-positive guard per new assertion).docs/oss-validation-playbook.md— 318-line maintainer-internal procedure deleted. Most content was either a one-time validation campaign procedure or duplicatede2e-reviewer/SKILL.mdPhase 4. The two genuinely portable bits were preserved: BSD vs GNU sed quirks moved toskills/e2e-reviewer/scripts/scan.shheader comment; no-side-effect rule for OSS validation moved toAGENTS.md"What Not to Do".CONTRIBUTING.md— also deleted as part of the same audit. The "Quick start" was a duplicate ofAGENTS.md"Verification gate"; the eval procedures collapsed intoAGENTS.md"When You Edit Skills" #3.- CLAUDE.md preamble — trimmed to a single
@AGENTS.mdimport line. The 5-line preamble explaining why the file existed was decorative; the import is the load-bearing piece for Claude Code's auto-context. scripts/e2e-smell-scan-ast.sh— separate ast-grep companion deleted; logic merged into the bundledskills/e2e-reviewer/scripts/scan.shTier 2 block.- Mandatory pre-removal Procedures 2 + 3 —
waitForTimeoutgit blamecascade procedure andif (await x.isVisible())rg-context procedure were never used in the 13-repo OSS validation runs (subagents reliably distinguished band-aids via Phase 2 LLM judgment alone). Procedure 1 (force:trueafter readiness check) retained because it explicitly references the SQL-editor anti-example that recurred in two trial rounds.
- Historical
#7 Focused Test Leakseverity tier (superseded in 1.10.0). This release temporarily treated singleton.onlyas P1. The current contract reports every committed focus modifier as P0 because it silently narrows test discovery and becomes load-bearing as soon as a sibling is added; noJUSTIFIEDexemption exists. e2e-reviewer/SKILL.md#15/#16Locator/Page subject confirmation — Phase 1 grep flagged any line starting withexpect(orpage.locator(...).action(. Phase 2 now confirms the subject is a Locator/Page before flagging P0; non-Locator subjects (booleans, primitives, custom service methods likeexpect(await myService.isEnabled()).toBe(true)) explicitly skipped.e2e-reviewer/SKILL.mdPhase 2 multi-line continuation skip for#8a— the regex^\s*page\.(locator|getBy*)(...)flags continuation lines inside multi-lineawait expect(\n page.locator(...)\n).toBeVisible()chains as dangling locators. Phase 2 now skips when the previous non-empty line ends with(or,.e2e-reviewer/SKILL.mdPhase 2Promise.allskip for#16— actions insidePromise.all([waitForEvent(...), action()])arrays are awaited by the wrappingPromise.all; explicitawaiton the array element is wrong syntax. Phase 2 skips these hits.- AGENTS.md parity-surface list — referenced "README.md Quick Reference table" that doesn't exist; the list also missed
skills/e2e-reviewer/scripts/scan.sh. Updated. - AGENTS.md
When You Edit Skillspaired-file rule — said "do not editreferences/grep-patterns.mdwithout checking that the matchingPhase 1block inSKILL.mdstill lines up". Phase 1 was rewritten to callscan.shinstead of inlining grep tables. Updated to "scan.sh is now the runtime source of truth, grep-patterns.md is an ID-meaning reference". .gitignore— added.serena/so Serena MCP tool's local config doesn't get committed..codex-plugin/plugin.jsonlongDescription— corrected stale references discovered during the cross-host audit: removed "OpenCode" from the host list (only Claude Code and Codex are explicitly supported in user-facing surfaces), and updated the standalone scanner path from the oldscripts/e2e-smell-scan.shto the currentskills/e2e-reviewer/scripts/scan.sh.playwright-test-generator/SKILL.mdStep 4 host-specific phrasing — generalized "In Codex/OpenCode, stop after presenting the plan" to "In hosts without [a planning mode]" so the instruction holds across allskillsCLI hosts, not just two named ones.- README +
AGENTS.mdCodex install path — was "register via the Codex marketplace UI (reads.codex-plugin/plugin.json)", which was wrong on two counts: (a) Codex's plugin marketplace is a CLI (codex plugin marketplace add ...), not a UI, and (b) without an.agents/plugins/marketplace.jsonindex the manifest alone is unreachable. After surveying real-world usage (anthropics/skills 135k★ and vercel-labs/agent-skills 26k★ both ship zero.agents/plugins/marketplace.json; the..path traversal needed for a single-source-of-truth layout is hard-blocked bycodex-rs/core-plugins/src/manifest.rs:421-424; thenpx skills add -a codexroute already drops the bundle into~/.codex/skills/for auto-discovery), we intentionally did NOT ship the native marketplace path. README now points Codex users at the cross-agent CLI route, which is functionally equivalent for skill-only plugins.
.codex-plugin/plugin.json— added a dedicated Codex plugin manifest (peer to.claude-plugin/plugin.json) carrying the Codex-specificinterfaceblock (displayName,shortDescription,longDescription,developerName,category,capabilities,websiteURL,defaultPrompt[]per skill,brandColor) while pointing to the same sharedskills/directory used by Claude Code. Both hosts now read from one source of truth for skill behavior with host-specific display surfaces.- Manifest version parity CI —
scripts/ci/review.shCheck 6 verifies that.claude-plugin/plugin.json, thee2e-skillsentry in.claude-plugin/marketplace.json, and.codex-plugin/plugin.jsonshare the sameversionstring.scripts/ci/test-parity.shCase 8 mutates.codex-plugin/plugin.jsonto assert the parity check fires; Case 9 mutates the Codex description out of order to confirm the existing description-parity loop also covers it. - Root
AGENTS.md— added a cross-agent canonical contributor guide at repo root following the AGENTS.md convention. Auto-loaded by Codex CLI, OpenCode, Cursor, and GitHub Copilot at session start; opt-in via config for Aider (--read AGENTS.mdor.aider.conf.yml) and Gemini CLI (.gemini/settings.jsoncontext.fileName). Covers verification gate, directory layout, pattern/failure-code conventions, framework scope, local development commands, lock-step parity surfaces, cross-host parity rules, and installation paths. CLAUDE.md / agent-specific files can defer to this when present. - SKILL.md
license+metadatafrontmatter — addedlicense: Apache-2.0andmetadata: { author: voidmatcha, version: "1.2.2" }to all fourskills/<name>/SKILL.mdfiles to match the canonical Agent Skills frontmatter shape used byanthropics/skillsandvercel-labs/agent-skills. - README SEO/GEO improvements — added Contents TOC under the intro, a "Comparison with Other Tools" table positioning
e2e-revieweragainsteslint-plugin-playwright/ Playwright docs / raw grep, and a 7-question FAQ covering common pre-install user questions.
- Testbed for live OSS validation — added
testbed/(gitignored) as the canonical location to clone real-world Playwright/Cypress repos against which the skills can be exercised. Clone manually (git clone --depth 1 <url> testbed/<name>); thenbash scripts/e2e-smell-scan.sh testbed/<name>or invokee2e-revieweron the path. - Local-install reinstall script (historical behavior) — the original
implementation ran
npx skills removethennpx skills add <repo-root> --copy. This entry's old claim that working-tree edits could not enter the copy is superseded: the current installer intentionally snapshots the exact working tree at invocation, uses a verified pinned CLI dependency closure, and verifies receiving surfaces before accepting replacement. - Pre-push git hook (historical behavior) — the original hook ran
npx skills update. The current hook delegates to the hardened reinstall script, so the installed copy reflects the working tree present at push time rather than claiming an automatic HEAD-only snapshot.
e2e-reviewer/SKILL.mdfrontmatter YAML parse regression — description re-introduced):(colon-space) tokens forbidden in YAML plain scalars when the severity-first restructure added phrases likeP0 must-fix (silent always-pass):. Same failure mode as v0.7.3 (YAMLExceptionin gray-matter, skills CLI silently skips the skill). Fix: wrap the description in single quotes. Also updatedscripts/ci/review.shCheck 5 regex lookahead to accept the closing single quote (\.(?=[\s']|$)) and added a stdlib-only frontmatter guard that fails any unquoteddescription:value containing:. Drift smoke Case 10 covers the regression.- Pattern parity CI check — added a "Pattern and description parity" section to
scripts/ci/review.shthat programmatically catches the drift categories surfaced manually across the v1.2.2 audit rounds. Verifies: (1) every pattern id ingrep-patterns.md,e2e-smell-scan.sh, anddocs/e2e-test-smells.mdmaps back to a base id in thee2e-reviewer/SKILL.mdQuick Reference; (1b) every Quick Reference base id appears indocs/e2e-test-smells.md(reverse completeness); (2) docs P0/P1/P2 section placement agrees with QR severity (composite severities likeP0/P1tolerated); (3) README severity-section placement (#### P0 / P1 / P2) agrees with QR severity; (3b)e2e-reviewer/SKILL.mdReview Checklist severity-section placement (### P0 / P1 / P2) agrees with QR severity; (3c) Quick Reference has exactly 19 rows and the set of####ids across### P0 / P1 / P2sections equals the set of Quick Reference ids; (4)playwright-debuggerandcypress-debuggerevals.jsononly reference F-codes present in theirSKILL.mdtaxonomy; (5) the 19 pattern phrases from the SKILL.md frontmatter (grouped P0 → P1 → P2) appear in order in bothplugin.jsonandmarketplace.jsondescriptions (with punctuation-tolerant normalization). - Drift smoke test — added
scripts/ci/test-parity.shthat mutatesgrep-patterns.md,docs/e2e-test-smells.md,README.md,e2e-reviewer/SKILL.md, and.claude-plugin/plugin.jsonin known-bad ways, assertsreview.shexits non-zero with the expected error substring, and restores each file from backup. Validates that the parity checks themselves actually catch drift (not just the current state). Wired intoscripts/ci/ci-local.shbetween review checks and the smell scan; gated byE2E_SKILLS_SKIP_PARITY_SMOKE=1for parity with the existing skip-flag pattern. - Codex/OpenAI metadata — added
agents/openai.yamlmetadata for all four skills to improve Codex skill discovery and display. - README OSS proof section — documents merged open-source E2E/testing contributions by
voidmatchaacross Cal.com, Storybook, and Element Web. - README review surface expansion — adds broader E2E review guidance for selectors, waits, isolation, network boundaries, auth, accessibility, visual checks, CI diagnostics, and test scope.
- Standalone E2E smell scanner — added
scripts/e2e-smell-scan.shfor agent-free P0/P1 mechanical checks. - Eval validator — added
scripts/validate-evals.shto keep skill eval definitions structurally valid. - Convention/security CI — added
scripts/ci/ci-local.sh,scripts/ci/review.sh, andscripts/ci/pre-push-security.shto validate skill metadata, eval metadata, shell syntax, local links, agent manifests, version sync, and high-confidence security patterns. - GitHub Action example — added
.github/workflows/e2e-smell-scan.ymlto run convention/security checks, run the scanner in CI, and upload a report. - Open-source docs — added E2E smell taxonomy, OSS case studies, eval guidance, framework scope, and agent compatibility.
- Namespace rename
dididy→voidmatcha— replaced legacy GitHub namespace withvoidmatchaacross.claude-plugin/marketplace.json(name), README install commands, anddocs/agent-compatibility.md. Author display fieldYONGJAE LEEpreserved across the threeauthorentries (plugin.json, marketplace.json, .codex-plugin/plugin.json). - README installation docs — added Codex/OpenCode user-skill installation commands.
playwright-test-generatorapproval gate wording — replaced Claude-only planning-mode wording with Claude Code, Codex, and OpenCode-compatible approval instructions.marketplace.jsonkeywords — removed a stale unsupported automation keyword after scope was narrowed to Playwright and Cypress.
e2e-reviewerJUSTIFIED scope extended to enclosing block and chained calls — the Phase 1 interpretation rule andreferences/grep-patterns.mdboth said "Lines where the immediately preceding line contains// JUSTIFIED:are intentional — skip them". A real-world Zeppelin Angular review surfaced two routine false-positive shapes the rule did not cover: (1)document.querySelectorinside apage.evaluate(() => { ... })orpage.waitForFunction(() => { ... })callback where the JUSTIFIED comment sits above the enclosing call, not above eachquerySelectorline; (2) chained Playwright calls split across lines (page.locator(...)\n .filter(...)\n .first()) where the JUSTIFIED comment sits above the chain's starting expression, not above the.first()line. Extended the rule (inSKILL.mdPhase 1 and thereferences/grep-patterns.mdheader) to recognize// JUSTIFIED:in three positions: immediately preceding the hit, immediately preceding the enclosing callback/block, or immediately preceding a multi-line chain's starting expression. Added a "read 1–3 lines of surrounding context before flagging" reminder so grep-only review output does not become the source of false positives.e2e-reviewer#4 multi-URL substring fix guidance — Fix list saidexpect(page.url()).toContain(x)→await expect(page).toHaveURL(x). A real fix pass converted consecutiveexpect(page.url()).toContain('A'); expect(page.url()).toContain('B');calls into a singleawait expect(page).toHaveURL(/A.*B/)— which silently introduces an ordering constraint not in the original substring checks (passes only when A precedes B in the URL). Added explicit guidance to replace each call with its ownawait expect(page).toHaveURL(/.../)and not combine them into one regex with.*.e2e-reviewer#4 compound boolean expression variant — the #4 anti-pattern catalogue coveredexpect(await el.isVisible()).toBe(true)but not the equivalent compound formexpect(visible1 || visible2).toBe(true)where two boolean variables are or'd together insideexpect(). Added the compound boolean case to the fix list with guidance: prefer locator-levelexpect(page.locator('.a, .b')).toBeVisible()or gate the test withtest.skip()on the unsupported branch rather than collapsing into a one-shot boolean check.e2e-reviewer#10a method-name and fallback-loop exemptions —.nth()/.first()/.last()had a binary "needs// JUSTIFIED:" rule that produced false positives on two routine self-documenting shapes: (a) POM methods whose names already encode positional intent likegetParagraphByIndex(index) { return this.paragraphs.nth(index); }; (b) fallback-selector loopsfor (const sel of fallbackSelectors) { ... locator(sel).first() ... }where.first()means "any match for this candidate selector", not positional. Added explicit exemption bullets to #10a so reviewers skip these shapes without requiring a// JUSTIFIED:comment. Also exemptedawait expect(items).toHaveCount(1); const only = items.first();where the adjacent count assertion already documents that exactly one element exists.e2e-reviewer#14 scope narrowed to actual auth usage — #14 Hardcoded Credentials flagged any literal matching(login|fill|type).*(password|secret|admin). This produced false positives on input-behavior tests likepasswordInput.fill('typed text'); await expect(passwordInput).toHaveValue('typed text');where the literal is test data for verifying form input acceptance, never used as an actual credential. Tightened the rule: only flag literals passed to authentication operations (loginPage.login(...), password fields immediately followed by submit, API auth posts, or fixtures namedvalidUser/testAdmin). Input-acceptance testing and intentional invalid-creds fixtures with dummy username/password values for negative-path tests are explicitly excluded. Reviewers must read 2–3 lines below a literal to confirm a login/auth call follows.e2e-reviewerPhase 2.5 framework-agnostic selector check — the systemic "CSS-only selectors" row only listed Playwright APIs (getByRole/getByTestId/getByLabel/getByPlaceholder/getByText). Running on a Cypress project that usedcy.get('[data-cy=...]')exclusively (a perfectly good user-facing selector strategy) would still hit zerogetBy*and emit a false-positive P2 finding. Renamed the row to "No stable user-facing selectors" and made the detection rule framework-aware: Playwright keeps thegetBy*check; Cypress checks zero[data-cy=]/[data-testid=]selectors and zerocy.findBy*(cypress-testing-library) usage.e2e-reviewerReview Checklist reframed as Pattern Reference — the section header## Review Checklistplus the intro "Run each check against every non-skipped test" framed the 19-pattern catalogue as a separate execution phase placed after Phase 3. Phase 1 / Phase 2 / Phase 2.5 already execute all 19 patterns, so the wording risked LLMs running the full check set a second time as a duplicate pass. Renamed to## Pattern Referencewith intro "Do not re-run these checks as a separate pass — the phases above already cover them. When emitting a finding, consult the matching section here for the canonical Symptom / Rule / Fix wording." CI Check 3b is unaffected (it parses### P0 / P1 / P2subsection headers, not the parent heading).e2e-reviewerPhase 2 #10 LLM role made concrete — the Phase 2 LLM Review table row for#10 Flaky Test Patternssaid "Requires context judgment for nth() and serial ordering", but the Phase 1 grep +// JUSTIFIED:mechanical check already handles both#10a nth/first/lastand#10b describe.serial. It was unclear what additional judgment Phase 2 should add. Replaced with an explicit task: verify that any// JUSTIFIED:comment on a#10hit gives a concrete rationale (e.g. "server returns in fixed order") rather than a vague one ("needed for now"); flag if the comment doesn't actually justify the position-coupling or serial dependency. Hits without a JUSTIFIED comment are skipped here — Phase 1 already flagged them.e2e-reviewerOutput Format heading simplified — the example finding heading was## [P0/P1/P2] Task N: [filename] — [issue type]. There is no "Task" concept anywhere in the Phase 0 / 1 / 2 / 2.5 / 3 workflow — it was a leftover artifact from a previous tasking structure. Simplified to## [P0/P1/P2] [filename] — [issue type]and the per-finding sub-heading from### N-1.to###.playwright-test-generatorStep 7 attempt categories reframed as heuristic — the failure handling table hadAttempt 1 → Selector mismatches,Attempt 2 → Assertion failures,Attempt 3 → Structural issues. Real failures don't arrive in that order, so the strict per-attempt category mapping was misleading. Reframed as a "Likely cause / Fix" table with explicit note that "the order is heuristic — the real failure dictates which category to try first". The max-3-attempts ceiling andplaywright-debuggerhandoff are unchanged.playwright-test-generatorStep 6 e2e-reviewer P0 loop ceiling — Step 6 said "P0 issues found: fix immediately, re-invokee2e-reviewer, repeat until 0 P0s" — no maximum attempt count. Unfixable P0s (e.g. an intentionaltest.onlyleft during development, or aforce: truewith no JUSTIFIED rationale that the model can't infer) would loop indefinitely. Capped at 3 fix attempts to match Step 7's max-3-attempts pattern; remaining P0s are listed in the final report and the pipeline proceeds to Step 7 with a warning.e2e-reviewerPhase 2.5 deduplication wording made unambiguous — the No-auth systemic row read "Skip if Phase 2 already reported #12 on every affected file individually — only surface the suite-wide rollup", which could be read as "skip the Phase 2.5 rollup entirely when Phase 2 covers every file". The Deduplication Rule paragraph below it said the opposite (always emit one rollup line; just don't enumerate per-file findings). Reworded the row to "Always emit a single rollup line here; do not enumerate per-file findings — those belong in Phase 2" so the table cell and the paragraph agree.e2e-reviewer#11Pattern Reference heading aligned with Quick Reference — Quick Reference shows#11as "YAGNI + Zombie Specs" but the Pattern Reference section heading read#### 11. YAGNI — Dead Test Code. Body content covers both11aPOM YAGNI and11bZombie spec files correctly, but the heading dropped the zombie-spec naming. Renamed to#### 11. YAGNI + Zombie Specsfor grep parity with Quick Reference anddocs/e2e-test-smells.md.e2e-reviewerPhase 0 framework-skip lists made symmetric — the Cypress-skip list under "If Playwright" was prose (cy.wait,#3b uncaught:exception) while the Playwright-skip list under "If Cypress" enumerated explicit pattern IDs (describe.serial, danglingpage.locator,#18,#15/#16,#17). Made the lists symmetric with pattern IDs on both sides:#9b cy.wait(ms)/#3b Cypress uncaught:exceptionfor Playwright reviews,#8a/#10b/#15/#16/#17/#18for Cypress reviews.- README Phase 1 grep enumeration — README "How E2E Reviewer Works → Phase 1" listed the early grep targets as
#3 error swallowing, #5 bypass patterns, #8 missing assertions, #9 hard-coded sleeps, ... and supplementary code-quality checks. The trailing catch-all hid#14Hardcoded Credentials,#15Missing await on expect,#16Missing await on action,#17Direct page action API, and#18expect.soft()overuse — five grep-detectable patterns reviewers using only the README would not know to look for. Replaced with an explicit enumeration of all grep-phase ids so the README matches the Phase 1 grep tables ine2e-reviewer/SKILL.md. #5Bypass Patterns composite severity marker —#5is composite (5aP0evaluate()DOM bypass /5bP1nth()index reliance) but the SKILL.md Review Checklist heading and the README severity row presented it as a single item without disclosing the sub-pattern severity split. Annotated the SKILL.md heading as#### 5. Bypass Patterns [grep-detectable] (5a P0, 5b P1)and the README P0 row as(5a P0, 5b P1)so reviewers see the split without opening the body. Quick Reference already usesP0/P1in the severity cell; CI Check 3 / 3b tolerate composite severities.e2e-reviewerSKILL.md frontmatter description compaction — description was 1538 chars (compared to 461–682 chars for the other three skills) after the severity-first restructure expanded the pattern list. Dropped the parenthetical sub-pattern enumerations (already documented in the body) while keeping the P0/P1/P2 severity grammar Check 5 depends on. New length 1193 chars; trigger phrase coverage preserved.e2e-reviewerCI parity hardening — Check 3c — added a Quick Reference / Review Checklist set-equality check inscripts/ci/review.sh. Verifies (a) the Quick Reference table has exactly 19 rows and (b) the set of####ids across### P0 / P1 / P2sections equals the set of Quick Reference ids. Catches drift where a pattern is added to one place but not the other.e2e-reviewerPhase 2.5#12double-report — Phase 2.5 "No authentication strategy" systemic row duplicated#12 Missing Auth Setup. Both were P0 with no rule distinguishing when to report which. Added an explicit suite-level rollup contract: Phase 2 emits one finding per affected file, Phase 2.5 rolls up only when 3+ files share the issue, and a deduplication rule paragraph spells out that Phase 2.5 must not also list every file.e2e-reviewer/agents/openai.yamldescription alignment — the Codex manifest description listed "false-passing assertions, missing assertions, flaky selectors, sleeps, auth gaps, POM drift" — a stale curated list that predated the 19-pattern severity taxonomy. Updated to reference the P0/P1/P2 taxonomy with representative pattern examples per severity.e2e-reviewerReview Checklist restructured to severity-first organization — replaced theTier 1 (always check)/Tier 2 (check when time permits)/Supplementary Checkssplit with explicit### P0 — Must Fix,### P1 — Should Fix, and### P2 — Nice to Fixsections in bothe2e-reviewer/SKILL.mdand README. The previous Tier/Supplementary structure had two structural problems: (1) "Supplementary Checks" contained three P0 items (#12Missing Auth Setup,#15Missing await on expect,#16Missing await on action) — equally critical silent-pass bugs as the Tier 1 P0s, but the "Supplementary" label made them look optional; (2)#6Raw DOM Queries was under Tier 1 "always check" despite being P1 in the Quick Reference, while#11YAGNI was under Tier 2 P1/P2 despite being P2. The new severity-first layout aligns SKILL.md, README, plugin/marketplace descriptions, and the canonical Quick Reference table on a single P0/P1/P2 taxonomy: P0 (11 items), P1 (7 items), P2 (1 item) — 19 anti-patterns total. Frontmatter description rewritten to enumerate patterns under each severity.scripts/ci/review.shChecks 3 / 3b / 5 rewritten to verify (a) every####id under each### P0 / P1 / P2section in SKILL.md matches that severity in the Quick Reference, (b) every README severity-table row matches Quick Reference severity, and (c) the 19 patterns from the SKILL.md frontmatter appear in order in plugin.json / marketplace.json descriptions.- README duplicate Supplementary Check —
Weak CI observabilitywas listed both under### Supplementary Checksand### Full Review Surface(asCI diagnostics). Removed the duplicate row fromSupplementary Checks; the substantive item stays inFull Review Surfacewhere it semantically belongs (it is a review-surface concern, not a code-level check). .gitignore— ignores local.sisyphus/continuation state so generated session files are not committed accidentally.playwright-debuggerevals F-code taxonomy —evals/evals.jsonassertions used F-codes that did not match theSKILL.mdF1–F14 table: selector-not-found was mislabeled F1 (should be F2 Selector Broken), timeout was mislabeled F2 (should be F1 Flaky/Timing), API 500 was mislabeled F5 (should be F3 Network Dependency), session expiry was mislabeled F7 (should be F10 Auth/Session), and race condition / animation race were mislabeled F8 / F12 (should be F1 / F14). Eval expected_output and per-assertion F-codes corrected to matchSKILL.md.e2e-reviewerPhase 2 LLM table coverage —#15 Missing await on expectand#16 Missing await on actionhad per-rule "confirm in Phase 2" instructions but no corresponding rows in the Phase 2 LLM Review table. Rows added:#15confirms the subject is a Locator/Page (not a primitive),#16confirms the line lacks leadingawaitfor a real Playwright action.playwright-test-generatorfrontmatter and Pipeline overview — descriptions said "Playwright CLI or agent-browser" but Step 3 body already documents agent-browser as primary andnpx playwright codegenas a manual fallback (set in 1.1.2). Frontmatter description and Pipeline overview now match.e2e-reviewerfrontmatter wording — described#12 Missing Auth Setupand#13 Inconsistent POM Usageas "supplementary grep checks", but both are LLM-only inSKILL.md. Reworded to "supplementary checks" with detection method tagged per item. Cypressuncaught:exceptionsuppression broken out as its own group so the "13 anti-pattern groups" count matches the README Tier tables and Quick Reference.#9Hard-coded Sleeps pattern label alignment —playwright-debuggerandcypress-debuggerPhase 2 classification tables pointed to#9afor F1 Flaky/Timing and F14 Animation Race, andscripts/e2e-smell-scan.shused#9afor the PlaywrightwaitForTimeoutcheck. The canonical label ine2e-reviewer/SKILL.mdandreferences/grep-patterns.mdis#9(with#9bCypress and#9cnetworkidle as variants). All five references updated to#9.- README Tier placement of
#3b—Cypress uncaught:exception suppressionwas listed under Tier 2 (P1/P2) but is P0 in Quick Reference,grep-patterns.md, ande2e-smell-scan.sh. Moved to Tier 1 (P0/P1) where it belongs as a P0 sub-variant of#3 Error Swallowing. - README Phase 2 description coverage — Phase 2 LLM workflow summary omitted
#4 .toBeTruthy()Locator-subject confirmation,#15missing-await-on-expect Locator confirmation, and#16missing-await-on-action confirmation. Added so the README description matches the actual Phase 2 LLM Review table inSKILL.md. docs/e2e-test-smells.mdtaxonomy alignment — public taxonomy doc had two ID divergences from the canonicalSKILL.mdQuick Reference:#3b Cypress uncaught:exception suppressionwas described inside#3instead of getting its own P0 row, and an invented#11b Zombie specrow existed under P2 even thoughSKILL.mdcovers zombie specs inside#11 YAGNI + Zombie Specs. Added a dedicated#3bP0 row and folded#11bcontent into the#11row so the doc reflects the IDs the reviewer and scanner actually emit.plugin.json/marketplace.json13-group description — marketplace description listed the 13 anti-pattern groups in a non-numerical order (#8 missing assertionsbefore#5/#6/#7), labeled#10as "flaky selectors" (which omits thedescribe.serialsub-pattern), and labeled#11as "YAGNI" (which omits zombie specs). Aligned toSKILL.mdfrontmatter wording and numerical order so marketplace listing matches the canonical skill description.
e2e-reviewerunsupported automation scope removed — the description previously listed an automation target with zero grep patterns and zero eval coverage. Scope narrowed to Playwright and Cypress with full grep + LLM analysis. General principles (name-assertion alignment, missing Then, YAGNI) still apply to any framework.e2e-reviewerPhase 1 grep tables extracted toreferences/grep-patterns.md— SKILL.md reduced from 625 to 571 lines. Patterns loaded on demand, not always in context.e2e-reviewertest directory auto-detection — Phase 1 no longer hardcodese2e/; instructs auto-detection from project structure (tests/,__tests__/,spec/,cypress/e2e/, etc.).e2e-reviewer#14 credentials grep pattern expanded — now catchescy.get('#password').type('<literal-secret>')andpage.getByLabel('Password').fill('test')in addition tologinPage.login('admin', 'pass').
e2e-reviewerevals strengthened — all 7 eval assertions now include line numbers, specific context, and P0/P1/P2 severity tags. False positive assertions added to eval 1 (public route not flagged as missing auth), eval 2 (toBeVisible not flagged as always-passing), and eval 6 (chained cy.get not flagged as dangling).
e2e-reviewer#18expect.soft()overuse (P1, grep+LLM) — Phase 1 grep detects allexpect.soft()hits; Phase 2 LLM confirms if >50% of assertions in a single test are soft. Tests with only soft assertions never fail early, functionally equivalent to error swallowing.e2e-reviewer#3b Cypressuncaught:exceptionsuppression (P0, grep) — detectscy.on('uncaught:exception', () => false)in both spec files andcypress/support/directory. Blanket suppression is P0; scoped handlers with// JUSTIFIED:are acceptable.playwright-test-generatorforbidden patterns expanded —toBeAttached(),expect(locator).toBeTruthy(), directpage.click(selector),{ force: true },waitUntil: 'networkidle',expect(page.url()).toContain(x)added to code-rules.md forbidden table.playwright-test-generatorawait rule — explicit rule: everyexpect()on a Locator and every Playwright action must beawaited.playwright-test-generatorSuppression Convention section — documents// JUSTIFIED:pattern for unavoidable forbidden patterns (nth(),force: true,timeout: 0,evaluate()), bridging generator output toe2e-reviewergrep checks.best-practices.mdanti-patterns expanded —networkidle, directpage.click(selector), missingawaitadded.
e2e-reviewerselector priority (#10a) — updated fromdata-testid → role/labelto Playwright official order:getByRole→getByLabel→getByTestId→getByText→ attribute → class → generic. Now consistent withplaywright-test-generatorcode-rules.md.e2e-reviewerPhase 0 framework skip — expanded to include new checks: Playwright skips#3b; Cypress skips#18,#15/#16,#17.e2e-reviewerpattern count — 11 → 13 (description, README, Quick Reference updated).playwright-test-generatorexpect.soft()guidance — changed from unconditional recommendation to conditional: at least one hardexpect()must gate per test.playwright-test-generatorspec example credentials — replaced hardcoded sample user/password literals withprocess.env.TEST_USER/process.env.TEST_PASSWORD.- README — pattern count 11 → 13, Tier 2 table updated, Review Workflow updated.
e2e-reviewersupplementary grep checks — 6 additional patterns for general code quality (missing auth setup, inconsistent POM usage, hardcoded credentials, missing await on expect/action, direct page action API usage, networkidle). These supplement the core 11 anti-patterns during review.- Evals for all 4 skills —
e2e-reviewer(7 evals),playwright-debugger(4 evals),cypress-debugger(4 evals),playwright-test-generator(3 evals) .gitignore— eval fixture files and workspace dirs excluded from git
playwright-test-generatorStep 3 — browser exploration method corrected:playwright-cli(non-existent package) removed entirely. Agent-browser tools (browser_navigate,browser_snapshot,browser_click,browser_type,browser_close) are now the primary exploration method.npx playwright codegendocumented as a manual reference only — interactive, not automatable in an agent pipeline.- README Compatibility —
playwright-test-generatorCompatibility section updated to reflect agent-browser as primary exploration method.
e2e-reviewerrenumbered to 11 checks — #8 Missing Assertion inserted (Tier 1, P0); Flaky Test Patterns reordered to #10, YAGNI to #11. Final order: #1–#8 Tier 1, #9 Hard-coded Sleeps, #10 Flaky, #11 YAGNI.e2e-reviewer#4 Always-Passing expanded — four new sub-cases: one-shot DOM reads (textContent/getAttribute), Locator-as-truthy (toBeTruthy()on Locator), assertion retry disabled ({ timeout: 0 }), and explicittoBeAttached\(\)grep pattern added to Phase 1.e2e-reviewer#8 Missing Assertion (new, P0) — merged from former #11/#12: 8a dangling locator[Playwright grep], 8b boolean result discarded[all frameworks grep]. Cypress dangling selectors require Phase 2 manual check.e2e-reviewer#3 Error Swallowing —try/catchin specs moved to Phase 2 LLM (false positive risk in setup/teardown);.catch(() => {})in POM remains Phase 1 grep. Quick Reference updated togrep+LLM.e2e-reviewer#5a Conditional Bypass — clarified as runtimeif-gated assertion only; removed misleading "mid-testtest.skip()" reference.e2e-reviewer#9 Hard-coded Sleeps — severity P2 → P1 (direct flakiness cause); added BAD/GOOD code example.e2e-reviewerassertion weakening removed —toBeDefined()/not.toBeNull()dropped; unit-test concern, low ROI in E2E context.e2e-reviewercode examples simplified — generic placeholders throughout;async ({ page })signatures added to #1/#2;// JUSTIFIED:colon normalized.e2e-reviewersub-label corrections — Flaky:8a/8b→10a/10b; YAGNI:10a/10b→11a/11b; Phase 1 grep list reordered #3–#10 sequential.- README — pattern count 10 → 11, #4/#8 table entries updated, Review Workflow updated, Phase 1 range noted as partial for #10.
- plugin.json / marketplace.json — version 1.1.2, pattern count updated to 11.
e2e-reviewer#4 Always-Passing — expanded with three new sub-cases:- Non-retrying state snapshot:
expect(await el.isDisabled()).toBe(true)resolves a one-shot boolean with no auto-retry; use web-first assertions (toBeDisabled(),toBeEnabled(),toBeChecked(),toBeHidden()) instead. New grep pattern:expect\(await.*\.(isDisabled|isEnabled|isChecked|isHidden)\(\)\). - Assertion weakening —
toBeDefined()passes fornull;not.toBeNull()passes for"". Usenot.toBeNull()whennullis the sole invalid case; usetoBeTruthy()when empty string is also invalid (OAuth codes, secrets, slugs). - SKILL.md #4 section updated with concrete bad/good examples for assertion weakening.
- Non-retrying state snapshot:
playwright-test-generator— new skill for generating Playwright E2E tests from scratch- 7-step pipeline: environment detection → coverage gap analysis → live browser exploration (Playwright CLI / agent-browser) → scenario design with an approval gate → code generation → YAGNI audit + e2e-reviewer → TS compile + test run
- Structure-aware: auto-detects POM vs flat spec pattern, extends existing POMs when present
- Coverage gap analysis: scans Angular, Next.js, React Router routing files; maps existing specs to routes; flags auth and form-heavy pages as high priority
- Browser exploration via Playwright CLI (
playwright-cli open/snapshot/close); falls back to agent-browser tools - Approval gate: scenario list + locator mapping table before any code is written
- Quality loop: YAGNI audit removes unused locators immediately after generation;
e2e-reviewerruns automatically (P0 issues fix-looped, P1/P2 reported) - Failure handling: 3 targeted auto-fix attempts (selectors → assertions → structure), then hands off to
playwright-debugger - Companion files:
code-rules.md(selector priority, POM/spec rules, forbidden patterns) andbest-practices.md(Playwright official best practices reference)
- README: added
playwright-test-generatoras Skill 1; updated workflow to include generation step; added Compatibility entry
e2e-reviewerReferences: Added Playwright and Cypress best-practices links at top of SKILL.md.e2e-reviewer#4 Always-Passing —isVisible()boolean trap: Addedexpect(await.*\.isVisible\(\))as a new grep-detectable variant.isVisible()resolves a one-shot boolean with no auto-retry; a transiently absent element can cause a silent pass. Rule extended to flag these and direct toexpect(locator).toBeVisible()(web-first, auto-retries). Fix line and Quick Reference Detection Signal updated accordingly.e2e-reviewer#7 Focused Test Leak (new check, P0, grep):test.only/it.only/describe.onlycommitted to source silently skips the entire suite in CI — all other tests show as "not run" but the step passes. No// JUSTIFIED:exemption. Pattern:\.(only)\(in spec files. Added to Phase 1 grep, Tier 1 section, and Quick Reference.e2e-reviewer#8a Positional selectors — selector priority ranking: Added one-line selector priority guide. Priority order (best → worst):data-testid/data-cy→ role/label →nameattr →id→ class → generic. Class and generic selectors are "Never."
e2e-reviewerDuplicate Scenarios removed: Dropped the fuzzy per-test 70% overlap check — subjective threshold, expensive cross-file comparison, high false positive rate. Zombie spec file detection (entire file covered by another) absorbed into #10 YAGNI as sub-pattern 10b.e2e-reviewerRenumbered to 10 checks: #7 Focused Test Leak inserted (Tier 1, P0); Flaky Test Patterns (P1) reordered before Hard-coded Sleeps (P2). Final order: #1–#7 Tier 1, #8 Flaky, #9 Hard-coded Sleeps, #10 YAGNI.e2e-reviewer#10 YAGNI expanded: Added sub-pattern 10b zombie spec files + single-use Util wrapper rule (2+ threshold). Section renamed to "YAGNI — Dead Test Code."e2e-reviewerSuppression rule:// JUSTIFIED:now suppresses on the line above the flagged pattern instead of on the same line. Updated across all grep-checked patterns (#3, #4, #5, #8 partial, #9). Exception: #7 Focused Test Leak has no// JUSTIFIED:exemption.e2e-reviewer#8b Serial: Added explicit note that// JUSTIFIED:on the line above suppresses thedescribe.serialflag.e2e-reviewer#6 Raw DOM Queries: Simplified code examples — redundant BAD example removed; "Why it matters" moved above the code block.e2e-reviewerfrontmatter description: Updated to reflect 10 checks, zombie spec files, and focused test leak.
e2e-reviewer#5 expanded → "Bypass Patterns": Added{ force: true }detection (P1, grep) as sub-pattern 5b alongside existing conditional assertion bypass (5a).force: truewithout// JUSTIFIED:hides real actionability failures that real users would encounter.e2e-reviewer#9 expanded → "Flaky Test Patterns": Addedtest.describe.serial()detection (P1, grep)[Playwright only]as sub-pattern 9b alongside existing positional selectors (9a).describe.serialcreates order-dependent tests that break parallel sharding.e2e-reviewertrigger phrases expanded: Added "my tests are fragile", "tests break on every UI change", "test suite is hard to maintain", "we have coverage but bugs still slip through" to SKILL.md frontmatter descriptionmarketplace.jsonkeywords expanded: Addedfragile-test,brittle-test,static-analysis,test-maintenance,test-smell,false-positive,end-to-end,spec- README Key Insight moved to top (after workflow section) for GEO discoverability
e2e-reviewerreduced to 10 patterns — removed 3 more checks that weren't reliably detectable via static analysis:- #5 Boolean Trap removed —
expect(locator).toBeTruthy()is rare in practice among Playwright/Cypress users; low ROI - #10b Animation Race removed from Flaky Patterns — cannot be detected statically; requires running the tests to confirm
- #4 Always-Passing
toBeAttached()simplified — replaced multi-step template-reading decision tree with a single rule: flag anytoBeAttached()with no inline comment. No template analysis required.
- #5 Boolean Trap removed —
- Description updated across
marketplace.jsonandplugin.json— new hook: "catch what CI misses — tests that pass but prove nothing, and failures that are hard to trace" - Project-specific examples removed from SKILL.md — replaced with generic element/variable names throughout
e2e-reviewerreduced to 11 patterns — removed 4 checks that were too subjective or context-dependent for general use:- #8 Render-Only removed — smoke tests legitimately use only
toBeVisible(); too many false positives - #10 Misleading Names removed — absorbed into #1 (Name-Assertion Alignment); a name that implies a mechanism the test doesn't use is already a name-assertion mismatch
- #11 Over-Broad Assertions removed — too domain-specific; "known enum values" is not universally determinable
- #12 Subject-Inversion removed —
expect([200, 202]).toContain(status)is a common and readable pattern in many teams - #13b Missing Network Mock removed from Flaky Patterns — real E2E philosophy intentionally avoids mocks; prescribing mocks conflicts with team conventions
- #13 Hard-coded Timeout narrowed — now only flags explicit sleeps (
waitForTimeout,cy.wait(ms)); no longer flagstimeout:option values inwaitForcalls
- #8 Render-Only removed — smoke tests legitimately use only
- Renumbered: 1–7 unchanged; 8=Duplicate, 9=Hard-coded Sleep, 10=Flaky Patterns, 11=YAGNI
e2e-reviewer#9 Zombie spec file detection: Added "zombie spec file" pattern — if ALL tests in a spec file are subsets of tests in another file covering the same feature, flag the entire file for deletion. Previously only individual test-level overlap was detected; whole-file redundancy (e.g., a 1-test file that duplicates a test in a larger suite) was missed.e2e-reviewer#14 Empty wrapper class detection: Added check for POM classes that extend a parent but declare zero additional members (class Foo extends Barwith constructor-only body). Flags these for review (P2) — may be intentional convention or future-extension placeholder, so automatic deletion is not prescribed. Previously YAGNI only checked individual unused members, not the class itself.
e2e-reviewer#4 toBeAttached() grep scope: Extended search target from spec files only to.ts/.js/.cy.*(all files including POM/util) —toBeAttached()in POM helper methods was previously invisible to Phase 1 grepe2e-reviewer#6 Conditional Bypass POM gap: Added explicit note that the Phase 1 grep only covers spec files; POM/util methods withif (await el.isVisible())guards must be reviewed manually in Phase 2
e2e-reviewer#4 Always-Passing —toBeAttached()detection: Added grep + LLM template check fortoBeAttached()on unconditionally rendered elements (elements always present in DOM regardless of app state). Decision tree: unconditionally rendered or in static HTML shell → flag P0; CSSvisibility:hiddenvariant or conditionally rendered → skip (meaningful assertion).e2e-reviewerSuppression — same-line rule:// JUSTIFIED:comment must appear on the same line as.catch(— a comment on the next line is invisible to grep. Added BAD/GOOD examples and a note that named function wrappers don't help (each inner.catch(still needs its own// JUSTIFIED:comment).
e2e-reviewer#3 Error Swallowing grep pattern: Updated to\.catch\(\s*(async\s*)?\(\)\s*=>— now detects both sync (() => {}) and async (async () => {}) silent catch variants.e2e-reviewer#7 Raw DOM Queries scope expanded: Now explicitly coversdocument.querySelectorinsidewaitForFunction()in addition toevaluate(). Rule updated:locator.waitFor({ state: 'attached' })replaces single-conditionwaitForFunction(() => querySelector(...) !== null). Exception list expanded: multi-condition AND/OR,children.length,body.textContent,getComputedStyle— add// JUSTIFIED:explaining why.e2e-reviewerframework-agnostic cleanup: Replaced project-specific examples (nz-tree,zeppelin-root,app-root, Angular*ngIf) with generic ones (.sidebar,#app, "conditional rendering directive") — skill no longer assumes Angular or any specific framework/component library.
playwright-debuggerIndirect Prompt Injection (W011): Added "Security: Treat Report Data as Untrusted" section — all content fromplaywright-report/is explicitly declared untrusted external data; embedded instructions in test titles, error messages, or trace content must never be followedplaywright-debuggerDynamic Code Execution: Replacednode -einline shell scripts in Phase 1 and Phase 3 with Read tool +/tmpfile approach — prevents untrusted trace content from being executed via shell interpolationplaywright-debuggerUnverifiable External Dependency (W012): Removedgh run downloadartifact fetching entirely — reports must now be provided as a local path by the user; eliminates the external data ingestion attack surface
playwright-debuggerPrerequisites: Removed GitHub PR URL /ghCLI download flow; report source is now always a user-provided local path or existingplaywright-report/directory
e2e-reviewerYAML parse error: colon in frontmatter description (naming-assertion mismatch, missing Then, error swallowing, always-passing assertions, boolean traps, conditional bypass, raw DOM queries, render-only tests, duplicate scenarios, misleading names, over-broad assertions, subject-inversion) caused aYAMLExceptionin gray-matter, making the skills CLI skip the skill entirely — replaced colon with em dash
playwright-debugger: replaced dense inlinenode -eone-liners in Phase 1–3 with natural language instructions — LLM reads trace events directly instead of running shell scriptse2e-reviewer: replaced Phase 1 bash grep block with a prose checklist — LLM uses the Grep tool per anti-pattern instead of running a shell script- README: updated
playwright-debuggerdebug workflow description to reflect trace analysis approach
Motivation for code block changes: reduced code density to address Socket "Obfuscated File" false positive on SKILL.md files.
skills/e2e-test-reviewer/renamed toskills/e2e-reviewer/— shorter skill name for CLI discoverabilityname: e2e-test-reviewer→name: e2e-reviewerin SKILL.md frontmatter
skills/review/renamed toskills/e2e-test-reviewer/— folder name matches skill name
cypress-debugger— new skill for diagnosing Cypress test failures from mochawesome/JUnit report files- Phase 1: parses
mochawesome.jsonor JUnit XML for failed tests, error messages, duration - Phase 2: classifies each failure into F1–F14 root cause categories
- Phase 3: screenshot and video analysis via
cypress/screenshots/andcypress/videos/ - Phase 4: concrete fix suggestion per failure with P0/P1/P2 severity
- Phase 1: parses
playwright-debuggerGitHub PR integration — given a PR URL, automatically finds the failed CI run and downloads the playwright-report artifact viagh; reuses PR URL from conversation context when user says "failed again"
e2e-test-debuggerrenamed toplaywright-debugger— reflects Playwright-only scopeskills/debug/renamed toskills/playwright-debug/— consistent naming with newcypress-debug/playwright-debuggertitle updated to "Playwright Failed Test Debugger" — removes ambiguous "E2E" prefix- Repository renamed to
e2e-skills— shorter, cleaner package name - README: restructured with "When to Use" sections, three-skill pipeline, Compatibility section
- Installation: fixed install command typo (
e2e-test-skill→e2e-skills) - Skill descriptions disambiguated:
e2e-test-revieweris static code analysis;playwright-debuggerandcypress-debuggerare runtime failure diagnosis — prevents incorrect skill selection on "flaky tests" queries marketplace.json: addedci,ci-failure,playwright-debugger,cypress-debugger,test-review,test-audit,regressionkeywords
- Skill names renamed to
e2e-test-reviewerande2e-test-debugger— shorter, more intuitive
- Plugin renamed from
e2e-test-reviewertoe2e-test-skill— reflects expanded scope - Mono-skill structure: skills now live in
skills/review/andskills/debug/subdirectories - Skill names updated to
e2e-test-skill-reviewande2e-test-skill-debug - plugin.json skills array updated to
["./skills/review", "./skills/debug"]
e2e-test-skill-debug— new skill for diagnosing Playwright test failures- Phase 1: parses
results.jsonto extract failed tests, error messages, duration - Phase 2: classifies each failure into F1–F14 root cause categories using error signals
- Phase 3: trace analysis via direct
trace.zipparsing (unzip -p | node) — no extra dependencies; covers failed steps, DOM snapshot, network failures, JS console errors - Phase 4: concrete fix suggestion per failure with P0/P1/P2 severity
- Cross-references
e2e-test-skill-reviewpattern numbers (e.g. F2 → #14 POM Drift) - Temporary
page.screenshot()+ browser agent pattern for cases trace alone can't resolve
- Phase 1: parses
- #11b Subject-Inversion (P1): Detects
expect([expected]).toContain(actual)where expected values are placed as the subject instead of the actual value — produces confusing failure messages like "Expected [200, 202] to contain 204"
Discovered during n8n (177k stars) review.
- #5 Boolean Trap: No longer flags
toBeTruthy()on actual boolean return values (response.ok(),isVisible(),isChecked(), etc.). Only flags when used on non-boolean objects (Locator, ElementHandle) that are always truthy — the real bug. Phase 1 grep now excludes known boolean-returning methods viagrep -v. - Quick Reference updated to clarify boolean trap scope
Validated against 5 major open-source projects (Cal.com, Ghost, Grafana, Documenso, Appsmith). Documenso had 230+ expect(response.ok()).toBeTruthy() instances — these are working assertions on actual booleans, not bugs. Previous versions would have flagged all of them as P1.
- SKILL.md moved to repo root — eliminates redundant
e2e-test-reviewer/skills/e2e-test-reviewer/nesting when installed as a plugin - plugin.json skills path updated from
./skills/e2e-test-reviewerto./
- P0/P1/P2 severity classification for all 14 checks — P0 (must fix), P1 (should fix), P2 (nice to fix)
- Phase 3: Coverage Gap Analysis — identifies missing error paths, edge cases, accessibility, and auth boundary tests after review
- Review Summary table in output format — aggregates findings by severity with affected file list
- Flaky sub-patterns in #13: network dependency without mock (13b), animation race conditions (13c)
- Procedure + Common patterns for checks #1 (Name-Assertion), #2 (Missing Then), #9 (Duplicate Scenarios) — matching depth of #14 YAGNI
- Network mock grep in Phase 1 — detects
page.goto/cy.visitwithout nearby route/intercept setup
- #13 renamed from "Flaky Selectors" to "Flaky Patterns" — now covers positional selectors, network mocks, and animation timing
- Tier headers updated to show severity range (P0/P1 for Tier 1, P1/P2 for Tier 2)
- Severity guide changed from HIGH/MEDIUM/LOW to P0/P1/P2 with clearer definitions
- Quick Reference table replaced Tier column with Sev column
- #14 YAGNI in POM: Clarified scope of "2+ specs" rule — it applies when creating new shared utils, not as grounds for deleting existing util files/classes that are actively imported and used. The rule now explicitly states: only flag unused individual members within util files, do not delete entire files that specs depend on.
- Phase 1: Automated Grep Checks — deterministic pattern detection via
grepbefore LLM analysis. Covers checks #3 (Error Swallowing), #4 (Always-Passing), #5 (Boolean Trap), #6 (Conditional Bypass), #7 (Raw DOM), #12 (Hard-coded Timeout), andpage.isClosed()guards - Phase 2: LLM-only Checks — LLM now only performs subjective checks (#1, #2, #8-11, #13, #14) that require semantic interpretation
[grep-detectable]/[LLM-only]tags on each checklist item for quick classification- Phase column in Quick Reference table to indicate grep vs LLM detection
- Suppression mechanism —
// JUSTIFIED: [reason]inline comment excludes lines from Phase 1 grep results - Installer CLI method in README (historical; current docs use one-command global installs with
npx skills add --skill '*' -g -a claude-code -a codex -a opencode, include--agent '*'for every supported agent, and showskills addwhen the CLI is already installed)
- Review workflow is now two-phase: mechanical grep first, LLM second — reduces token usage and ensures deterministic results for pattern-based checks
- Framework-agnostic grep patterns — Phase 1 covered Playwright (
toBeGreaterThanOrEqual,waitForTimeout), Cypress (should('be.gte'),cy.wait()), and an additional automation target in a single command using-Eextended regex (historical; current supported automation scope is Playwright and Cypress)
- Raw DOM Queries check (#7, Tier 1): Detects
document.querySelector*/getElementByIdinsideevaluate()/waitForFunction()that bypass framework element APIs - Hard-coded Timeouts check (#12, Tier 2): Detects
waitForTimeout()/cy.wait(ms)and magic timeout numbers without explanation - POM error swallowing detection in #3:
.catch(() => {})/.catch(() => false)on POM wait/assertion methods - POM boolean trap detection in #5: Methods returning
Promise<boolean>instead of exposing element handles - Cross-file duplicate detection in #9: Cross-check similar test names across different spec files
- Severity guide in output format: HIGH / MEDIUM / LOW classification for findings
- Quick Reference table restored (14 items, compressed)
- Skip protection rule:
test.skip()with a reason comment or string is intentional — do not flag
- Framework-agnostic: Principles were documented as framework-independent with specific guidance for Playwright, Cypress, and another automation target where they differ (#5, #7, #12) (historical; current supported automation scope is Playwright and Cypress)
- POM files in scope: Review checklist now explicitly covers Page Object Model files, not just spec files
- Renumbered all checks (1-7 Tier 1, 8-14 Tier 2) to accommodate new items
- Updated README pattern tables to 14 items with framework-agnostic examples
- Updated plugin.json and marketplace.json descriptions and keywords
- Playwright-only assumptions from rules and examples
- Tier structure: Checks split into Tier 1 (high-impact bugs, always check) and Tier 2 (quality improvements, check when time permits)
- Flaky Selectors check (#11): Detects positional selectors (
nth(),first()) and unstable raw text matching that break across environments or i18n changes
- Merged "Conditional Assertions" and "Conditional Skip" into a single Conditional Bypass check (#6) — both are symptoms of the same root cause
- Trimmed examples for obvious patterns (Always-Passing, Boolean Trap) — kept only BAD examples where the anti-pattern is self-evident
- Renumbered all checks to reflect tier ordering (1-6 Tier 1, 7-12 Tier 2)
- Updated README pattern tables to match new tier structure
- Quick Reference table (redundant with detailed check sections)
- Verification section (too generic to be useful)
- "When to Use" section (redundant with frontmatter description)
- ~48% reduction in SKILL.md line count while preserving all detection rules
- Initial release with 12-point checklist
- Detects: name-assertion mismatch, missing Then, render-only tests, duplicate scenarios, misleading names, over-broad assertions, always-passing assertions, conditional assertions, error swallowing, boolean traps, conditional skips, YAGNI in Page Objects
- Framework-agnostic design with Playwright examples
- YAGNI audit procedure with classification table output
- Task-based output format with concrete code fixes