docs: correct the version-bump gate and branch-refresh documentation
ADR-0022 and gates.md now state the merge-base baseline, the fail-closed cases, the PyYAML requirement and the multi-ref push gap (shared with check-release-needed); the new hook gets its own gates.md group. Both ADR additions follow each file's amendment format. README and AGENTS.md now say to discard a refreshed apm.lock.yaml on a feature branch, and that an .apm/ edit is live only once it is on the remote's main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,14 @@ blocked on cutting a premature tag — but it means `--hook-stage pre-push --all
|
||||
rehearsal of 8 hooks and a skip of the ninth. The script's own header records the same gap for
|
||||
a PR merged through Gitea's merge button, where no local push happens at all.
|
||||
|
||||
A real push has a gap of its own. When one `git push` carries several refs
|
||||
(`git push origin a b`, `git push --all`), pre-commit runs the pre-push stage once, for the first
|
||||
ref that has new commits: `_pre_push_ns` in pre-commit's `hook_impl.py` returns as soon as it finds
|
||||
that ref. The two hooks that read the pushed ref — `check-release-needed` and
|
||||
`check-skill-version-bump` — therefore check only that ref, and the others are pushed unchecked.
|
||||
For `check-release-needed`, a multi-ref push whose first ref is not `main` never gates `main` at
|
||||
all. Push one ref at a time when the gate matters.
|
||||
|
||||
## The pre-push gate
|
||||
|
||||
Nine hooks, grouped below by what they guard rather than by the order `.pre-commit-config.yaml` declares them in.
|
||||
@@ -75,12 +83,17 @@ drift in generated text.
|
||||
|---|---|
|
||||
| `validate-marketplace` | `claude plugin validate --strict` on the root marketplace manifest |
|
||||
|
||||
**Skill versioning** (every push, any branch)
|
||||
|
||||
| Hook | Guards |
|
||||
|---|---|
|
||||
| `check-skill-version-bump` | fails if a skill directory changed since the pushed commit's merge-base with `main` without its `metadata.version` rising above the merge-base's (see [below](#check-skill-version-bump)) |
|
||||
|
||||
**Release**
|
||||
|
||||
| Hook | Guards |
|
||||
|---|---|
|
||||
| `check-release-needed` | on a real `git push` to `main` only — fails if files exposed via `.pre-commit-hooks.yaml` changed since the last tag. A no-op everywhere else, including under `pre-commit run --hook-stage pre-push` (see [the caveat above](#running-the-gates)) |
|
||||
| `check-skill-version-bump` | on every push — fails if a skill directory changed since the merge-base with `main` without its `metadata.version` rising (see [below](#check-skill-version-bump)) |
|
||||
|
||||
Two of these shell out to `apm`: `apm-audit-ci` and `apm-pack-check-clean`. The second is a bare
|
||||
`apm …` entry and the first is a `bash -c` loop calling `apm` once per package, so without the CLI
|
||||
@@ -89,20 +102,38 @@ the push dies with an unhelpful "command not found". Install with `apm-install`,
|
||||
|
||||
### `check-skill-version-bump`
|
||||
|
||||
ADR-0022 makes `metadata.version` mandatory and a skill change carries a bump; `skill-size-check`
|
||||
only checks the field's presence and shape, so this hook holds the bump itself.
|
||||
ADR-0022 makes `metadata.version` mandatory and says a skill change carries a bump;
|
||||
`skill-size-check` only checks the field's presence and shape, so this hook holds the bump itself.
|
||||
|
||||
- **Baseline is `git merge-base origin/main <pushed ref>`** (local `main` if `origin/main` does not
|
||||
resolve). Readers install from `main`, so "changed" means changed against what `main` ships. The
|
||||
remote branch tip is not the baseline: a second push would excuse an unbumped change the first
|
||||
push already carried. With no `main` ref or no merge-base, the hook fails closed.
|
||||
- **It runs on every push and under a manual `pre-commit run --hook-stage pre-push`.** It does not
|
||||
read `PRE_COMMIT_REMOTE_BRANCH`, so unlike `check-release-needed` the manual rehearsal really
|
||||
checks it. The pushed commit is `PRE_COMMIT_TO_REF`, or `HEAD` when that is unset.
|
||||
- **Baseline is the merge-base of the pushed commit with `origin/main`** (local `main` if
|
||||
`origin/main` does not resolve). The pushed version is compared with the skill's version *at
|
||||
that merge-base*, not with `main`'s current tip. Readers install from `main`, so "changed" means
|
||||
changed against the `main` the branch started from. The remote branch tip is not the baseline:
|
||||
a second push would excuse an unbumped change the first push already carried.
|
||||
- **It fails closed when it has no trustworthy baseline:** neither `origin/main` nor `main`
|
||||
resolves; there is no merge-base (shallow clone, unrelated history); or only local `main`
|
||||
resolves and the pushed commit *is* the merge-base, so local `main` already contains the pushed
|
||||
commit and says nothing independent about what shipped.
|
||||
- **A changed skill must end strictly above its baseline version**, compared numerically
|
||||
(`1.0.10` > `1.0.9`). Any bump size passes. A missing or non-`MAJOR.MINOR.PATCH` version at the
|
||||
pushed ref fails. Skills absent at the baseline (new, renamed, merged) or at the pushed ref
|
||||
(deleted) are exempt.
|
||||
pushed ref fails, and so does a skill directory left without its `SKILL.md`. Versions are
|
||||
ASCII-only with at most nine digits per part — stricter than `skill-size-check`'s shape check,
|
||||
so a Unicode digit or an overflowing part cannot pass as a bump. Skills absent at the baseline
|
||||
(new, renamed, merged) or at the pushed ref (deleted) are exempt.
|
||||
- **It also fails closed on read errors:** a pushed ref that does not resolve to a commit, or a
|
||||
`SKILL.md` that `git show` or `python3` cannot read, stops the push with a read-failure message
|
||||
rather than being reported as a missing version. Frontmatter that reads but does not parse
|
||||
counts as an invalid version.
|
||||
- **`<skill>/tests/` is excluded**: no agent loads it, so a test-only change ships nothing.
|
||||
- **Known gap:** a PR merged through Gitea's merge button runs no local hook, the same gap
|
||||
`check-release-needed` has.
|
||||
- **It needs `python3` and PyYAML** to read the frontmatter, and fails with a clear message if
|
||||
either is missing, for the reasons in
|
||||
[`python3` and PyYAML are hard requirements](#python3-and-pyyaml-are-hard-requirements).
|
||||
- **Known gaps, both shared with `check-release-needed`:** a PR merged through Gitea's merge button
|
||||
runs no local hook; and a multi-ref push checks only its first ref with new commits (see
|
||||
[Running the gates](#running-the-gates)).
|
||||
|
||||
## Skill and agent context gates (ADR-0020)
|
||||
|
||||
@@ -416,6 +447,8 @@ which is the exact vacuous-green failure the `python3` check exists to avoid. `p
|
||||
**Neither requirement generalises to every hook in this repo.** `check-rtk-prefix` needs `python3`
|
||||
but **not** PyYAML: it reads the markdown body and never touches frontmatter, so it has no scalar to
|
||||
fold.
|
||||
`check-skill-version-bump` needs both, for the same reason as `skill-size-check`: it parses
|
||||
`metadata.version` out of frontmatter.
|
||||
|
||||
## Agent files take the description gates, not the body gate
|
||||
|
||||
|
||||
Reference in New Issue
Block a user