Files
octopus-workflow/core/skills/release/SKILL.md
T

18 KiB

name, description, triggers, role
name description triggers role
release 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.
release
cut release
鍙戠増
publish release
ship release
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).

瑙勫垯姝f枃 / 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锛塩reate 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鈫抰o 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.
  • *璺緞閲嶅彔锛坆atch 鎴愬憳瑙︾鐩稿悓鏂囦欢 鈫?涓嶅苟鍗曪紱鍞竴璞佸厤 = 鍙啀鐢熷叡浜枃浠?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 鎷嗗寘鍥為€€锛坆isect锛?*: 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.

## 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 <last-tag>..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.
## 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 <last-tag>..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).

## [{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.

## 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 <instance-registry-host> -u <user> --password-stdin.

Steps

  1. Version + draft release (idempotent 鈥?skips if already published):

    GITEA_TOKEN=<pat> 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):

    OCTOPUS_VERSION={version} OCTOPUS_RELEASE={release-id} \
      GH_REPO=Octopus/octopus GITEA_TOKEN=<pat> \
      bun <harness-package>/script/build.ts
    

    build.ts attaches the 12 release assets to the draft release.

  3. Publish npm / docker / AUR + git sync:

    OCTOPUS_VERSION={version} OCTOPUS_RELEASE={release-id} \
      GITEA_TOKEN=<pat> GH_REPO=Octopus/octopus \
      NODE_AUTH_TOKEN=<npm-token> \
      bun script/publish.ts
    

    Set OCTOPUS_GITEA_ONLY=true to skip npm/docker/AUR/homebrew.

  4. Undraft the release (makes it public):

    curl -fsS -X PATCH \
      "<instance-base-url>/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:

    curl -sS "<instance-base-url>/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):

    CI_PAT=<pat> VERSION={version} bash script/sync-public-install.sh
    CI_PAT=<pat> VERSION={version} bash script/sync-public-assets.sh
    CI_PAT=<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.
## 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 <original-branch>.

Phase 7 鈥?Report

## 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