| name | release-management |
| description | Use when cutting, shipping, promoting, diagnosing, or previewing DomainForge releases, release-please PRs, component tags, deploy triggers, or release readiness. |
Release Management
Overview
DomainForge releases use release-please manifest mode on main, independent
per-package versions, component-prefixed tags, and a tag-triggered deploy
workflow that dispatches to existing publish workflows. All protected-branch
checks on main must pass.
Current Release Model
| Package key | Component | Tag | Layout |
|---|
domainforge-core | domainforge-core | domainforge-core-vX.Y.Z | domainforge-core/ |
domainforge-python | domainforge | domainforge-vX.Y.Z | domainforge-python/ |
domainforge-typescript | domainforge-typescript | domainforge-typescript-vX.Y.Z | domainforge-typescript/ |
Release PRs are opened or updated by .github/workflows/release-please.yml
after pushes to main. Merging a release-please PR creates the matching
component tag. Component tags trigger .github/workflows/deploy.yml, which
parses the tag and dispatches to the appropriate reusable publish workflow:
domainforge-core-v* → release-crates.yml (crates.io)
domainforge-v* → release-pypi.yml (PyPI wheels, multi-platform matrix)
domainforge-typescript-v* → release-npm.yml (npm + WASM)
There is intentionally no separate prod GitHub environment gate. This is a
solo-maintainer repository: CI on main is the merge gate, and a component
tag push is the publish signal. The publish workflows handle their own
idempotency (--skip-existing, npm view checks, cargo "already published"
detection).
Treat older v*.*.* release scripts and docs/RELEASE_PROCESS.md as legacy
unless the user explicitly asks for that path.
Hard Rules
- Never hand-edit changelogs, manifests, or package versions for a release.
release-please owns those files.
- Never push release tags manually. Tags come from merging release-please PRs.
- Always run a dry-run before saying what a release will contain.
- Check commit scopes before blaming release-please. Invalid scopes can place
changes in the wrong package or hide them from a component changelog.
- Do not assume a
prod environment or branch exists. This repo only has
main. Tag push → publish workflow → registry.
Quick Commands
| Need | Command |
|---|
| Preview release plan | .codex/skills/release-management/scripts/release-dryrun.sh |
| Required checks on a PR | gh pr checks <number> |
| Release PRs | gh pr list --search "release in:title" --state open |
| Tags on origin | git ls-remote --tags origin 'refs/tags/*-v*' |
| Deploy runs | gh run list --workflow deploy.yml --limit 10 |
Workflows
What will the next release contain?
Run the dry-run script from the repo root. Report each component's current
version, proposed version, bump reason, and changelog delta. Do not commit.
Cut or ship a release
- Confirm
main is green with the latest required checks.
- Run the dry-run and summarize proposed component releases.
- Confirm release PRs exist. If missing, inspect
release-please.yml runs and
CREATE_PR_TOKEN; GITHUB_TOKEN cannot trigger downstream tag workflows.
- If the user explicitly authorizes merging, merge the release PR. Otherwise
tell the user which release PR to merge.
- After merge, verify the component tag exists and the expected publish
workflow (
release-crates.yml / release-pypi.yml / release-npm.yml)
started via deploy.yml.
Publish did not trigger
Check in order:
- Tag exists:
git ls-remote --tags origin 'refs/tags/*-v*'.
- Tag matches
deploy.yml: domainforge-core-v*, domainforge-v*, or domainforge-typescript-v*.
- Tag came from release-please using
CREATE_PR_TOKEN, not GITHUB_TOKEN.
deploy.yml has not been disabled and Actions permissions are healthy.
- The dispatchable publish workflow file exists and is not disabled.
Add a package to release-please
- Add package config to
release-please-config.json.
- Add current version to
.release-please-manifest.json.
- Create a directory at repo root named after the package key (release-please
resolves file paths as
<package-key>/<file>).
- Move the package's version file (pyproject.toml, package.json, Cargo.toml)
into that directory with relative paths adjusted to reach
domainforge-core/.
- Add a
deploy-<component> job to deploy.yml that dispatches to the
appropriate publish workflow, gated by if: needs.identify.outputs.component == '<component>'.
- Add the component to
commitlint.config.cjs scope-enum (if you maintain one).
- Update
docs/governance.md.
- Run the dry-run and focused workflow validation.
Rename a component / bootstrap a renamed package
Renaming a component (e.g. sea-core -> domainforge-core) or relaunching
under new registry names breaks release-please's continuity: the old
<old-component>-v* tags no longer match the new component names, so
release-please reports No user facing commits found since - skipping. The
historical commits also reference the old paths, so path-based attribution
stops matching, and a rename committed as a hidden type (chore) does not
trigger a release on its own.
To cut the first release under the new names:
- Update
release-please-config.json package keys, component, and
package-name to the new names (done by the rename commit).
- Update
.release-please-manifest.json keys to the new package keys with the
current version as the starting point.
- Add a per-package
"release-as": "<X.Y.Z>" to release-please-config.json
for each renamed component to force the bootstrap release at the intended
version. release-please then opens a release PR even though no conventional
user-facing commit is attributed to the new components yet.
- Merge the release PR, confirm the new
<component>-v* tags appear, and that
deploy.yml dispatches the publish workflows.
- After the first release ships, remove
release-as from the config —
otherwise it pins every future release to that version.
Common Mistakes
| Mistake | Correction |
|---|
Looking for dev, stage, or prod branches | Use main; this repo has only main. Tags trigger publish directly. |
Looking for prod environment gate | None exists. Publish workflows are dispatched by deploy.yml on tag push. |
| Asking for approval before merge | Required approvals are 0; required checks still block merge. |
| Manually creating tags to unblock publish | Fix release-please or merge the release PR. |
| Treating old local release scripts as canonical | Use release-please unless the user requests legacy scripts. |
| Putting package version files at repo root | release-please requires them under <package-key>/. Root-level packages must have a directory. |
Treating deploy.yml as a builder | deploy.yml is a router. The publish workflows own build + publish logic. |
Listing the workspace-root Cargo.lock in a package's extra-files | release-please resolves extra-files relative to the package dir and rejects .. (illegal pathing characters). For domainforge-core (whose lockfile lives at the workspace root), omit Cargo.lock from extra-files — the rust strategy bumps Cargo.toml, and the lockfile regenerates during the publish build. |
Leaving release-as in the config after the bootstrap release | release-as pins the version. Add it to force the first release of a renamed component, then remove it once the tag lands. |
References
- Config:
release-please-config.json, .release-please-manifest.json
- Router:
.github/workflows/deploy.yml
- Publish workflows:
.github/workflows/release-crates.yml, release-pypi.yml, release-npm.yml
- Release-please trigger:
.github/workflows/release-please.yml
- Governance:
docs/governance.md
- Commit rules:
commitlint.config.cjs