Safe PR — Evidence-Rich Pull Request (Phase 4)
Open a pull request a senior engineer can approve with confidence, because the evidence is right there. This is the final phase of a slice: build (red-green-refactor) → CI (tdd-ci) → PR. Treat opening a PR as an outward-facing action — be careful and confirm before pushing.
The harness builds web and non-web apps, so evidence comes in two shapes:
- Web slice → Playwright screenshots + a recording of the passing acceptance run (plus the HTML report).
- Non-web slice (CLI / HTTP API / service) → terminal transcripts: the test-run output and a real endpoint invocation (CLI stdout, or an HTTP request+response). Transcripts stand in for screenshots/recordings.
Bundled resources:
assets/pr-body-template.md— the PR description structure (with an<!-- EVIDENCE -->marker the script fills).scripts/collect-evidence.mjs— collects evidence into the committed evidence folder and generates the PR body. For web slices it embeds screenshots and links the recording/report; for non-web slices (--type cli|api|servicewith--transcript) it embeds the transcripts as code blocks. Modality is auto-detected (Playwright artifacts → web) or forced with--type. It runs in two phases —--copy-only(copy + secret-scan) before you commit the evidence, then--body-only(build the body, pinned to the evidence commit) after — so embedded links resolve. It auto-detects repo visibility and, on private repos, renders screenshots as clickable blob links rather than inline images that wouldn't render. Run with Node.
Requires the GitHub CLI (
gh) authenticated, and a GitHub remote. Confirm both early (gh auth status,git remote -v).
Preconditions (verify, don't assume)
- The slice is green. Re-run the full suite (unit + e2e) and confirm it passes. Never open a PR on red. If anything fails, stop and return to
red-green-refactor. - CI workflow exists.
.github/workflows/runs the unit + e2e tests (fromtdd-ci). If missing, runtdd-cifirst. - On the slice's feature branch.
feat/<feature-slug>/<NN>-<slice-slug>. If you're onmainor another branch, create/switch to the slice branch. Never develop or PR a slice frommain. - Working tree committed. All slice work is committed in small, green-only commits.
Procedure
Produce the evidence by actually running the tests. Show real run output, not claims. Always capture the unit-test summary (counts, pass/fail). Then, depending on the slice's boundary:
- Web slice: run the e2e suite with Playwright configured to capture screenshots + video (
video: 'on') + HTML report (see thered-green-refactortest-strategy reference). The PR must include a screenshot and a recording of the passing acceptance run — if none was produced, re-run with video on before continuing. - Non-web slice (CLI / API / service): capture two transcripts to files — (a) the test-run output (e.g.
npm test/pytest -q), and (b) a real invocation through the boundary (the CLI run with its stdout + exit code, or the HTTP request + response). Redirect them to files so the collector can attach them, e.g.npm test > test-run.txt 2>&1andnode src/cli.js 2 3 > cli-demo.txt 2>&1.
Run tests from the slice's project directory if the app lives in a subfolder (see the plan's Project directory field).
- Web slice: run the e2e suite with Playwright configured to capture screenshots + video (
Copy the evidence in (
--copy-only). Run the collector from the repo root in copy-only mode — it copies the artifacts intodocs/tdd-evidence/<feature>/<NN-slice>/and scans them for secrets, but does not write the PR body yet. (Two phases matter: the body pins URLs to the current commit, so the evidence must be committed before the body is generated — otherwise the links point at a commit that doesn't contain the files. See step 5.) Plain Node, so the same invocation works on Windows/macOS/Linux:Web slice — point
--report-dir/--results-dirat the project dir if the app is in a subfolder (the collector resolves them from the repo root):node "${CLAUDE_SKILL_DIR}/scripts/collect-evidence.mjs" --feature <feature-slug> --slice <NN-slice-slug> --report-dir <project-dir>/playwright-report --results-dir <project-dir>/test-results --copy-onlyNon-web slice (
--type cli|api|service, one or more--transcript):node "${CLAUDE_SKILL_DIR}/scripts/collect-evidence.mjs" --feature <feature-slug> --slice <NN-slice-slug> --type cli --transcript test-run.txt --transcript cli-demo.txt --copy-onlyBy default it drops raw traces (
*.zip) and HAR files (which often carry auth tokens); pass--include-tracesonly if you need them and have checked them.Review the evidence for secrets — BEFORE committing anything. Read the collector's output: if it reports
SECRETS SUSPECTED, open the named files and remove or redact any tokens, cookies, passwords, or env dumps (Playwright traces, HAR captures, and HTML reports are the usual culprits). This evidence is about to be committed and pushed and cannot be un-published once in history. Do not proceed until it is clean. Keep the project's rawtest-results/andplaywright-report/out of git via.gitignore— but anchor those patterns to the project dir (e.g.sandbox/web-adder/test-results/) or add!docs/tdd-evidence/**, so the unanchored patterns don't also ignore the committed copies underdocs/tdd-evidence/(a silent-empty-evidence trap).Commit the cleaned evidence. Two statements (Windows PowerShell does not support
&&):git add docs/tdd-evidence/<feature>/<NN-slice>/ git commit -m "docs(<feature>): test evidence [slice NN]"Confirm it actually committed (
git show --stat HEAD) — if the folder is empty, your.gitignoreswallowed it (step 3).Generate the PR body (
--body-only), now pinned to the evidence commit. Re-run the collector in body-only mode — it does not re-copy; it buildsPR_BODY.mdfrom the committed evidence, pinning every link to the currentHEAD(the commit you just made), so the links resolve:node "${CLAUDE_SKILL_DIR}/scripts/collect-evidence.mjs" --feature <feature-slug> --slice <NN-slice-slug> [--type cli] --body-only --template "${CLAUDE_SKILL_DIR}/assets/pr-body-template.md" --out PR_BODY.mdFor a web slice it embeds screenshots and links the recording/report; for a non-web slice it embeds each transcript as a fenced code block (capped via
--max-transcript-lines, default 200) and links the full file. Private repos: the collector auto-detects visibility (gh repo view) and, on a private repo, renders screenshots as clickable blob links instead of inline![]()embeds — becauseraw.githubusercontent.comdoesn't render for private repos. Override with--public/--privateif detection is wrong. Then openPR_BODY.mdand fill the remaining<placeholders>from the slice plan (description, what changed, how to review, the unit-test summary, risk notes, plan path); tick the checklist items that hold; be honest about anything partial. The PR body itself is git-ignored (regenerable), so it isn't committed.Confirm, then push. Show the user the PR title, the body, and the branch you will push, and get explicit confirmation (this is outward-facing). Then push:
git push -u origin feat/<feature-slug>/<NN-slice-slug>This also triggers the CI workflow. Never force-push.
Open the PR into main (single line so it pastes cleanly in any shell):
gh pr create --base main --head feat/<feature-slug>/<NN-slice-slug> --title "feat(<feature-slug>): <slice goal> [slice NN]" --body-file PR_BODY.mdNever target a base other than
mainunless the user explicitly asks.Record the result. Put the PR URL into the slice plan's status log and the feature README's slice table, and tick the Definition-of-Done. Optionally note that CI is now running on the PR and they can require those checks before merge (see
tdd-ci).Report to the user (required — this is how branches stay tidy). End the run with an explicit, scannable summary so nothing is silently left behind:
- New branch created: name the branch you cut and pushed (
feat/<feature-slug>/<NN-slice-slug>), and that it now exists both locally and onorigin. - PR opened: the title and URL, into
main. - Cleanup reminder: state plainly that this slice left a feature branch behind, and that once the PR is merged or closed they should run
safe-cleanupto retire the now-stale local branch (it reports first and confirms before deleting, and records recovery SHAs). This matters for repo hygiene — every slice adds a branch, so they accumulate fast.
Surface this every time you open a PR, even mid-pipeline under
tdd-harness— a one-time reminder at the end of a multi-slice session is easy to miss. Keep it short, but never skip the branch name or the cleanup nudge.- New branch created: name the branch you cut and pushed (
Safety rules (non-negotiable)
- Confirm before any push or
gh pr create— these are outward-facing and visible to the team. - Never force-push, never rewrite shared history, never push directly to
main. - Base is always
mainunless the user says otherwise. - Open the PR only on green with evidence attached. A PR without real test evidence defeats the purpose.
- Don't include secrets, tokens, or large binaries beyond the necessary evidence. Keep videos short; rely on
'retain-on-failure'if artifacts get heavy.
What the reviewer gets
A PR whose description proves the slice works: the behaviour described in plain language, the failing-then-passing acceptance test, the unit-test summary, a checklist, a link back to the execution plan, and modality-appropriate evidence of it working — embedded screenshots plus a linked recording and HTML report for a web slice, or the embedded test-run and real-invocation transcripts for a CLI/API/service slice. That is "everything a developer needs to review it and know the desired feature was built."