Operational procedures for trellis. For day-to-day development conventions
see AGENTS.md; for architecture see
docs/architecture.mmd and SPEC.md.
This runbook covers:
- Cutting a release.
- Triaging a failed publish.
- Rolling back a bad release.
Key facts:
- Package:
@os-eco/trellis-cli - Primary branch:
main - Release workflow:
.github/workflows/publish.yml(version-gated) - Version sources (must agree):
package.json"version"andsrc/index.tsexport const VERSION - Changelog:
CHANGELOG.md - Tracker prefix:
trellis- - Package smoke test:
bun run smoke:package(scripts/smoke-package.ts) — packs the tarball and confirms the deterministic analyzer ships complete: bin entry, runtime dependencies, analyzer assets, and a real audit of a fixture workspace through the packed CLI.
bun --version≥ the version inpackage.jsonengines.bun.gh auth status→ authenticated, withrepo+workflowscopes.git remote -vshows the canonical origin.- For npm publish:
npm whoami→ publisher account; 2FA enabled. - Local working tree on
main, fully up to date,git statusclean.
Cut releases from main only. Never tag a feature branch. The /release
slash command (.claude/commands/release.md) automates §1.1–§1.4.
Follow SemVer. trellis is pre-1.0 while package.json
"version" starts with 0.; while pre-1.0, breaking changes go in MINOR and
additive changes go in PATCH. Default to PATCH unless explicitly bumping
minor/major.
package.json and src/index.ts must agree — the release workflow fails the
job if they disagree.
# bump package.json
sed -i '' 's/"version": ".*"/"version": "X.Y.Z"/' package.json
# bump src/index.ts: export const VERSION = "X.Y.Z";Use git diff to confirm both moved. Commit:
git add package.json src/index.ts
git commit -m "release: trellis X.Y.Z"CHANGELOG.md must have a dated entry for the new version at the top, with
items moved out of [Unreleased]:
## [X.Y.Z] — YYYY-MM-DD
### Added
- ...
### Changed
- ...
### Fixed
- ...Group under standard headings (Added / Changed / Fixed / Deprecated / Removed /
Security). Link entries to trellis-XXXX or #NNN. Commit (or squash with the
version commit — be consistent):
git add CHANGELOG.md
git commit -m "release: changelog for X.Y.Z"bun install
bun run lint
bun run typecheck
bun test
bun run check:all
bun run smoke:packageAll must exit 0. If any fails, stop — fix locally and re-run. The smoke test is the last line of defense against shipping a tarball that omits an analyzer asset or dependency (it packs, unpacks, and audits a fixture through the packed CLI — offline, no registry involved).
git push origin mainPushing triggers .github/workflows/publish.yml, which:
- Re-runs the gate suite in CI.
- Asserts
package.jsonandsrc/index.tsagree onX.Y.Z. - Builds + publishes
@os-eco/trellis-clito npm (with provenance). - Tags
vX.Y.Zand creates a GitHub release with theCHANGELOG.mdsection as the body.
Watch it live:
gh run watchgit pull --tags
git tag --list | tail -5 # confirm vX.Y.Z is present
gh release view vX.Y.Z # confirm release page renders
npm view @os-eco/trellis-cli version # confirm the published versionSmoke-install in a clean dir and run a real audit — the published package must measure a workspace, not just boot:
mkdir /tmp/trellis-smoke && cd /tmp/trellis-smoke
bun install @os-eco/trellis-cli
bunx @os-eco/trellis-cli --version
mkdir fixture && printf 'export const x: number = 1;\n' > fixture/x.ts
bunx @os-eco/trellis-cli audit fixture --json | head -20The audit must exit 0 and print a §6.4 report whose analyzerVersion
matches the release. (The audit is offline and stateless — no Git, network,
or database needed, so a clean-dir smoke is a faithful install check.)
When .github/workflows/publish.yml exits non-zero:
gh run view --log-failed| Symptom | Likely cause | Fix |
|---|---|---|
version mismatch |
package.json / src/index.ts disagree |
Sync versions, push fix commit. |
npm publish ... 403 |
Missing/expired NPM_TOKEN secret |
Settings → Secrets → update NPM_TOKEN, re-run. |
npm publish ... E409 |
Version already published | Bump to next patch; do not unpublish a live version. |
gh release create ... already exists |
Tag exists, prior run left an incomplete release | Delete the orphan release in the UI, re-run. |
tsc / biome / bun test failure |
Local greens diverged from CI | Reproduce with bun run check:all; do not force-push to main. |
After the fix commit lands on main:
gh workflow run publish.yml --ref mainOr push a no-op commit (git commit --allow-empty -m "release: retry") if the
workflow only triggers on push.
If npm publish completed but gh release create failed (or vice versa), do
not unpublish. Instead:
- Create the missing GitHub release manually:
gh release create vX.Y.Z --notes-file <(awk '/^## \[X.Y.Z\]/,/^## \[/' CHANGELOG.md) - Or, if npm has the version but the tag is missing:
git tag vX.Y.Z && git push origin vX.Y.Z
Record the deviation in trellis-XXXX.
A "rollback" never means unpublishing — npm and git tags are immutable. Rollback means publishing a corrective version.
- Critical (data loss, security, total breakage): cut a new patch reverting the change, under 30 minutes.
- High (regression on a common path): cut a patch within the day.
- Medium/Low: fix forward on the next planned release.
git checkout main && git pull
git log --oneline -10
git revert <bad-sha> # new commit, preserves historyFollow §1.1–§1.5. Note the rollback explicitly in CHANGELOG.md:
## [X.Y.(Z+1)] — YYYY-MM-DD
### Fixed
- Reverted <bad-commit-summary> from X.Y.Z which caused <symptom>.
Tracking in trellis-XXXX.If the bad version is dangerous:
npm deprecate @os-eco/trellis-cli@X.Y.Z "Critical bug; install X.Y.(Z+1) or later. See CHANGELOG.md."npm deprecate does not remove the version (which would break deterministic
installs); it surfaces a warning at install time.
- Add a banner to the GitHub release notes for
vX.Y.Z:> ⚠️ This release contains a regression. Use vX.Y.(Z+1) or later. - File
trellis-XXXXwith root cause + remediation links.
# Inspect recent releases
git tag --sort=-creatordate | head -5
gh release list --limit 5
# Inspect a failing workflow run
gh run list --workflow=publish.yml --limit 5
gh run view <run-id> --log-failed
gh run rerun <run-id> --failed-
package.json+src/index.tsupdated to X.Y.Z and in agreement. -
CHANGELOG.mdhas a dated[X.Y.Z]section. -
bun run check:allexits 0 locally. -
bun run smoke:packageexits 0 (packed tarball ships the analyzer). -
gh run watchconfirmed the release workflow succeeded. -
npm view @os-eco/trellis-cli versionreports X.Y.Z. - Smoke install in a clean dir succeeds.
- GitHub release page renders the changelog section correctly.
Prepare tools separately with bun install --frozen-lockfile. Run
bun run smoke:provider-tools, bun run smoke:package, and the full gates.
Use bun scripts/provider-acceptance.ts /path/to/prepared/representative
under OS network denial; the command installs nothing. Record actual host,
runtime/parser versions, source hashes, timings, memory measurement semantics
and incomplete evidence. A skipped real-tool test is not acceptance.
See provider-acceptance.md for the executed macOS command, failure matrix and combined Knip integration, and quality-evidence.md for migration examples. Provider upgrades require new digests and conformance observations; preserve native scoring and treat incompatible provider evidence as noncomparable.