--- name: release description: > Use ONLY when ready to cut a release. The Release Manager (Worker) inspects project state (git log, version files, build) to determine the semver bump, generate the changelog, tag, and produce a release artifact. Works on any project state — no pipeline artifacts required. triggers: - release - cut release - 发版 - publish release - ship release role: Producer --- > Core 中立版(Increment 6a 改写,原 deferHard verbatimDir)。机制、结构与 frontmatter 保持;实例术语(工具名、路径、工单号)按 `core/adapters/TERMINOLOGY.md` 绑定到具体实例。 # Release Prepare and tag a release based on project facts — git history since the last tag, current version, build status, and dependency health. No dependency on `.artifacts/{slug}/` (**deprecated** — SDLC artifacts now live in the Gitea wiki at `{slug}/...`; see `_shared/gitea-read-patterns.md`) or any pipeline skill outputs. **This SKILL does not deploy.** Deployment is project-specific (K8s, Docker, npm publish, etc.) and varies too widely to template. The release artifact produced here is the input to project-specific deployment workflows. ## Agent Role The release is owned and executed by the **Release Manager** (Worker). **Context compaction**: release is a pipeline stage boundary. The main session compacts at this clean boundary ONLY when a capacity/projection trigger holds, per `core/rules/compact.md` §"Stage-boundary compaction" (long multi-stage runs — DAG Epic orchestration — keep the legacy every-boundary compaction; short runs — bugfix / DAG task — and standalone runs default to NOT compacting). The Release Manager itself is single-phase and artifact-driven (version files, changelog, tag); a mid-run compaction loses nothing — re-read the release checklist state and git log to resume. --- ## Preconditions Before starting the release: - [ ] Working tree is clean (`git status` has no uncommitted changes). - [ ] `core/checklists/release.md` is accessible. --- ## Phase 1 — Pre-release Gate Run each check against the live project. Stop and report failures. 1. **Clean workspace**: `git status` must show nothing to commit. 2. **Branch**: confirm the current branch. Default assumption: release from `main` or `master`. If on another branch, note it. 3. **Build**: run the project's build command. Must pass. 4. **Typecheck + Lint**: run the project's typecheck and lint. Must pass. 5. **Tests**: run the project's test suite. Must pass. 6. **Dependency audit**: run the project's vulnerability scanner (e.g. `npm audit`, `bun audit`, `pip-audit`, `cargo audit`). - No new HIGH or CRITICAL CVEs block the release. - Pre-existing HIGH/CRITICAL CVEs do NOT block but **MUST be filed as tech-debt** before the release lands. File them in TWO TIERS (batching rule, [org-internal #3846] — a single release audit once fanned out to 14 TDs → 14 PRs → 56-84 CI runs on a saturated runner pool): Tier A below is the general mechanical-change batching rule; Tier B and the escape hatch follow it. ### Tier A — 通用机械变更并单规则 / Universal mechanical-change batching rule Origin [org-internal #3846] (audit-batch pilot, generalized in place): the rule covers ANY single-source fan-out of mechanical changes, not just dependency audits. Two source types today: (a) dependency bump audits; (b) docs 批量修订 (batch docs revisions). #### 规则正文 / Rule text - **Core**: mechanical changes of ONE class fanning out from a single source are filed as ONE batch issue + ONE batch PR — never N×issue + N×PR. The batch body keeps one row per item (per-CVE / per-doc mapping), so per-item traceability is unchanged; work the batch as a single PR. - **(a) Dependency bump audits** (the [org-internal #3846] origin case): advisories whose remediation is a plain version bump (lockfile-only diff, no semver-major jump, no overrides/catalog surgery, no API or adapter fallout) are filed together via `工单 API(见 TERMINOLOGY)create` with `title="[{origin}] audit-batch@{version}: mechanical bumps for {N} advisories (TD-{NNN})"` and labels `[tech_debt_label_id, severity_label_id, kind_bug_label_id]` (severity = highest in the batch). One row per advisory — CVE/GHSA ID, affected package + from→to version, severity (CVSS), advisory URL, recommended remediation, and a per-advisory Reactivation Trigger ("resolved when `npm audit` / `bun audit` reports no HIGH/CRITICAL for this advisory") — so the release-notes `TD-NNN → #NNNN` mapping stays per-advisory. - **(b) Docs 批量修订 (batch docs revisions)**: many small same-class docs corrections discovered in one pass (e.g. a terminology sweep) file as ONE issue via the same flow, `title="[{origin}] docs-batch: {class} revisions for {N} files"`, with one row per file (path, correction, reason); same ONE-batch-PR landing. - **Landing**: when members live on separate branches, compose the single batch PR via the `land-batch` skill (`core/skills/land-batch/SKILL.md` — cross-branch batch composition, topology B). For dependency-bump batches pass `batch-compose --convergent bun.lock` (script `.gitea/scripts/batch-compose.ts`): the regenerable lockfile is exempt from path-overlap admission and lockfile-only conflicts are surgically resolved to the running head's version. #### 适用边界 / Applicability boundary - **非mechanical(含判断成分的变更,不并单)** — items requiring judgment (wording decisions, behavior/API changes, review-dependent edits) are never batched; file each as its own issue. - **路径重叠(batch 成员触碰相同文件 → 不并单;唯一豁免 = 可再生共享文件`bun.lock` 经batch-compose `--convergent` 收敛)** — the sole path-overlap exemption is the regenerable shared lockfile under `--convergent`; every other same-file collision stays un-batched. - **semver-major 跳变 / overrides·catalog 手术 / adapter·peer 联动(→ Tier B 独立单,维持 per-package)** — these stay per-package Tier B issues. Tier B and the escape hatch (below) survive this generalization unchanged. #### 降级路径 / Degradation path - **并单 PR CI 失败 → 按锁文件 hunk 拆包回退(bisect)**: for dependency batches, bisect by splitting lockfile hunks per package back into per-package PRs — the original Tier A bisect semantics carried over verbatim (同语义,泛化措辞: every mechanical batch degrades the same way). - **Docs batches**: split per file back into per-file PRs. - **land-batch composition fallback** (exit codes per `.gitea/scripts/batch-compose.ts`): `3` path-overlap → split the batch along the reported pairs (or land the overlapping member 1:1) and re-run per group; `4` merge conflict / `5` transport/git error → fall back to 1:1 PRs for the whole batch. - **Preflight hedge ([org-internal #3846])**: run `bun install --dry-run` before opening a dependency-batch PR — the resolver accepting the composed version set is a cheap pre-CI rejection of impossible bump combinations. Tier B and the escape hatch (unchanged by the generalization): - **Tier B — surgery, one issue per package**: semver-major jumps, adapter/peer fallout, or overrides/catalog surgery keep the per-package issue: `title="[{origin}] {CVE-ID/GHSA-ID} in {package} (TD-{NNN})"` with the same labels; body requirements match the Tier A rows. - Escape hatch: either tier may carve a single advisory into its own issue when same-day remediation is required (urgent HIGH/CRITICAL). De-duplicate against open `tech-debt` issues (match by CVE/GHSA ID) before creating. Record the `TD-NNN → #NNNN` mapping in the release notes. See `verify` Phase 5.5 for the tech-debt promotion body template and `_shared/gitea-write-patterns.md` Pattern 3. ```markdown ## Pre-release Gate | Check | Status | | ----------------- | ----------------------------------------------------- | | Clean workspace | ✅/ ❌ | | Branch | {branch name} | | Build | ✅/ ❌ | | Typecheck + Lint | ✅/ ❌ | | Tests | ✅/ ❌(N passed, M failed) | | Dependency audit | ✅/ ⚠️ N known CVEs (pre-existing) / ❌N new CVEs | ``` If any gate fails except known CVEs, stop and report what failed. --- ## Phase 2 — Version Bump Determine the new version by inspecting git history since the last tag. 1. **Find last tag**: `git describe --tags --abbrev=0` (or `git tag --sort=-v:refname | head -1`). - **No previous tag?** This is the first tracked release. Use the initial commit as baseline: `git rev-list --max-parents=0 HEAD`. After tagging this release, create a retroactive baseline tag (`v{base-version}`) on the initial commit so future cycles have a clean `{tag}..{tag}` range. Document the gap in the release report. 2. **Read commits since last tag**: `git log ..HEAD --oneline`. 3. **Read current version** from the project's version file (`package.json` `version`, `Cargo.toml`, `VERSION`, etc.). 4. **Categorize commits** by change type: | Conventional prefix | Semver | Examples | | -------------------- | ------- | ----------------------------- | | `BREAKING CHANGE:` / `!:` | MAJOR | API removal, schema change | | `feat:` | MINOR | New feature, new endpoint | | `fix:` | PATCH | Bug fix only | | `perf:` | PATCH | Performance improvement | | `refactor:` | PATCH | Internal restructuring | | `docs:` / `chore:` | (skip) | Not user-visible | If no conventional prefix found, infer from the subject line: - "add", "implement", "introduce" → MINOR - "fix", "resolve", "correct" → PATCH - "remove", "drop", "rename" (public API) → MAJOR 5. **Compute bump**: - If any MAJOR commit → bump MAJOR. - Else if any MINOR commit → bump MINOR. - Else → bump PATCH. ```markdown ## Version Bump **Last tag**: {tag} **Current version**: {old version} **New version**: {new version} **Type**: MAJOR / MINOR / PATCH **Commits since last tag**: {N} **Reason**: {justification — e.g. "2 feat + 3 fix → MINOR"} ``` --- ## Phase 3 — Changelog Generate the changelog from `git log ..HEAD`: 1. **List all commits**. For each, extract: - Type (from prefix or inferred). - Scope (if present, e.g. `feat(auth):`). - Description (the subject line, past tense, human-readable). 2. **Group by type**: - **Added** — `feat:` commits. - **Changed** — modifications to existing behavior (non-breaking). - **Fixed** — `fix:` commits. - **Breaking** — `BREAKING CHANGE:` or `!:` commits. 3. **Deduplicate**: squash multiple commits for the same change into one entry where it makes narrative sense. 4. Read the existing root `CHANGELOG.md` (if any) and prepend this release. Write to root `CHANGELOG.md` (prepend section). ```markdown ## [{version}] — {YYYY-MM-DD} ### Added - {feature} ({commit hash short}) ### Changed - {change} ({hash}) ### Fixed - {bugfix} ({hash}) ### Breaking - {breaking change} ({hash}) — see migration notes above ``` --- ## Phase 4 — Tag & Finalize 1. **Update version file** — write the new version to the project's version manifest. 2. **Commit**: `git add` version file + changelog file, commit with message: ``` release: {version} ``` 3. **Tag**: `git tag v{version}` (adjust prefix per project convention — check existing tags with `git tag -l`). 4. **Verify tag**: `git tag -l v{version}` confirms the tag exists. **Do not push** — the user must explicitly request pushing to remote. ```markdown ## Release Artifact - **Version**: {version} - **Commit**: {commit hash} - **Tag**: v{version} - **Changelog**: CHANGELOG.md updated ### Files Changed | File | Change | | -------------- | ------------------------- | | {version file} | {old} → {new} | | CHANGELOG.md | Prepended {version} | ``` --- ## Phase 4b — Publish Release Artifacts (octopus project, manual) > Octopus-specific. Other projects: substitute your own artifact pipeline — > the goal is identical (turn the tag into downloadable assets). The CI publish pipeline (`.gitea/workflows/publish.yml`) was **retired by [org-internal #2003]** (138/138 historical runs cancelled; the pipeline sat unused for 35+ days). Releasing octopus is now a **manual local process**. All former CI steps live in repo scripts, runnable from a maintainer machine with the right credentials in the environment. ### Prerequisites - Clean checkout of the release commit (tag pushed or about to be pushed). - Credentials in env: - `GITEA_TOKEN` — PAT with `write:repository` (release create/undraft, tag push) and `write:package` (container registry). The `CI_PAT` secret value is the canonical token. - `NODE_AUTH_TOKEN` — only if publishing to npm. - `AUR_KEY` — only if pushing the AUR package. - Docker logged in to the Gitea container registry: `echo "$GITEA_TOKEN" | docker login -u --password-stdin`. ### Steps 1. **Version + draft release** (idempotent — skips if already published): ```bash GITEA_TOKEN= GH_REPO=Octopus/octopus bun script/version.ts ``` Creates (or refines) the draft Gitea release for `v{version}`. 2. **Build the 12 platform binaries + archives** (longest step): ```bash OCTOPUS_VERSION={version} OCTOPUS_RELEASE={release-id} \ GH_REPO=Octopus/octopus GITEA_TOKEN= \ bun /script/build.ts ``` `build.ts` attaches the 12 release assets to the draft release. 3. **Publish npm / docker / AUR + git sync**: ```bash OCTOPUS_VERSION={version} OCTOPUS_RELEASE={release-id} \ GITEA_TOKEN= GH_REPO=Octopus/octopus \ NODE_AUTH_TOKEN= \ bun script/publish.ts ``` Set `OCTOPUS_GITEA_ONLY=true` to skip npm/docker/AUR/homebrew. 4. **Undraft the release** (makes it public): ```bash curl -fsS -X PATCH \ "/api/v1/repos/Octopus/octopus/releases/{release-id}" \ -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" -d '{"draft": false}' ``` 5. **Verify assets** — the release must carry 12 assets: ```bash curl -sS "/api/v1/repos/Octopus/octopus/releases/tags/v{version}" \ -H "Authorization: token $GITEA_TOKEN" | jq '.assets | length' ``` 6. **Sync the public mirror repo** (`Octopus/octopus-release`): ```bash CI_PAT= VERSION={version} bash script/sync-public-install.sh CI_PAT= VERSION={version} bash script/sync-public-assets.sh CI_PAT= VERSION={version} bash script/verify-public-sync.sh ``` ### Notes - **Windows code-signing** is not provisioned (no Windows runner / Azure Trusted Signing); the CLI ships unsigned, as before ([org-internal #252]). - **Rollback**: if a step fails mid-release, the draft release + tag can be deleted and re-run; every script above is idempotent or safely re-runnable. - If a future CI pipeline replaces this manual flow, update this section and reference [org-internal #2003] for the retirement rationale. --- ## Phase 5 — Rollback Plan Document how to undo this release: 1. **Git rollback**: - `git tag -d v{version}` (if not yet pushed). - `git revert {commit_hash}` (if already merged). - If the release includes DB migrations, confirm the `down` migration exists. 2. **Data rollback** (if applicable): - For each migration, confirm the `down` migration exists and has been tested. - If the release changes data format without a reversible migration, mark `⚠️ IRREVERSIBLE DATA CHANGE`. ```markdown ## Rollback Plan ### Git Rollback git tag -d v{version} # or: git revert {hash} ### Data Rollback - Migration `{name}`: down {exists / NOT FOUND} - {additional risks} ### Rollback Triggers | Condition | Threshold | Duration | | ------------------ | ------------ | -------- | | P99 latency spike | 2x baseline | 5 min | | Error rate spike | 1% | 1 min | | Critical bug | Data loss / security breach | immediate | ``` --- ## Phase 6 — Post-release Smoke Test 1. Run the project's build command on the tagged commit. Must pass. 2. Run the test suite. Must pass. 3. Return to the branch: `git checkout `. --- ## Phase 7 — Report ```markdown ## Release Report **Version**: {old} → {new} (MAJOR / MINOR / PATCH) **Tag**: v{version} **Commits**: {N} since last tag ({feat} features, {fix} fixes) **Typecheck**: ✅| Lint: ✅| Tests: ✅ ### Changelog {paste changelog section} ### Release Artifacts - Commit: {hash} — `release: {version}` - Tag: v{version} - Changelog: CHANGELOG.md updated --- **Deploy**: {manual step — "merge to main triggers CI", etc.} **Rollback**: see Phase 5 above **Next**: `core/skills/retrospective/SKILL.md` (optional — run on any project state) ``` --- ## References - `core/checklists/release.md` — Release checklist - Semver spec: https://semver.org - Conventional Commits: https://www.conventionalcommits.org