AGENT SKILL

release-effector

δ-side mechanics for cutting a release — scripts/release.sh invocation, tag push, release CI polling, CI-red recovery runbook, branch cleanup. The platform-actions companion to release/SKILL.md (β-side authoring).

License 许可
Apache-2.0
所属仓库
usurobor/cnos
最近更新
2026-11-07
Stars
★ 9
Forks
0

01 前言 Front Matter

name
release-effector
calls
[ "release/SKILL.md", "operator/SKILL.md" ]
scope
task-local
inputs
[ "gamma-closeout.md on main", "VERSION decision (per release/SKILL.md §2.2)", "RELEASE.md on main (per release/SKILL.md §2.5)", "cycle directories under .cdd/unreleased/{N}/ ready to move" ]
parent
cdd
outputs
[ "bare X.Y.Z annotated tag on origin", "release CI green (or operator-accepted override)", "merged cycle/{N} branches deleted (or attempt recorded if harness blocks delete)", "gate-action completion confirmations per operator/SKILL.md §3.3" ]
requires
[ "δ has read operator/SKILL.md §3 (outward gate policy)", "γ has shipped RELEASE.md and the cycle-dir moves per release/SKILL.md §2.5–§2.5a (or scripts/release.sh will perform the moves)", "gamma-closeout.md exists on main (per cnos.cds/skills/cds/CDS.md §\"Artifact contract\" → §\"Frozen snapshot rule\")", "β has signaled \"release ready for δ tag\" in beta-closeout.md (per release/SKILL.md §2.6)" ]
triggers
[ "release effector", "scripts/release.sh", "tag push", "release CI", "branch cleanup", "disconnect release" ]
visibility
internal
description
δ-side mechanics for cutting a release — scripts/release.sh invocation, tag push, release CI polling, CI-red recovery runbook, branch cleanup. The platform-actions companion to release/SKILL.md (β-side authoring).
kata_surface
embedded
artifact_class
skill
governing_question
When γ declares closure, what does δ run, in what order, and how does δ verify the release disconnected cleanly?

03 SKILL.md 正文

Release Effector (δ)

Core Principle

The tag is the disconnection point. δ runs the script, polls CI, cleans the branches.

release-effector is the mechanics-of-the-disconnect skill. β-side authoring (RELEASE.md, CHANGELOG, version decision, cycle-dir move) lives in release/SKILL.md. δ-policy frame (when the release gate fires, what δ does NOT do, override authority) lives in operator/SKILL.md (Phase 4a will relocate to delta/SKILL.md). This skill is the executable surface between them: the exact commands δ runs at the disconnect moment, in order, with the failure-mode runbook for red CI.

Failure mode: manual tagging. A git tag X.Y.Z && git push --tags skips version stamping, skips consistency checks, skips structured tag-message generation, and produces a release whose VERSION, cn.json, package manifests, and tag disagree. The single-command release script is the only sanctioned path.


Preconditions

Before δ runs scripts/release.sh, all of the following must be true on main:

Precondition Owner Verified where
gamma-closeout.md exists on main γ cnos.cds/skills/cds/CDS.md §"Artifact contract" → §"Ownership matrix"; gamma/SKILL.md §2.10 closure gate
RELEASE.md exists at repo root γ release/SKILL.md §2.5; cnos.cds/skills/cds/CDS.md §"Artifact contract" → §"Ownership matrix"
Cycle directories moved (or ready to move via the script) γ release/SKILL.md §2.5a
VERSION decided (semver bump) γ + β release/SKILL.md §2.2
β signaled "release ready for δ tag" in beta-closeout.md β release/SKILL.md §2.6
Local main up to date with origin/main δ scripts/release.sh step 2 (auto-check)
Tag X.Y.Z does not already exist δ scripts/release.sh step 3 (auto-check)

The script enforces the last two mechanically and errors out if they fail. The first five are γ/β responsibilities; δ confirms they are met before invoking the script.


1. Run scripts/release.sh

Single-command release. Edit VERSION, then run the script:

# Edit VERSION first (canonical: bare X.Y.Z, no v-prefix), then:
scripts/release.sh

# Or pass version directly:
scripts/release.sh 3.67.0

Manual tagging is not allowed. Do not run git tag directly. The release script is the only way to tag. It prevents the class of failures where VERSION, cn.json, and package manifests disagree.

What the script does, in order (mechanical narration; matches scripts/release.sh as of cycle #399):

  1. Set VERSION (writes $1 to VERSION if a version arg was passed).
  2. Confirm current branch is main and HEAD matches origin/main. Errors out otherwise.
  3. Confirm the tag does not already exist locally. Errors out if it does (use release/SKILL.md §3.6 amend-don't-re-tag procedure when CI fails post-tag).
  4. Run scripts/validate-release-gate.sh — RELEASE.md + cycle-artifact completeness check (see release/SKILL.md §2.1 for the validator surface).
  5. Run scripts/stamp-versions.sh — derives cn.json and package manifests from VERSION.
  6. Run scripts/check-version-consistency.sh — validates all version-stamped files agree.
  7. Move .cdd/unreleased/*/ directories to .cdd/releases/$VERSION/ (no-op when γ has already moved them per release/SKILL.md §2.5a; the script handles both cases idempotently).
  8. Stage + commit release: $VERSION if anything changed in the working tree.
  9. Warn-prompt (interactive y/N) if CHANGELOG.md does not contain a ## $VERSION heading. Abort path is N.
  10. Generate the annotated tag message via scripts/generate-release-tag-message.sh — includes issue metadata, wave context when present, and CDD review artifacts when available; deterministic fallback when metadata is unavailable.
  11. Create the annotated (not lightweight) tag at the bare version: git tag -a $VERSION -F <message-file>.
  12. git push origin main --tags — pushes the release commit and tag in one command.

After the script returns, monitor release CI per §3 below.

  • ❌ git tag 3.67.0 && git push --tags (skips stamp, skips consistency check, skips structured tag message)
  • ❌ γ's skill patches sit on main untagged across multiple cycles
  • ❌ δ defers release "because there are no consumers" (the tag is structural, not consumer-driven)
  • ✅ scripts/release.sh 3.67.0 — stamps, verifies, commits, tags, pushes

2. Tag policy (bare X.Y.Z, no v-prefix)

All tags are bare X.Y.Z. Canonical reference: cnos.cds/skills/cds/CDS.md §"Artifact contract" → §"Location matrix" — "Tags are bare X.Y.Z everywhere (VERSION file, git tag, branch-name version segment, CHANGELOG row, RELEASE.md, snapshot directory). v-prefixed tags are legacy and warn-only."

The bare-version convention applies to every surface in the chain:

Surface Form
VERSION file content 3.67.0
Git tag 3.67.0 (annotated, not lightweight)
Branch name version segment cycle/399, claude/3.67.0-22-... (release directory 3.67.0/)
CHANGELOG.md ledger row ## 3.67.0
RELEASE.md body references the bare version
Snapshot dir docs/gamma/cdd/3.67.0/
Cycle-dir move target .cdd/releases/3.67.0/{N}/

Annotated tags only. scripts/release.sh creates git tag -a $VERSION -F <message-file> — never lightweight tags. The annotated form carries the structured message generated by scripts/generate-release-tag-message.sh (issue metadata, wave context, CDD review artifacts, deterministic fallback). δ can inspect the tag-message body after the fact with:

git show <version>
git for-each-ref refs/tags/<version> --format='%(contents)'

One tag per release. Per release/SKILL.md §3.6: if CI fails post-tag, amend the release commit, delete-and-recreate the same bare-version tag, force-push. Do NOT proliferate 3.9.1, 3.9.1-fix, 3.9.1-fix2.

  • ❌ v3.67.0 (legacy v-prefix; warn-only per cnos.cds/skills/cds/CDS.md §"Artifact contract" → §"Location matrix")
  • ❌ Lightweight tag (git tag $VERSION without -a or -F) — loses the structured message
  • ❌ Tag proliferation across CI-red retries
  • ✅ One bare 3.67.0 annotated tag, amended until CI green

3. Poll release CI

After tag push, the release CI workflow builds binaries (linux-x64, macos-x64, macos-arm64) and attaches them to the GitHub release. δ blocks release completion until that workflow reports green.

# Quick view:
gh run list --branch <version>

# Or watch a specific run to completion:
gh run watch <run-id> --exit-status

# Or via the workflow surface:
gh run list --workflow release.yml --limit 1

Decision rule:

  • CI Green → δ declares the release complete; report completion to γ per operator/SKILL.md §3.3.
  • CI Red → execute the recovery runbook in §4 below. δ MUST NOT declare release complete while CI is red.

The gate does not close until CI is green or operator explicitly accepts a known pre-existing failure (override per §4 step 5).

  • ❌ "Tag pushed; we're done" — release CI may still be running or red
  • ❌ Deploy from binaries before CI completes (stale binary from previous release)
  • ✅ gh run watch <id> --exit-status then verify assets attached, then report completion

4. CI-red recovery runbook

When release CI reports red, δ owns the recovery. The runbook is mandatory; declaring the release complete while CI is red is a discipline break (see §6 below).

  1. Investigate the release CI logs to identify which workflow step failed and why.
  2. Classify the failure:
    • Release-specific — the failure is caused by something this release introduced (a broken stamp, a manifest drift, a binary-build error new to this version).
    • Pre-existing infrastructure — the failure was present before this release (flaky smoke test, transient registry outage, expired token, environment regression unrelated to release content).
  3. Fix or escalate based on classification:
    • Fixable / release-specific — fix the cause, then amend the release commit and re-tag per release/SKILL.md §3.6 (git commit --amend --no-edit && git tag -d $VERSION && git tag -a $VERSION -F <message-file> && git push --force origin main $VERSION). Then re-poll CI. Do NOT proliferate tags.
    • Pre-existing / infrastructural — document the failure, escalate to operator-as-human, and do NOT declare release complete on the agent's authority.
  4. Re-verify — after any fix attempt, re-poll CI (§3 above) and reapply this runbook if CI is still red.
  5. Operator override — explicit operator-as-human acceptance is the only path to declare release complete with red CI. This is the escape hatch for known pre-existing failures (canonical examples: v3.66.0 and v3.67.0 smoke failures). The override must be declared per operator/SKILL.md §4 (override protocol) and recorded in gamma-closeout.md or cdd-iteration.md so the next release sees the precedent.

The gate does not close until CI is green or operator explicitly accepts the failure.

  • ❌ Declare release complete because the tag is on origin, ignoring red CI
  • ❌ Proliferate 3.67.0-fix, 3.67.0-fix2 instead of amend-and-re-tag
  • ❌ Apply operator override to a release-specific failure ("just ship it")
  • ✅ Investigate → classify → fix-or-escalate → re-verify → operator-override-if-pre-existing

5. Branch cleanup

After the release is declared complete (CI green or accepted override), δ deletes merged cycle branches. This is mechanical cleanup, not judgment — if it's merged into main, it's dead.

Targets:

  • Cycle branches: cycle/{N} (post-#287 canonical form per cnos.cds/skills/cds/CDS.md §"Development lifecycle" → §"Branch rule")
  • γ session branches: harness-given claude/... or operator-named gamma/session-{N} patterns
  • Any other branches merged into main during this release

No orphan γ session branches survive past closure.

Procedure (mirrors release/SKILL.md §2.6a):

git branch -r --merged origin/main \
  | grep -v main \
  | grep -v HEAD \
  | sed 's/origin\///' \
  | xargs -I{} git push origin --delete {}

For targeted deletion of a single cycle branch:

git push origin --delete cycle/399

Harness 403 / push-restriction case. Some Claude Code harness environments block remote branch deletion (403 on git push origin --delete <branch>; same constraint as operator/SKILL.md §5.2 consequence 3). δ attempts the delete; if the harness blocks it, δ records the failure in the cycle's close-out and proceeds — the merged-into-main state is authoritative. Branches blocked from delete in-cycle become an inter-cycle cleanup task (operator-as-human or a follow-on agent session with delete permission).

  • ❌ Leave merged branches accumulating ("someone might need them")
  • ❌ Treat a 403 on delete as a release failure (the merge is what matters; the branch is residue)
  • ✅ Attempt delete immediately after release; record any 403s in close-out; merged-to-main state is the source of truth

6. Disconnect-release rules

These are the hard rules δ enforces from the release-effector surface. Each maps to a precondition or a discipline failure mode evidenced by prior cycles.

  1. Do not tag/release before gamma-closeout.md exists on main. γ closure declaration gates the tag. (cnos.cds/skills/cds/CDS.md §"Artifact contract" → §"Ownership matrix"; gamma/SKILL.md §2.10. The doctrinal frame lives in operator/SKILL.md §3.4.)
  2. Manual git tag is not allowed. scripts/release.sh is the only way to tag a release.
  3. One tag per release. Per release/SKILL.md §3.6: amend the release commit and reuse the same bare-version tag on CI-red retry. Do not proliferate 3.9.1-fix, 3.9.1-fix2.
  4. Annotated tags only. Generated message via scripts/generate-release-tag-message.sh. Lightweight tags lose the structured metadata.
  5. δ blocks release completion until CI is green (or operator explicitly accepts a known pre-existing failure per §4 step 5).
  6. The tag is the signal. No separate completion announcement is needed for the disconnect tag itself — the tag appearing on main IS proof that all gate actions completed. Mid-cycle gate actions (per operator/SKILL.md §3.3) still require explicit completion confirmations; the disconnect tag does not. (The doctrinal claim lives in operator/SKILL.md §3.5; the mechanical fact that the tag is what δ pushes lives here.)
  7. No mixing v-prefixed and bare tags. Bare X.Y.Z only, per cnos.cds/skills/cds/CDS.md §"Artifact contract" → §"Location matrix".
  8. The release is structural, not consumer-driven. Untagged post-cycle patches on main are an open boundary; δ does not defer release because "no one is downstream right now." (The doctrinal claim lives in operator/SKILL.md §3.4.)

7. Docs-only disconnect (no tag)

When a cycle ships docs-only — no code change, no version bump — the disconnect is the merge commit on main, not a tag. Release-effector is not invoked:

  • scripts/release.sh is NOT run.
  • VERSION is unchanged.
  • The merge commit hash IS the disconnect signal.
  • Cycle directories move to .cdd/releases/docs/{ISO-date}/{N}/ (γ does this per release/SKILL.md §2.5b, not δ).
  • No CHANGELOG ledger row; no tag-message generation; no release CI; no branch cleanup driven from release-effector (any merged-branch cleanup runs as ordinary inter-cycle housekeeping under δ-policy, not under this skill).

See release/SKILL.md §2.5b for the docs-only flow and issue/SKILL.md for the docs-only mode declaration on the issue.

  • ❌ Run scripts/release.sh on a docs-only cycle "just to be safe" (version drift; synthetic bump)
  • ❌ Skip the cycle-directory move because no tag is being cut (loses cycle-to-release association)
  • ✅ γ moves cycle dirs to .cdd/releases/docs/{ISO-date}/{N}/; merge commit IS the disconnect

8. What this skill does NOT do

These are intentionally out of scope. Crossing them is a role-boundary violation or an authority claim release-effector does not hold.

  • Do not author RELEASE.md. That's γ per release/SKILL.md §2.5.
  • Do not write the CHANGELOG ledger row. That's β/γ per release/SKILL.md §2.4 and §3.8.
  • Do not decide the version bump. That's γ + β per release/SKILL.md §2.2.
  • Do not run scripts/validate-release-gate.sh outside the script's flow. β/CI may run it independently for pre-merge validation; release-effector triggers it transitively via the script's step 4 only.
  • Do not deploy. Deployment (binary distribution, host installation, daemon restart) is a separate effector surface — see release/SKILL.md §2.8 for the current deploy step (β/operator).
  • Do not declare the cycle closed. Cycle closure is γ's; release completion is δ's. The two are sequential: γ closes → δ tags → release complete.
  • Do not override the gate. Override is operator/SKILL.md §4 territory. Release-effector executes the mechanics; override is a δ-policy decision.

9. Cross-references

  • operator/SKILL.md §3 (outward gate policy: when the gate fires, who requests, completion-reporting protocol)
  • operator/SKILL.md §3.4 (doctrinal frame: the tag is the disconnection point)
  • operator/SKILL.md §3.5 (doctrinal frame: the tag is the signal)
  • operator/SKILL.md §4 (override protocol — used in §4 step 5 above)
  • operator/SKILL.md §7 (cycle lifecycle table — Disconnect row points here for mechanics)
  • release/SKILL.md §2.1 (pre-release validator; invoked transitively by script step 4)
  • release/SKILL.md §2.4 (CHANGELOG — γ/β surface; release-effector does not author)
  • release/SKILL.md §2.5 (RELEASE.md — γ surface; release-effector does not author)
  • release/SKILL.md §2.5a (cycle-dir move — γ surface; script step 7 idempotently handles too)
  • release/SKILL.md §2.5b (docs-only disconnect — release-effector is not invoked)
  • release/SKILL.md §2.6 (release-readiness signaling — β surface; precondition above)
  • release/SKILL.md §2.7 (release CI polling — β-side text says δ owns this per release-effector §3)
  • release/SKILL.md §3.6 (amend-don't-re-tag — invoked by §4 step 3 above)
  • release/SKILL.md §3.8 (TSC scoring — γ/β; release-effector does not score)
  • cnos.cds/skills/cds/CDS.md §"Artifact contract" → §"Location matrix" (canonical tag policy)
  • cnos.cds/skills/cds/CDS.md §"Artifact contract" → §"Ownership matrix" (gamma-closeout.md gates tag)
  • gamma/SKILL.md §2.10 (γ closure gate — verifies preconditions above)
  • gamma/SKILL.md §2 release-prep (γ writes RELEASE.md + moves cycle dirs before requesting δ disconnect)
  • scripts/release.sh (the script — δ does not reimplement)
  • scripts/validate-release-gate.sh (script step 4)
  • scripts/stamp-versions.sh (script step 5)
  • scripts/check-version-consistency.sh (script step 6)
  • scripts/generate-release-tag-message.sh (script step 10)

10. Embedded Kata

Scenario

γ has declared cycle/{N} closure. gamma-closeout.md is on main. RELEASE.md is at repo root. The cycle directory under .cdd/unreleased/{N}/ is ready to move. VERSION still reads the previous release's version. The current release will be 3.67.0.

Task

Execute the disconnect release.

Expected actions

  1. Verify preconditions. gamma-closeout.md on main; RELEASE.md on main; beta-closeout.md carries "release ready for δ tag" signal; local main matches origin/main.
  2. Run the script. scripts/release.sh 3.67.0 (or edit VERSION then scripts/release.sh).
  3. Watch the script's output. Expect: VERSION set, gate validated, manifests stamped, consistency OK, cycle dirs moved, release commit, tag annotated, push complete.
  4. Poll release CI. gh run list --branch 3.67.0, then gh run watch <id> --exit-status.
  5. CI Green path. Confirm completion to γ per operator/SKILL.md §3.3 ("Released 3.67.0 — CI green, binaries attached"). Delete merged cycle branches (git push origin --delete cycle/{N}).
  6. CI Red path. Execute §4 runbook: investigate, classify, fix-or-escalate, re-verify. Operator override only for pre-existing infrastructural failures.

Common failures

  • Manual git tag 3.67.0 && git push --tags instead of scripts/release.sh (skips stamp/consistency check; produces version drift).
  • Pushing the tag before gamma-closeout.md exists on main (γ closure gate violated).
  • Declaring release complete while CI is red without operator override (discipline failure; the gate has not actually closed).
  • Treating a 403 on git push origin --delete cycle/{N} as a release failure (the merge is authoritative; the branch is residue — record and move on).
  • Letting merged branches accumulate ("someone might need them") instead of deleting at release time.
  • Proliferating tags (3.67.0, 3.67.0-fix) instead of amend-and-re-tag on CI-red retry (release/SKILL.md §3.6).

04 同仓库更多 Skills

usurobor
usurobor/cnos

issues/triage

Issue triage doctrine pointer. Use when classifying a new issue, deciding dispatch eligibility, or deciding whether to close/supersede/defer.

★ 9
usurobor
usurobor/cnos

issues/taxonomy

Issue label taxonomy pointer. Use when classifying an issue's kind/area/priority/effort/status/protocol/dispatch labels, or when the board needs to interpret them.

★ 9
usurobor
usurobor/cnos

issues/map

Issue-board (Issue Pivot) generation domain. Use when reasoning about `cn issues map`'s fetch, taxonomy-parse, effort-computation, and render pipeline, or about the board's JSON/HTML output contract.

律师与法务前端开发
★ 9
usurobor
usurobor/cnos

issues

cnos.issues package entrypoint. Use when reasoning about issue taxonomy, the Issue Pivot / issue-board (`cn issues map`), the read-only CDS issue-state evaluator (`cn issues fsm evaluate`), or the current command-dispatch disposition of issue tooling.

★ 9
usurobor
usurobor/cnos

receipt-stream

Cross-cycle receipt-stream doctrine for protocol-iteration. Use when γ closes a cycle and writes the per-cycle iteration artifact (`cdd-iteration.md`), when γ updates the cross-cycle aggregator at `.cdd/iterations/INDEX.md`, when ε reads the receipt stream across cycles to surface protocol-gap patterns, when a finding's disposition lands a cross-repo patch and the cross-repo trace bundle attaches, or when a courtesy empty-findings stub is written for a `protocol_gap_count == 0` cycle.

★ 9
usurobor
usurobor/cnos

mid-flight

Mid-flight γ→in-flight-α rescue channel. Use when γ needs to change a load-bearing fact (pinned axis, scope, AC, blocker) after α has begun coding, when γ edits the cycle's GitHub issue body mid-cycle, or when a spec change lands on origin/main while α/β polling sessions are in-flight.

★ 9