catstack-local overlay for the draft-pr skill: adds extra gates before publishing a PR that touches engine/hooks, engine|corpus|product skills -- hook e2e coverage, three-harness install, and ecosystem boundaries. Loaded automatically instead of draft-pr for PRs in this repo.
Installs into .claude/skills of the current project.
Are you the author of Make Pr?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/edbertchan-make-pr)
---
name: make-pr
description: >
catstack-local overlay for the draft-pr skill: adds extra gates before
publishing a PR that touches engine/hooks, engine|corpus|product skills --
hook e2e coverage, three-harness install, and ecosystem boundaries.
Loaded automatically instead of draft-pr for PRs in this repo.
---
# make-pr (catstack overlay)
Use `draft-pr`'s schema, confirmation rules, and diff-atomicity gate exactly
as documented -- this file only adds repo-local rules on top.
## Review unit from path
Declare exactly one review unit that matches the dominant changed paths:
| Paths | Review Unit |
| --- | --- |
| `engine/` (hooks, engine skills, scripts) | `engine-runtime` |
| `corpus/skills/` | `corpus-lesson` |
| `product/skills/` | `product-skill` |
One review unit per PR. Repo-root `tests/` and the `docs/ecosystem.md`
inventory row ride along; every other doc, including the root `README.md`,
is its own `docs` unit. `drafter.config.json` is the only source of these
rules, and preflight reads it rather than a copy. A change whose
code reads another unit's output is two stacked PRs, producer first -- not
one PR with the coupling explained in Slice Rationale.
## Stack on top, or overwrite the branch
A PR that already exists gets one of two treatments, decided from its current
diff against its base -- never from how much work went into it, and never by
asking the user which they would prefer:
- **Stack on top** when the claim still holds and what is being added is
small: a fix to code the PR already ships, the base merged in, or docs for
the same claim. Push another commit to the same branch, or open a new PR
based on it.
- **Overwrite the branch** when the diff no longer matches the claim. Any one
of these is enough:
- it carries work already on the base
- it mixes review units
- it ships a second claim
Save the old head as `backup/pr<number>-<short-sha>`, say where it went,
then `git push --force-with-lease=<branch>:<old-sha>` and rewrite the title
and body to the slice that remains.
The record that gets read later is the PR's final diff and body, so a branch
whose diff stopped matching its title is rewritten, not explained in Slice
Rationale. `--force-with-lease` and the backup branch are the floor, not a
precaution to skip: a lease failure means another session moved that branch,
so re-read it and decide again instead of forcing past it.
## Order of operations
Run these in order. Do not draft a PR body, then discover the review unit
from a mixed working tree.
1. **Choose one publish path before the first push.** Pick the repo's
merge-queue / stack tool when the repo uses one, otherwise the host's
ordinary open-PR command. Keep that choice for the whole slice — do not
push with one tool and open the PR with another. In this repo that means
`mergify stack push` (then refresh title/body); elsewhere it is usually
`gh pr create` / `gh pr edit`.
2. **Classify the slice before drafting the body.** Run commit-scoped
preflight in dry-run mode first; that is enough to learn the unit and
the gate list:
```sh
python3 engine/skills/make-pr/scripts/preflight.py --base origin/main --commit-only --dry-run
```
3. Draft the description from the declared Review Unit.
4. **Check the draft against the same commit-scoped slice** (pass the
description file into preflight), then push and publish only via the
path chosen in step 1.
## Preflight (run first — before the body)
```sh
python3 engine/skills/make-pr/scripts/preflight.py --base origin/main --commit-only --dry-run
python3 engine/skills/make-pr/scripts/preflight.py --base origin/main --commit-only --body-file <draft-description.md>
```
Default classification is **commit-scoped**: paths from
`git diff --name-only <merge-base>`. Untracked files are ignored so a
parking-lot file cannot expand or mix the review unit. Use `--staged` to
add the index; use `--include-untracked` only when you mean to classify
those files (it fails when untracked count is high — park them or pass
`--paths`). Prefer `--commit-only` (or the default) for Invoker and PR
units.
Claims about the repository's own past (how long, how many, who wrote it,
never/always, first/last) are banned from PR descriptions. The premises a
change depends on are proven in the session, so the description never needs
one. `--body-file` sends the draft to the background judge with the
`pr-description-history-claims` dictionary; a hit fails preflight, and so does
a draft no judge could read. Delete the sentence rather than adding evidence
beside it.
A PR that adds rule lines to skill prose also runs
`engine/skills/make-pr/scripts/rule_scope_check.py`, which sends each added
rule to the judge with the `incident-scoped-rule` dictionary: a rule whose main
sentence is one named tool, label, command or error message fails preflight, and
so does a rule no judge could read. Rewrite the lead as the general lesson and
keep the name as an example. It runs beside
`scripts/ci/check_no_dated_provenance.py`, which reads shapes rather than
meaning and stays as it is.
It reads the commit-scoped diff, prints the review unit from the table above,
fails on any mix of review units (printing one `split` line per unit), and
runs every gate below for the hooks and skills actually touched. Paste its
output into the PR's Test Plan. The sections below describe what it runs; you
only run them by hand if it fails.
A failing preflight or `validate-pr-body.mjs` is a stop, in headless and
merge-gate runs too: do not emit a publishable body that says "needs
splitting" or "not ready to merge". Output the `split` lines and the failing
check's message instead, so whatever publishes gets nothing to publish
(fail-safe defaults, Saltzer & Schroeder 1975,
https://web.mit.edu/Saltzer/www/publications/protection/Basic.html).
## Extra gate: hook e2e coverage
Before publishing any PR that touches `engine/hooks/<name>/`:
```sh
python3 scripts/ci/check_hook_test_coverage.py engine/hooks/<name>
```
Must pass for every touched hook that has a `detect.py`. If it fails, add
the missing positive test (reproduces the bad case the hook exists to
catch, asserts it fires) or negative test (a clean case, asserts it stays
silent) first -- do not publish around the gate. This is in addition to,
not instead of, `draft-pr`'s own diff-atomicity gate.
## Extra gate: skills three-harness install
Before publishing any PR that adds or changes a skill under
`engine/skills/`, `corpus/skills/`, or `product/skills/`:
```sh
python3 scripts/ci/check_skill_test_coverage.py --base <actual-pr-base> --head HEAD
python3 scripts/ci/check_skills_three_harnesses.py
python3 scripts/ci/check_ecosystem_boundaries.py
```
All must pass. The coverage command is diff-aware: each changed skill must
change its own colocated or explicitly mapped test in that direct PR slice;
tests inherited from a parent or child stack slice do not count. A skill MUST install to Claude, Cursor, and Codex (unless listed
in `CLAUDE_ONLY_SKILLS`). Do not publish a skill that only documents a
single harness. Do not land a skill in the wrong bucket.
`engine/hooks/auto-pr`'s delivered instruction already tells the agent to run these
checks as part of its auto-triggered flow; a human asking for a PR
interactively should run them too before publishing.
## Extra gate: fixture vs live on ship closeout
On stack/PR closeout for workers or integrations whose Goal includes live
side effects (Linear, deploy, live mine, external APIs):
- Require an explicit **fixture vs live** split in the Test Plan and Summary.
- Either include live evidence from the same turn, or prefix unsettled live
claims with `{{CAT-UNVERIFIED: <claim> -- cannot verify: <reason>}}`.
- Visual Proof that only shows UI registration must not be framed as product
e2e of the live side effect.
Follow `prove-it-ship-gate` and Invoker or locally installed `prove-it`.