Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion skills/github-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Steps 8 and 9 collapse into that second command: it publishes and carries the no

## References

- `references/release-process.md`
- `references/release-process.md` — the flow; also the 0.x → 1.0 release (what a first stable promises, and the suites CI must actually invoke) and the order for a coupled pair
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- `references/ecosystem-detection.md` — version-file patterns
- `references/immutable-releases.md` — immutable releases, tag burning
- `references/supply-chain-security.md` — SLSA, Sigstore
Expand Down
74 changes: 74 additions & 0 deletions skills/github-release/references/release-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,49 @@ When releasing many repositories that share one reusable release workflow (e.g.
4. **Run the notes overhaul as a separate pass.** The Phase 5 description overhaul is the step most likely to run out of context in a combined loop — do the bump→merge→tag→publish pipeline for the whole batch first, then a second pass for narrative notes via `gh release edit --notes-file`.
5. **Handle "drift" repos.** If a repo's version file was already bumped ahead of its latest tag, the next tag is `max(natural_bump, current_version_in_file)` — use the pre-bumped value (unless that version was already published/burned, in which case bump higher).

## The 0.x → 1.0 Release: What Changes Besides the Number

A patch or minor release ships changes. A first stable release ships a **promise**, and three
things that were optional until then become binding. It is also the release a project does once,
so nobody has the routine.

**1. The promise itself, written where a consumer reads it.** From 1.0 on, an incompatible change
to a public class, method or setting needs a new major. Put that sentence in the changelog entry,
not only in a commit message, and set the state that carries it in the ecosystem's own metadata —
for a TYPO3 extension `'state' => 'stable'` in `ext_emconf.php`, which is a separate field from
the version and is easy to leave at `beta` while every version surface says 1.0.0.

**2. Every deprecated API the release removes has its consumers migrated first.** See the next
section for the order; at 1.0 this is not a nicety, because a 1.0 is where the removals get
batched.

**3. Every test suite the repository ships is invoked by CI.** A suite that exists but is never
run is not a gate, and a first stable release is where that gap becomes a claim about quality.
List both sides and read them side by side. This is a **prompt, not a comparison**: the two
commands do not produce matching identifiers, and neither sees a suite launched through a
project script or a reusable workflow. It is here so the question gets asked at all.

```bash
# suites the repo declares
grep -o 'name="[^"]*"' Build/phpunit*.xml phpunit*.xml 2>/dev/null | sort -u
ls -d Tests/*/ 2>/dev/null

# what CI invokes — plus the callers, which hide the command in another repository
grep -rhoE '(runTests\.sh -s [a-z:]+|phpunit[^|]*--testsuite [a-z]+|npx (playwright|vitest)[a-z ]*)' \
.github/workflows/ | sort -u
Comment thread
coderabbitai[bot] marked this conversation as resolved.
grep -rhoE 'uses: [^ ]+\.ya?ml@' .github/workflows/ | sort -u # follow each one
```

For every suite in the first list, name where the second list runs it, and follow a reusable
workflow into its own repository rather than assuming its name covers the suite. A suite you
cannot place is either wired up before the release or named in the release notes as not running. `nr_passkeys_fe` 1.0.0 shipped 19 end-to-end specifications that
every file skipped with a blanket `test.skip()` and no workflow invoked; the release notes had to
say so, and the suite was built for real in 1.0.1.

Two more surfaces that only matter at 1.0: the documentation version (a docs build pinned to
`main` keeps serving the pre-release manual), and a dependency range that still allows the 0.x of a
sibling package you are releasing in the same batch.

## Releasing a Dependency: the Consumer's CI Races the Registry

When the repo you just released is a **dependency of another repo you are also working on**, the consumer's PR has a window in which its CI is guaranteed to fail for a reason that has nothing to do with its code.
Expand Down Expand Up @@ -451,6 +494,37 @@ composer show "netresearch/nr-vault" "$VERSION" >/dev/null 2>&1 && echo availabl

Observed 2026-07-30: 31 red checks on a consumer PR, zero real defects — CI created 16:13:27, release published 16:22:59.


## Releasing a Coupled Pair: the Consumer Migrates First

Where two packages you maintain are coupled — a frontend extension importing the backend
extension's services, a library and its bundle — a release that **removes** something they share
has an order, and the wrong order ships a combination that cannot work.

1. The consumer migrates to the new API and merges, while its constraint still allows the old
dependency version.
2. The dependency releases the removal.
3. The consumer opens its constraint to the new major and releases.

Doing it the other way round — remove first, migrate afterwards — leaves a window in which the
published pair is broken for anyone who installs both at their newest version.

Two checks before the removal, neither of which the dependency's own repository can answer:

```bash
# the consumer's whole tree, not just Classes/ — a caller can sit in a template,
# in JavaScript, in a fixture or in configuration
git -C /path/to/consumer grep -n "removedMethodName" -- ':!vendor' ':!.Build' ':!node_modules'

# who else declares the dependency
gh search code "netresearch/the-dependency" --owner netresearch --limit 50
Comment thread
CybotTM marked this conversation as resolved.
```

Measured in this fleet on 2026-09-20: `nr_passkeys_be` 1.0.0 removed two `RateLimiterService`
methods after a grep that covered only its own repository. `nr_passkeys_fe` called both, at three
sites. The broken pair was unreachable only because the consumer's `^0.12` constraint refused the
new major — luck, not order.

## Prove an Unproven Pipeline With an `-rc` Tag First

Before the first real tag on a pipeline that has not succeeded **in its current
Expand Down
Loading