diff --git a/README.md b/README.md index 2f052d9..09f5cea 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/release-version-sop.md b/docs/release-version-sop.md new file mode 100644 index 0000000..1cad6b6 --- /dev/null +++ b/docs/release-version-sop.md @@ -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** `/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/ + git branch -d + 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.