Cross-cutting contract for the test-readiness pipeline. Specifies the GitHub repository configuration that makes "tests pass" structurally meaningful instead of advisory — branch protection requiring CI to pass, required secrets, environments, deploy hooks, and PR templates. Referenced by `test-design` (Phase 2 emits the repo-config plan) and `test-roadmap` (Phase 4 auto-pairs workflow tasks with branch-protection tasks). Without these, the verification commands in each task body are honor-sy...
Scanned 9/2/2026
Install to Claude Code
npx -y skills add app-vitals/shipwright --skill repo-config --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Repo Config?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/app-vitals-repo-config)More formats (shields.io, HTML) on the badges page.
---
name: repo-config
description: >
Cross-cutting contract for the test-readiness pipeline. Specifies the GitHub repository configuration that makes "tests pass" structurally meaningful instead of advisory — branch protection requiring CI to pass, required secrets, environments, deploy hooks, and PR templates. Referenced by `test-design` (Phase 2 emits the repo-config plan) and `test-roadmap` (Phase 4 auto-pairs workflow tasks with branch-protection tasks). Without these, the verification commands in each task body are honor-system; with them, the workflow is the gate.
---
# repo-config skill
## Purpose
Capture the repository-level configuration that turns CI into an actual gate. The audit's verification commands are only meaningful if `main` cannot be merged unless they pass.
## When invoked
- By `test-design` (Phase 2) — emits the **Repo Configuration** section of the blueprint
- By `test-roadmap` (Phase 4) — applies the **pairing rule** (every CI-workflow task gets a paired branch-protection task)
- By `test-fix` (Phase 5) — the task's `acceptanceCriteria` array is the task-store equivalent of a Closing Checklist
## The repo-config plan
A complete repo-config plan covers four concerns:
### 1. Branch protection
For each protected branch (default: `main`):
This is the standard recommendation for every shipwright repo — one flat default, not a
choice between options. Shipwright repos are moving toward `shipwright-deploy` (agent
merges via `gh pr merge --squash`, deferring to native branch protection with the merging
account configured as a ruleset `bypass_actor`) across the board, so recommend the settings
below regardless of where a given repo currently is in that rollout.
- **Required PR** — no direct pushes.
- **Required status checks** — every layer-relevant CI job from `test.yml` (lint, per-service unit, per-service integration, smoke, E2E). This is the real gate: CI must be green on the branch that lands. `deploy.md` now defers to this natively rather than bypassing it outright, so it's a live merge-gate for the deploy pipeline too, not just a signal for merges that skip `deploy.md` (e.g. a human merging via the GitHub UI) — though a `bypass_actor` merging account can still push past it if a status check regresses after the pre-flight check ran.
- **Require conversation resolution before merging** — **no.** Not worth recommending: `check-patch.ts`'s `hasUnaddressedFindings()` only checks for unresolved threads when a qualifying review exists at the *current* HEAD commit — if a later commit lands without a fresh qualifying review at that commit (e.g. a CI-only fix), stale unresolved threads from an earlier review are never revisited, and nothing in the pipeline resolves them. The merging account is configured as a ruleset `bypass_actor`, which bypasses this setting regardless of whether `deploy.md` force-merges or defers natively, so the theoretical "free safety net for manual merges" benefit doesn't hold up against the risk of a PR getting stuck with orphaned unresolved threads no automated process will ever clean up. Not worth the effort to fix the gap for a benefit this narrow.
- **Require branches to be up to date before merging** — **no.** The merging account's `bypass_actor` status on the ruleset bypasses this regardless of whether `deploy.md`'s Step 4b merge goes through natively or hits the branch-protection-block backstop. It also has a concrete, independent cost: GitHub's strict status checks go stale whenever the base branch moves, and with multiple agents merging PRs in parallel, every merge into main invalidates the "up to date" status of every other open PR — forcing a rebase and a full CI re-run on each sibling PR just to become mergeable again. Real Action-minutes burned for no benefit, on top of being redundant with the required-status-check gate above.
- **Required approving reviews** — **0.** GitHub rejects self-APPROVE via the API, so `review.md` Step 10 force-downgrades an agent's review of its own PR to `COMMENT` with a `Verdict: APPROVE` marker — a required-review count can never be satisfied by the pipeline itself. `deploy.md` Step 4b now defers to this setting natively for identities that are not a ruleset `bypass_actor`, treating a missing required review as a branch-protection-block: the PR is marked `blocked` rather than force-merged, and a human must add a real approval or configure the identity as a `bypass_actor`.
- **Enforce on admins** — **no.** The merging account still relies on being a ruleset `bypass_actor` to merge self-reviewed PRs — `deploy.md` no longer force-bypasses branch protection outright, but the deploy model still depends on that bypass existing for the common case. `enforce_admins: true` would either block every automated merge for accounts without a real second approval, or require the merging account to be a permanent `bypass_actor` anyway, which is enforcement in name only.
- **Canary status check is NOT included** — canary runs post-merge; gates promotion staging → prod, not merge to main
### 2. Required secrets and environments
For each external integration named in the inventory, list the GitHub secret needed to authenticate against it in CI and canary mode. For each deployment target, list the GitHub Environment.
Minimum set for the test pipeline:
- `TEST_CANARY_API_KEY` — scoped canary auth token
- `TEST_TARGET_URL` per environment (often set in the workflow, not as a secret)
- Per-external: `<SERVICE>_API_KEY` (used only if recording new fixtures)
- Per-environment GitHub Environments (`staging`, `production`) with required reviewers if promotion is gated
### 3. Deploy hooks
- Post-deploy workflow trigger — typically `workflow_run` on the deploy workflow's `completed` event filtered to success
- Canary workflow uses the deployed env's `TEST_TARGET_URL`
- Promotion workflow gated on canary success
See [`references/deploy-canary-promote.md`](./references/deploy-canary-promote.md) for a full reference contract covering the three critical wiring details: non-empty-tag guard, skipped-job-counts-as-success hole, and staging-vs-prod `TEST_TARGET_URL`.
### 4. PR conventions
- **Closing PR must reference the issue** via `Closes #N` or `Fixes #N` in the body
- **Verification output** — the PR description must include the output of the issue's `Verification command` (or a CI run link that shows the same)
- **Acceptance-criteria checkboxes** — all checked before merge
A PR template (`.github/pull_request_template.md`) can enforce the verification-output convention via a checklist.
### 5. Recorded-fixture maintenance loop (deferred)
Recorded fixture doubles (msw handlers, nock recordings, hand-authored JSON) capture the shape of third-party API responses at a point in time. Without scheduled re-recording, recorded fixtures go stale silently — the test suite stays green while the live integration is broken. A maintenance loop catches this before production does.
**Status: deferred — not implemented.** The auto-recording GitHub Actions pattern below is a target design, not built tooling. Do not wire it up until the manual, hand-authored-fixture workflow is running and tuned.
A repo can permanently opt out of this pattern via a `## Recorded-fixture automation`
declaration in its root `CLAUDE.md` (same mechanism as `## Deploy model` — see
`test-roadmap/SKILL.md` Process step 1 and step 5). When declared `not planned`,
`test-roadmap` emits zero recorded-fixture-refresh tasks regardless of what the migration
phase flags as a gap — do not treat "deferred" in this doc as "eventually mandatory" for
a repo that has explicitly opted out.
See the [full recorded-fixture maintenance loop pattern](#recorded-fixture-maintenance-loop-deferred) below for implementation details (deferred).
## The pairing rule (Phase 4 — test-roadmap)
When generating the task list, every task that creates or modifies a CI workflow file (e.g., `.github/workflows/test.yml`) MUST be paired with a task that enables the corresponding branch-protection requirement.
Pattern:
| Workflow task | Paired branch-protection task |
|---|---|
| Create / update `test.yml` with required jobs | Enable branch protection on `main` requiring those jobs |
| Add canary workflow | Wire as deploy-gate (NOT as merge-gate) |
| Add deploy workflow | Add `staging` / `production` GitHub Environments with required reviewers |
The branch-protection task `depends_on` the workflow task. Land workflow first; verify green on main for a few days; then enable protection. Sequencing is in this order to avoid the chicken-and-egg block.
## Coverage-check pairing (3-stage lifecycle)
The coverage-required-check is a specific instance of the pairing rule above, extending the
general 2-stage pattern (job lands → dependent task enables it as required) to a 3-stage
lifecycle. The general 2-stage table still governs `test.yml`/canary/deploy workflow tasks
unchanged — this section adds a third stage for the coverage gate only, because jumping
straight from "no coverage check" to "hard ≥90% required check" would instantly break every
repo currently below that floor. `test-roadmap` (Phase 4) is the component that detects gap
closure and auto-emits the stage-3 promotion task, mirroring the same role it plays applying
the pairing rule above.
Stages:
| Stage | Trigger | What happens |
|---|---|---|
| 1. Coverage CI job lands | `test.yml` gains a coverage-computing job | No required check yet — signal only |
| 2. Never-decrease required (immediately) | Paired task, same mechanism as the pairing rule above | Branch protection requires a "coverage must not decrease" status check as soon as the coverage CI job lands — `depends_on` the job-lands task, enabled immediately, not deferred |
| 3. Hard ≥90% required (auto-emitted) | `test-roadmap` re-run detects `coverage_gate.line_coverage_pct >= 90` | `test-roadmap` automatically emits a repo-config task promoting the required check from never-decrease to a hard ≥90% gate — no manual step |
**Onboarding skip rule:** a repo already at ≥90% line coverage at onboarding time skips
stage 2 entirely and goes straight to stage 3 — the hard ≥90% gate. Passing through the
never-decrease stage first would be unnecessary when the repo already clears the target
floor; only repos onboarding below 90% need the never-decrease stage as an interim gate
while their roadmap closes the gap.
## The closing checklist (Phase 5 — test-fix)
Every task-store task `test-fix` queues must carry closing-checklist concerns in its
`acceptanceCriteria` array (a flat list of strings — see `test-fix/SKILL.md`'s field-building
section). This converts the verification command from a suggestion into a mergeable gate:
```json
"acceptanceCriteria": [
"All acceptance-criteria items above are satisfied",
"Verification command `{verify}` run; output captured in the closing PR description",
"CI is green on the PR (assuming branch protection is enabled)",
"No source files outside `Files to touch` were modified, or the deviation is justified in the PR description"
]
```
The third item only becomes a hard gate once branch protection is on. Until then, it's still good hygiene.
## What this skill produces in artifacts
### In `test-system.md` (Phase 2 output)
A `## Repo configuration` section with the four concerns filled in for the target repo:
- Branch protection rule definition
- Required secrets list
- Required environments list
- PR template recommendation
### In `test-readiness-plan.md` (Phase 4 output)
A `## Repo configuration tasks` block listed alongside the test tasks. By default these go in **M1** (infrastructure baseline) with `depends_on` pointing at the corresponding workflow task.
### In each task issue body (Phase 5 output)
A `## Closing checklist` section at the bottom.
## Recorded-fixture maintenance loop (deferred)
> **Deferred — not implemented.** This section documents the target auto-recording design (a scheduled GitHub Actions workflow). None of this tooling — the workflow, the environment, the re-record scripting — has been built yet. Defer implementation until the manual, hand-authored-fixture workflow is running and tuned. If a repo has never actually exercised a real live-recording flow (i.e. `RECORD=1`-style capture against a real third-party API with real credentials, not just hand-authored recorded fixture double JSON), that precondition is not met — building the automated loop first, with no proven recording step to automate, is the failure mode this note exists to prevent. A repo in that position should declare `## Recorded-fixture automation` → `not planned` in its `CLAUDE.md` rather than accept a bundled "build recording + automate it" task; see the opt-out note above.
Recorded fixture doubles (msw handlers, nock recordings, hand-authored JSON) capture the shape of third-party API responses at a point in time. Without scheduled re-recording, recorded fixtures go stale silently — the test suite passes while the integration is broken in production. A recorded-fixture maintenance loop catches drift weekly before it becomes a production incident.
### Where the cron lives
A dedicated GitHub Actions workflow `.github/workflows/recorded-fixture-refresh.yml` (not yet created) on a weekly schedule (e.g., `cron: '0 9 * * 1'` — Mondays at 09:00 UTC). The job:
1. Checks out the repo
2. Runs re-record mode for each service that has recorded fixtures (typically `RECORD_MODE=all bun test` or the msw/nock equivalent)
3. Checks `git diff` for any changed fixture files
4. If diff is non-empty: opens a PR titled `chore: refresh recorded fixtures (auto)` via `gh pr create`, with a `git diff --stat` summary and one expanded hunk in the body
5. If diff is empty: exits 0 silently
### What triggers re-recording
| Trigger | When to use |
|---|---|
| Scheduled cron | Weekly (always) |
| Manual `workflow_dispatch` | When a third-party publishes a breaking-change notice |
| Feature-branch CI | Never — recorded fixtures are not re-recorded on PRs (avoids network flake) |
The re-record job authenticates against the real third-party APIs using credentials stored in a dedicated `recorded-fixture-refresh` GitHub Environment. This environment has narrower permissions than `production` and is not shared with integration test runs.
### How diff alerts are surfaced
| Scenario | Outcome |
|---|---|
| No diff | Silent pass — no PR, no noise |
| Diff detected | PR opened, tagged `recorded-fixture-refresh`, body includes diff stat + one expanded hunk |
| API call fails during re-record | Workflow fails; GitHub sends default failure notification to repo watchers |
| Open refresh PR already exists | Skip creation — check for existing open PR with same title before calling `gh pr create` |
**Do not auto-merge.** The diff is a signal, not a safe auto-apply. A human must verify whether the response shape change is a breaking change, a non-breaking evolution, or a transient fluke before merging.
### Repo-config additions
Include in the **Required secrets and environments** section of the `test-system.md` artifact:
- **GitHub Environment:** `recorded-fixture-refresh` — no required reviewers (cron runs unattended), restricted to the `recorded-fixture-refresh.yml` workflow
- **Secrets in this environment:** one per external integration (`STRIPE_TEST_API_KEY`, `SENDGRID_API_KEY`, etc.)
- The `recorded-fixture-refresh` workflow runs on `main` and submits PRs — it is safe to enable before branch protection is configured
Include in the **test-readiness-plan.md** task list (M1):
- One task per external HTTP dependency that has recorded fixtures: "Wire recorded-fixture-refresh cron for `<service>`"
- One task: "Add `recorded-fixture-refresh` GitHub Environment + secrets"
These tasks depend on the same-layer "record fixtures for `<service>`" task, not on branch protection.
## Anti-patterns
- **"Tests pass" without branch protection.** Without enforcement, the audit is a social contract. Engineers WILL bypass it under deadline pressure. Branch protection is the only mechanism that scales beyond one careful reviewer.
- **Required canary checks at merge time.** Canary needs a deployed env; gating merge on canary creates a deadlock. Canary gates *promotion*, not merge.
- **Recommending required-review ≥1 / enforce-admins:true / require-up-to-date / require-conversation-resolution for a shipwright-deploy repo.** The merging account's ruleset `bypass_actor` status (see `deploy.md` Step 4b, which now defers to native branch protection rather than force-bypassing with `--admin`) is the deploy model's real bypass mechanism, not an emergency exemption, and it bypasses all four of these settings wholesale regardless of whether the merge command itself carries a bypass flag. Requiring ≥1 approval doesn't add a review — self-APPROVE is blocked by GitHub's API — "up to date" burns real CI minutes on every sibling PR each time main advances, and conversation resolution can leave a PR permanently stuck (see `check-patch.ts`'s `hasUnaddressedFindings()` gap above) for a benefit (a "safety net" for manual merges that also get bypassed) not worth the maintenance cost. Stricter-looking, not actually stricter.
- **Secrets baked into workflows.** Use GitHub Secrets + Environments. Workflow YAML references `${{ secrets.X }}`, never literal values.
- **No PR template.** Closing checklists in issue bodies help, but PRs without a template forget the verification-output convention within weeks.
- **Committing recorded-fixture diffs directly to main.** The refresh job must always open a PR, never push directly — the diff must be reviewed.
- **Re-recording on every CI run.** Network calls in CI create flake, inflate build times, and burn third-party rate limits. Re-record on schedule only.
- **Sharing recorded-fixture-refresh credentials with CI integration tests.** Integration tests should use read-only scoped tokens. Re-record credentials are broader — keep them scoped to the `recorded-fixture-refresh` environment.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!