docs: add release/version process SOP (#68)
Add docs/release-version-sop.md: operator SOP for cutting a versioned release — master-based flow, SemVer bump decision, version-bump PR prep, required pre-release checks, scripts/release-tag policy (safe-by-default, master-only, clean-tree, tag-after-merge), who may merge/tag, self-review/ self-merge restrictions, status:in-progress handling, worktree cleanup, and an explicit do-not list. Kept distinct from docs/release-workflows.md (future release-mcp orchestrator). Link from README. Documentation only; no code behavior changed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -186,6 +186,9 @@ Notes:
|
||||
[`docs/llm-workflow-runbooks.md`](docs/llm-workflow-runbooks.md) for the
|
||||
task-scoped, profile-based runbooks (create/review/merge/close, thin
|
||||
launchers, migration, fail-closed rules).
|
||||
- See [`docs/release-version-sop.md`](docs/release-version-sop.md) for the
|
||||
operator SOP on cutting a versioned release (version bump, checks, merge,
|
||||
`scripts/release-tag`, and cleanup).
|
||||
- For the **portable** version of this workflow (issue-first, isolated
|
||||
worktrees, no self-review/merge, profile safety, cleanup, fail-closed) that
|
||||
can be copied into any project, see the reusable skill
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
# Release / Version Process SOP
|
||||
|
||||
Operator standard operating procedure for cutting a versioned release of
|
||||
Gitea-Tools: version bump, checks, merge, tag, and cleanup.
|
||||
|
||||
> **Scope.** This is the **human/operator** SOP. It is deliberately distinct
|
||||
> from [`release-workflows.md`](release-workflows.md), which describes the
|
||||
> **future `release-mcp` orchestrator** boundary (a coordination concept), not
|
||||
> the day-to-day tagging process. When they disagree, this document governs how
|
||||
> a release is actually cut today.
|
||||
|
||||
---
|
||||
|
||||
## 1. Branch flow
|
||||
|
||||
The repo is **`master`-based**. Releases are cut from `master`; there is no
|
||||
separate `dev`/`release` branch unless and until that is explicitly introduced
|
||||
and this SOP is updated to match. All work lands on `master` via reviewed PRs
|
||||
from short-lived, issue-linked branches (e.g. `docs/issue-68-...`).
|
||||
|
||||
## 2. Where "the version" lives
|
||||
|
||||
There is **no `VERSION` file and no `CHANGELOG` file** in the repo today. The
|
||||
released version is expressed **only as an annotated git tag** of the form
|
||||
`vMAJOR.MINOR.PATCH` (existing tags: `v1.0.0`, `v1.0.1`). Release notes are
|
||||
carried as the **annotated tag's message** (via `--notes-file`), not a tracked
|
||||
changelog.
|
||||
|
||||
> Do **not** confuse this with `SUPPORTED_VERSION` in `gitea_config.py` — that is
|
||||
> the **config-schema** version, unrelated to the application release version.
|
||||
|
||||
If a `VERSION`/`CHANGELOG` file is added later, update this SOP to list it under
|
||||
"files to update".
|
||||
|
||||
## 3. Deciding the version bump (SemVer)
|
||||
|
||||
Pick the bump against the last tag using semantic-versioning intent:
|
||||
|
||||
* **PATCH** (`v1.0.1 → v1.0.2`): bug fixes, docs, tests, internal cleanups — no
|
||||
change to tool names, parameters, return payloads, or behavior.
|
||||
* **MINOR** (`v1.0.1 → v1.1.0`): backward-compatible additions — new MCP tool,
|
||||
new optional parameter, new script, additive behavior.
|
||||
* **MAJOR** (`v1.1.0 → v2.0.0`): backward-**incompatible** changes — renamed or
|
||||
removed tools, changed return-payload shape, changed default behavior, or a
|
||||
tightened safety gate that rejects previously-accepted input.
|
||||
|
||||
When unsure between two levels, choose the higher one.
|
||||
|
||||
## 4. Preparing a version-bump / release PR
|
||||
|
||||
Releases are still gated by the normal issue-first, PR-reviewed flow.
|
||||
|
||||
1. Open (or use) a tracking issue for the release and **claim it** with
|
||||
`status:in-progress` (see §9).
|
||||
2. Create an isolated, issue-linked branch + worktree from latest `master`
|
||||
(e.g. `chore/issue-63-v1.1.0`). Never commit directly to `master`.
|
||||
3. Include in the PR:
|
||||
* Any code/docs changes that belong to the release.
|
||||
* The **release notes** for the annotated tag (draft them in the PR body or a
|
||||
notes file you will pass to `scripts/release-tag --notes-file`).
|
||||
* If a `VERSION`/`CHANGELOG` file exists at that time, its update.
|
||||
4. Open the PR **targeting `master`**.
|
||||
|
||||
The tag is **not** created in the PR. Tagging happens only after merge (§6).
|
||||
|
||||
## 5. Required checks before release
|
||||
|
||||
Run all of these green before merging the release PR and before tagging:
|
||||
|
||||
```bash
|
||||
python3 -m py_compile mcp_server.py
|
||||
python3 -m py_compile manage_labels.py
|
||||
bash -n scripts/clear-provenance
|
||||
./venv/bin/python -m pytest tests/ -q
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Plus a secret sweep (there is no third-party scanner wired in; do a staged-diff
|
||||
sweep — see [`developer-testing-guidelines.md`](developer-testing-guidelines.md)
|
||||
§7):
|
||||
|
||||
```bash
|
||||
git diff --cached | grep -nEi "authorization: (basic|bearer)|password[:=]|token=[A-Za-z0-9]" || echo "clean"
|
||||
```
|
||||
|
||||
`scripts/release-tag` **also** runs the test suite itself before tagging (unless
|
||||
`--skip-tests` is passed), so tests are enforced twice by default.
|
||||
|
||||
## 6. Running `scripts/release-tag`
|
||||
|
||||
Tag **only after** the release PR is merged to `master`. `scripts/release-tag`
|
||||
enforces the tagging policy and is **safe by default** (creates nothing on a
|
||||
dry-run; never pushes without `--push`).
|
||||
|
||||
Before it tags, it requires **all** of:
|
||||
|
||||
* version matches `vMAJOR.MINOR.PATCH` (SemVer);
|
||||
* `fetch --prune` has run;
|
||||
* you are **on `master`**;
|
||||
* the worktree is **clean** (no uncommitted changes);
|
||||
* local `master` **equals** `<remote>/master`;
|
||||
* `HEAD` is that same commit (the commit is present on remote master);
|
||||
* the tag does **not** already exist locally or on the remote;
|
||||
* the test suite passes (unless `--skip-tests`, which warns).
|
||||
|
||||
Typical sequence:
|
||||
|
||||
```bash
|
||||
# 1. Dry-run to confirm the plan (changes nothing)
|
||||
scripts/release-tag --dry-run v1.1.0
|
||||
|
||||
# 2. Create the annotated tag locally, with release notes
|
||||
scripts/release-tag v1.1.0 --notes-file /path/to/release-notes.md
|
||||
|
||||
# 3. Push the tag only when ready
|
||||
scripts/release-tag v1.1.0 --notes-file /path/to/release-notes.md --push
|
||||
```
|
||||
|
||||
Env injection points (mainly for CI/tests):
|
||||
`RELEASE_TAG_REMOTE` (default `prgs`), `RELEASE_TAG_TEST_CMD`
|
||||
(default `./venv/bin/python -m pytest tests/ -q`).
|
||||
|
||||
## 7. Who may merge / tag
|
||||
|
||||
* The release PR must be **merged by someone other than its author** — the
|
||||
author-cannot-merge safety gate applies to releases exactly as to any other PR.
|
||||
* Merge uses the gated `gitea_merge_pr` workflow; CLI/legacy merge is disabled.
|
||||
* Whoever tags must operate on clean master synced to the remote (enforced by
|
||||
`scripts/release-tag`). Tagging is an operator action performed after merge.
|
||||
|
||||
## 8. Self-review / self-merge restrictions
|
||||
|
||||
Release PRs are **not** exempt from the safety model:
|
||||
|
||||
* No self-review — the author may not approve their own release PR.
|
||||
* No self-merge — a different eligible identity merges.
|
||||
* These gates are enforced by the MCP tooling and must not be bypassed.
|
||||
|
||||
## 9. Handling `status:in-progress` during release work
|
||||
|
||||
* **Claim** the release tracking issue with `status:in-progress` before starting.
|
||||
* Keep it claimed while the release PR is open and under review.
|
||||
* On merge/close, the tracker-hygiene automation releases `status:in-progress`
|
||||
for issues the PR closes; if it remains after the release lands, release it
|
||||
explicitly. Do not leave a shipped release issue marked in-progress.
|
||||
|
||||
## 10. Branch / worktree cleanup after merge
|
||||
|
||||
After the release PR merges and the tag is pushed:
|
||||
|
||||
* Delete the remote release branch (if repo policy allows).
|
||||
* Remove the local worktree and delete the local branch:
|
||||
|
||||
```bash
|
||||
git worktree remove branches/<release-worktree>
|
||||
git branch -d <release-branch>
|
||||
git worktree prune
|
||||
```
|
||||
* Confirm the root repo is clean and on `master` synced to the remote.
|
||||
|
||||
## 11. What NOT to do
|
||||
|
||||
* **No direct commits to `master`.** All changes land via reviewed PRs.
|
||||
* **No force-push** (to `master` or to tags).
|
||||
* **No self-merge** of a release PR.
|
||||
* **No tagging before merge** — tag only commits already on remote `master`.
|
||||
* **No release from a dirty worktree** — `scripts/release-tag` refuses, and so
|
||||
should you.
|
||||
* **No `--skip-tests`** for a real release unless there is an explicit,
|
||||
documented reason.
|
||||
* **No re-tagging / moving an existing tag** — pick the next version instead.
|
||||
Reference in New Issue
Block a user