ci(studio): require before and after captures on studio and player prs - #4131
Conversation
somanshreddy
left a comment
There was a problem hiding this comment.
Reviewed at 0798a5a7. Genuinely well-built check — the design and the test suite are both strong. One real red gate (Fallow), and I traced its actual cause because it changes the fix.
Verified sound
- The gate is designed to be safely required. The workflow runs on every PR with no path filter, and the script no-ops when neither
packages/studionorpackages/playerchanged (evaluatereturnsokonfiles.length === 0) — so it can become a required check without blocking unrelated PRs. editedre-triggers it.on: pull_request: types: [..., edited]means adding the captures to the body re-runs the check — it sidesteps the "body edits don't re-trigger, you must push" evidence-at-push-time trap that bites the HFI evidence gate.- It fails loud and reads the right source.
assert-free butprocess.exit(1)on problems /exit(2)when the diff can't be computed (no silent skip);--head $HEAD_SHA+--base origin/$BASE_REFwithfetch-depth: 0diffs correctly regardless of the checked-out merge commit; body comes fromgithub.event.pull_request.body(current on every event). - The bypass vectors are actually closed and tested.
normalize()strips HTML comments + fenced code and converts setext→ATX before matching, and the 26 tests hit the real exported functions (not mocks) across the evasions that matter: media in an HTML comment, in a code fence, in the wrong section, one image shared via a nested### After, setext headings, qualifier headings (## After: onpasses,## After the mergefails), and## Before-and-aftercorrectly not matchingBefore. Binary files spend the full no-visible-change budget. It's wired into CI (package.jsonaddscheck-pr-captures.test.mjstotest:scripts), so it runs and can fail. - Security posture is clean: PR body is regex-matched only,
execFileSyncruns git with fixed args (no shell), workflow perms arecontents: read+pull-requests: read.
Blocking — Fallow audit is RED, and the cause is coverage visibility, not complexity
The Fallow audit check fails with 4 fallow/high-crap-score findings on scripts/check-pr-captures.mjs (evaluate:112 flagged critical, CRAP 156). But the raw cyclomatic complexity of all four is under the threshold — <arrow>:42 = 5, parseNumstat:79 = 6, evaluate:112 = 12, main:137 = 6, threshold 30. CRAP = comp²·(1−coverage)³ + comp, so those scores (30 / 42 / 156 / 42) are exactly the coverage = 0 values; at full coverage they'd be 5 / 6 / 12 / 6, all passing. So fallow is measuring ~0 coverage on this script even though the 26-test suite exercises evaluate/parseNumstat/parseSections/hasMedia heavily — its node --test coverage isn't reaching the audit.
So the fix isn't "refactor evaluate" — it's to get fallow to count check-pr-captures.test.mjs's coverage. If that's not wired for scripts/*.test.mjs in this repo (the sibling check-docs-catalog.mjs carries a // fallow-ignore-next-line complexity, which suggests fallow doesn't see these node:test suites), a scoped suppression matching that precedent is the pragmatic path — with the caveat that main() (the CLI wrapper) is the one function that genuinely isn't unit-covered, so it's the only one where the score reflects real risk rather than a measurement gap. Either way the gate must go green before a stamp.
Also pending, not yet green: Build, Perf: *, CLI smoke, Analyze — normal long-runners, just not landed.
Nit (non-blocking)
When a ## No visible change section is present but doesn't qualify, evaluate appends both the "No visible change" does not apply: … messages and the no "Before"/"After" heading messages. Slightly redundant, but it does tell the author both routes are unsatisfied — leave it if intentional.
Once Fallow is green (coverage counted or a precedent-matching suppression) and the pending checks land, this is a clean approve — the check itself is one of the better-tested gate scripts in this batch.
Codex disabled per workspace cap — manual pass only.
a6207e2 to
96c9497
Compare
|
Independent review findings, and what changed:
|
96c9497 to
a71a0b6
Compare
somanshreddy
left a comment
There was a problem hiding this comment.
Re-checked at a71a0b6e. My hold reason is resolved — narrowing to just the content-validating Test job.
- Fallow audit is green now — the coverage-driven CRAP findings I held on (
evaluate/parseNumstat/etc. flagged only because fallow measured ~0 coverage) are cleared.Skills: manifest in sync/project-native lint + mirror/Test: runtime contractall pass; zerofailure-conclusion checks at this head. - Still pending:
Test(in-progress) — this is the job that runstest:scripts→check-pr-captures.test.mjs, i.e. the PR's own 26 tests. That's the content-validating check for a CI-check PR, so I'll flip to approve once it lands green rather than stamp with it pending. The other pending checks (Analyze,Render on windows,Smoke: global install, studio lanes) are peripheral long-runners.
My substantive review carries: the captures check is well-built — 26 adversarial tests (comment/fence/wrong-section/nested/setext/qualifier bypasses), edited re-trigger, no-op on irrelevant PRs so it can be required, real lint codes. Once Test is green it's a clean approve.
Codex disabled per workspace cap — manual pass only.
a71a0b6 to
08da89d
Compare
somanshreddy
left a comment
There was a problem hiding this comment.
Flipping to APPROVE at 08da89dc. My hold reason (the Fallow audit's coverage-driven CRAP findings) is cleared, and the whole run is now green: Fallow, Preflight, Test (runs the 26-test check-pr-captures.test.mjs), and Tests on windows-latest: studio-core all pass — 0 failure conclusions, 0 pending. The only #4131-specific delta since my last pass is the benign CODEOWNERS entry protecting the check's own files (the rest was #4138 rebased in from main). My review carries: 26 adversarial tests, edited re-trigger, no-op on irrelevant PRs so it can be required, real lint codes. Approving.
Codex disabled per workspace cap — manual pass only.
What changes
A required-check candidate: any PR that changes
packages/studioorpackages/playermust show the behaviour in its description. The body needs a Before and an After section, each with at least one attached image or video (auser-attachmentsasset, or an image/video link). Only the PR description counts; captures in comments are not read. The failure text names each missing piece and prints the exactgh pr edit --attachcommand.A PR may instead add a
## No visible changesection, accepted only when the diff under those two packages is under 20 changed lines and touches no.tsx,.cssor.htmlfile; the failure says which condition did not hold.Check name for the ruleset:
Studio and player captures(workflowPR captures). It runs on every PR tomain, with no path filter, so it can be required; the script passes when neither package changed. It re-runs when the description is edited.Shape
scripts/check-pr-captures.mjs: pureparseSections,hasMedia,parseNumstat,evaluate; the diff is read with--numstat -z --no-renames; an unreachable base exits 2.ci.yml; body, PR number, base ref and head sha reach the script throughenv, never interpolated intorun. Tests are added totest:scripts.Gate ownership
.github/CODEOWNERSlists the script, its test and the workflow, so a PR cannot change its own gate without a code owner. Enforcing it needs "Require review from Code Owners" in the ruleset.Open
.test.tsxor.spec.tsx-only change now counts as non-visual forNo visible change(component files still do not).or<img src>) counts without a file extension; a plain link to a page still does not.github.com) and path, and HTML comments are stripped until none remain, an unclosed one hiding the rest of the body as GitHub does.What I measured
node --test scripts/check-pr-captures.test.mjs: 29 pass. Run against a real Studio PR description and diff it exits 0; against an empty body it fails with the attach command. A copy of the script under a path with a space still runs (entry guard).