This repository uses a main-first GitFlow optimized for public SDK/model packages inside a monorepo with a fork-first contribution model.
- All development merges into
main. - All contributor work comes from forks (no pushing branches in the upstream org repo).
- Releases are published from versioned
release-<package>-<x.y.z>branches that exist in the upstream org repo. - Release/patch changes land via fork PRs into
release-*, not via upstream branch-to-branch merges. - Changes are done as two PRs: (1) fork →
main(dev fix), (2) fork →release-*(patch release). feature-*andtmp-*are non-release streams (GitHub Packages only), used to share dev builds safely.- CI/CD is path-scoped so only the impacted package(s) build/publish.
Why this exists: predictable releases across many packages, fast day-to-day development on
main, and safe CI/CD in a public repo with forks.
- Repo conventions
- Working with forks
- Branch types
- Branch retention & deletion
- Release flow
- Patch flow
- Feature & temp flows
- Release PR enforcement
- CI/CD routing requirements
- Quick reference
- Packages live under:
packages/<package>/ - Each package owns its own:
packages/<package>/package.jsonpackages/<package>/CHANGELOG.md(or equivalent changelog file)
- CI workflows are:
- Scoped by
paths: packages/<package>/** - Support a
workdirinput (or env var) pointing at the package folder
- Scoped by
- Publishing targets:
- GitHub Packages (GPR) for development streams (
dev,feature,temp) - NPM for release streams (
latest, optionallynext/betawhen explicitly needed)
- GitHub Packages (GPR) for development streams (
This repo assumes a fork-first workflow:
- Contributors do not push branches to the upstream org repo (the “Tether” repo).
- The upstream org repo is the source of truth for:
main- all
release-*branches (NPM publishing lines) - any upstream
feature-*/tmp-*branches (when we need a shared dev stream that publishes)
- Contributors create branches in their fork, then open PRs into upstream target branches.
- Maintainers can create branches within the org if they chose to, while updates to the release branch must come from the fork.
- Maintainers (upstream write access):
- Create upstream
release-*branches (and optional upstreamfeature-*/tmp-*branches when needed) - Approve/merge PRs
- Own CI/CD + publishing permissions
- Create upstream
- Contributors (fork-only by default):
- Create branches in their fork
- Open PRs into upstream
- Never merge upstream branches into other upstream branches
-
Upstream branches are targets, not workspaces
You don’t “work on” upstreammain/release-*/feature-*/tmp-*directly. You PR into them from your fork. -
No cross-merging inside upstream
No PRs likemain -> release-*orrelease-* -> maincreated from upstream branches.
If you need commits frommainin a release line, you cherry-pick locally in your fork and PR into the release branch. -
Publishing happens on merge to upstream
- Merge to
maincan publish dev builds (GitHub Packages) when package paths changed. - Merge to
release-*can publish to NPM for that package/version. - Merge to
feature-*/tmp-*can publish feature/temp builds (GitHub Packages).
- Merge to
# Fork in GitHub UI, then:
git clone https://github.com/<you>/<repo>.git
cd <repo>
git remote add upstream https://github.com/tetherto/<repo>.git
# Sync your local main to upstream main
git fetch upstream
git checkout main
git reset --hard upstream/main
git push origin main --force-with-leasegit fetch upstream
git checkout main
git reset --hard upstream/main
git checkout -b feature-<package>-<short-desc>
# make changes...
git push -u origin feature-<package>-<short-desc>
# Open PR: <your-fork>:feature-... -> upstream:main| Branch type | Pattern | Created in upstream by | Purpose | Publishes to | Notes |
|---|---|---|---|---|---|
| Main | main |
Maintainers | All active development | GitHub Packages (dev) | Default integration branch |
| Release | release-<package>-<x.y.z> |
Maintainers | Versioned release line | NPM | Stable releases only |
| Feature | feature-<package>-* |
Optional (maintainers) | Share a dev build for a large/isolated effort | GitHub Packages (feature) | Never publish to NPM |
| Temp | tmp-<package>-* |
Optional (maintainers) | Experiments / QA previews | GitHub Packages (temp) | Never publish to NPM |
Publishing semantics
- GitHub Packages dist-tags:
dev,feature,temp - NPM dist-tags:
latest(stable); optionallynext/betawhen explicitly requested
Dev build versioning (run-id traceability)
- Development publishes (GitHub Packages) should be uniquely traceable.
- CI will append a build identifier (e.g., run id) to the published version at publish time (implementation detail of CI), so multiple dev publishes don’t collide.
- Example shape:
x.y.z-dev.<run_id>(exact formatting is owned by CI)
- Example shape:
- Do not commit run-id version bumps to git.
Upstream branches are kept or cleaned up according to a separate, enforced policy documented in branch-retention.md:
mainand the branch backing each package's current latest published version are never deleted, nor is any branch with an open PR, branch protection, or a WIP flag.release-*branches follow a nested-semver window (latest 2 majors / 3 minors / 3 patches per package); older lines are eligible for deletion because the shipped code is preserved permanently by the release tag + npm artifact.feature-*/tmp-*are deleted after 2 months of inactivity; ad-hoc branches after 1 month.- A weekly GitHub Action flags candidates, waits a ~7-day grace period via a tracking issue, then deletes — applied across all qvac repos.
See branch-retention.md for the full rules, the safelist, and how to adopt the automation in another repo.
Release branches are created in the upstream org repo and are named per package and version:
# maintainer action in upstream repo
git checkout main
git pull
git checkout -b release-<package>-<x.y.z>
git push -u origin release-<package>-<x.y.z>Contributors: do not create
release-*branches in upstream. You will PR into them from your fork.
PR source: <your-fork>:<branch> → target: upstream:release-<package>-<x.y.z>
The PR must include:
- Version bump in
packages/<package>/package.json→x.y.z - Changelog update in
packages/<package>/CHANGELOG.md
PR naming suggestion:
release(<package>): v<x.y.z>
On merge to the upstream release-* branch, CI should:
- Build/package the artifact for
packages/<package> - Publish to NPM
- Create a git tag and GitHub release
Tag format (standard):
<package>-v<x.y.z>
Example:sdk-v1.0.0
We do not merge upstream release-* back into upstream main directly.
Instead, ensure main reflects the shipped version + changelog via one of these patterns:
- Preferred (planning discipline): the release branch is cut from a
mainthat already contains the intended changelog/version context (often true when release prep happens onmainfirst). - Otherwise (common): open a follow-up PR from a fork into upstream
mainthat applies the same version/changelog changes.
Goal:
mainremains the single source of truth for current development state, while release lines are controlled targets for NPM publishing.
- Do the work in your fork branch.
- Open PR into upstream
main. - Merge into upstream
main.
This keeps ongoing development correct and ensures the fix exists on the primary development line.
If patching the currently released line, create a new patch branch from the previous release branch:
# maintainer action in upstream repo
git checkout release-<package>-<x.y.z>
git pull
git checkout -b release-<package>-<x.y.(z+1)>
git push -u origin release-<package>-<x.y.(z+1)>Contributors: do not branch in upstream; you will PR into
release-<package>-<x.y.(z+1)>from your fork.
You will pull the upstream release branch into your local repo, then cherry-pick the fix commit(s) that already landed in upstream main.
git fetch upstream
# create a branch in your fork based on the upstream patch branch
git checkout -b patch-<package>-<x.y.(z+1)> upstream/release-<package>-<x.y.(z+1)>
# cherry-pick the fix commit(s) from upstream main
git cherry-pick <commit_sha_from_upstream_main>
# update required metadata
# - bump packages/<package>/package.json to x.y.(z+1)
# - update packages/<package>/CHANGELOG.md
git push -u origin patch-<package>-<x.y.(z+1)>PR source: <your-fork>:patch-... → target: upstream:release-<package>-<x.y.(z+1)>
The PR must include:
- Version bump to
x.y.(z+1) - Changelog entry describing the patch
On merge to the upstream patch branch, CI should:
- Publish to NPM
- Tag + GitHub release using
<package>-v<x.y.(z+1)>
Because the fix first landed on main (PR #1), main already contains the functional change.
Only the release-line version/changelog metadata may require a follow-up PR back to main if your process doesn’t prep release metadata on main first.
These branches are used to produce shareable dev builds without entering the immediate release train.
Use for a larger change where you need a development package to share with others.
Branch location
- If we need a persistent shared stream that publishes reliably, a maintainer may create a
feature-*branch in upstream. - Contributors PR into that upstream
feature-*branch from forks.
CI behavior (on merge/push to upstream feature-*):
- Build on changes scoped to the package path
- Publish to GitHub Packages with dist-tag
feature - Dev version includes a build identifier (run id) for traceability
- Never publish to NPM
- Do not create git tags or GitHub releases
Use for QA previews and experiments.
Branch location + PR rules are the same as feature branches:
- Upstream
tmp-*exists only when we need upstream workflows/publishing. - Contributors PR from forks into upstream
tmp-*.
CI behavior (on merge/push to upstream tmp-*):
- Publish to GitHub Packages with dist-tag
temp - Dev version includes a build identifier (run id) for traceability
- Never publish to NPM
- Do not create git tags or GitHub releases
For all release-* branches:
✅ Required in PR:
packages/<package>/package.jsonversion must increase vs basepackages/<package>/CHANGELOG.mdmust be updated
❌ CI will fail if:
- Version is unchanged / not incremented
- Changelog is missing or unchanged
To reflect branch intent correctly and keep fork PRs safe:
- Never publish from PR events.
- Publishing requires credentials and must only occur on merge to upstream branches.
- Publish jobs should trigger on:
pushto upstreammain/feature-*/tmp-*pushto upstreamrelease-*(or tags created from them)
-
NPM publish jobs
- Trigger only on
release-*branches (or tags created from them) - Must require version + changelog checks
- Trigger only on
-
GitHub Packages publish jobs
- Trigger on upstream:
main→ dist-tagdevfeature-*→ dist-tagfeaturetmp-*→ dist-tagtemp
- Trigger on upstream:
- All package workflows trigger on:
paths: packages/<package>/**
- Reusable workflows accept a
workdirinput pointing at the package directory.
mainrelease-<package>-<x.y.z>feature-<package>-<anything>tmp-<package>-<anything>
- Maintainer: create upstream
release-<package>-<x.y.z>frommain - Contributor: fork PR into upstream
release-<package>-<x.y.z>with:package.jsonbumped tox.y.z- changelog updated
- Merge → CI publishes to NPM and tags
<package>-v<x.y.z>
- PR #1: fork → upstream
main(the fix) - Maintainer: create upstream
release-<package>-<x.y.(z+1)>from previous release branch - PR #2: cherry-pick fix commit(s) into fork branch → PR into upstream patch branch with:
- version bump
x.y.(z+1) - changelog updated
- version bump
- Merge → CI publishes patch to NPM and tags
<package>-v<x.y.(z+1)>
- Upstream
feature-*/tmp-*exist only when we need a shared publishing stream. - Contributors PR from forks into those upstream branches.
- CI publishes to GitHub Packages with dist-tags
feature/tempand run-id traceability.



