RELEASING.md
Rama main · réplica de lectura del repositorio original.
## Releasing Rembric
Releases are fully automated. You never tag, bump, or publish by hand.
## Two release-please components
`release-please-config.json` is the source of truth. It declares exactly two components — one per deliverable — and **no** grouping plugin (`node-workspace`, `linked-versions`), so there is no cascade or anchor-tag dependency between them.
| Component | Path | Tag format | npm package-name | Bumps when commits touch… |
| --------- | -------------- | --------------- | ----------------- | ---------------------------------------------------------------- |
| `server` | `apps/web/` | `server-vX.Y.Z` | `@rembric/web` | anything under `apps/web/` |
| `plugin` | `apps/plugin/` | `plugin-vX.Y.Z` | `@rembric/plugin` | anything under `apps/plugin/` — a shared asset or any client dir |
The `plugin` component covers the **whole** `apps/plugin/` tree (it declares no `exclude-paths`), and its single version is shared by all five clients: Claude Code, Codex CLI, Hermes Agent, opencode, and Pi. The component's `extra-files` rewrite every client's version carrier in lock-step:
- `.claude-plugin/{package,plugin}.json`
- `.codex-plugin/{package,plugin}.json`
- `.hermes-plugin/plugin.yaml`
- the `// @rembric-plugin-version` comment in `.opencode-plugin/plugin.ts`
- `.pi-plugin/package.json` — also the version published to npm as `@rembric/pi`
So a fix scoped to one client bumps the number every other client reports. That is deliberate: `apps/plugin/CHANGELOG.md`, scoped by conventional commit, is what records which client actually changed.
The retired per-client components (`claude-code-plugin`, `codex-plugin`, `hermes-plugin`, `opencode-plugin`, `plugin-shared`) and their tags remain in git history, inert; release-please no longer creates them. Legacy pre-restructure `vX.Y.Z` tags remain too — `ghcr.io/susomejias/rembric:v0.17.0` stays pullable. There is no rolled-up "Rembric vX.Y.Z" release line.
## The flow
```
feat: / fix: commit release-please opens merge release PR
on main ─▶ one or more "release: <component> vX.Y.Z" ─▶ tags + GH releases
PRs (one per affected component) │
release-please.yml
checks which component
released
┌─────────┴─────────┐
server │ │ plugin
▼ ▼
docker-publish.yml publish-npm job
(workflow_call) npm publish --provenance
ghcr.io/susomejias/ @rembric/pi
rembric:<server-tag>
```
Both gates live in `.github/workflows/release-please.yml`, one per component:
```yaml
# publish-docker — rebuilds the server image
needs.release-please.outputs.server_release_created == 'true'
# publish-npm — publishes @rembric/pi (the Pi client extension)
needs.release-please.outputs.plugin_release_created == 'true'
```
A `plugin` release does **not** rebuild the server image, and a `server` release publishes **nothing** to npm. The npm publish authenticates via trusted-publishing OIDC (`permissions: id-token: write`, scoped to the `publish-npm` job) and carries provenance; there is no stored registry token, and introducing one requires an OpenSpec change against `supply-chain-hygiene`.
If a single commit touches both server and plugin paths, release-please opens separate PRs per component; merging the server PR is what triggers Docker, and merging the plugin PR is what triggers the npm publish.
## Day-to-day
Use [Conventional Commits](https://www.conventionalcommits.org/) — the `commit-msg` hook enforces this. Allowed types live in `CONTRIBUTING.md`. Release-affecting types:
- `feat: …` → bumps the **minor** version of every component whose path the commit touches
- `fix: …` / `perf: …` → bumps the **patch** version
- `feat!: …` or `BREAKING CHANGE:` footer → bumps the **major** version
- `chore`, `docs`, `test`, `build`, `ci`, `style` → no version bump, no changelog entry
Scope the commit so the path is unambiguous. Examples:
```bash
git commit -m "feat(consolidation): add drift heuristic for tag overlap" # → server
git commit -m "fix(bridge): handle missing PWD" # → plugin
git commit -m "feat(opencode): per-session summary on dispose" # → plugin
git commit -m "fix(hermes): tighten is_available 401 handling" # → plugin
```
Every client lives under `apps/plugin/`, so any of the last three bumps the one shared `plugin` version; the scope is what tells the CHANGELOG which client changed.
**To cut a release**: merge the relevant release PR. Merging the `server` PR triggers `docker-publish.yml`; the multi-arch image lands at `ghcr.io/susomejias/rembric:<server-version>` ~5–8 minutes later. Merging the `plugin` PR triggers `publish-npm`; `@rembric/pi` appears on the registry at the same version as the `plugin-vX.Y.Z` tag.
## On the server
```bash
docker compose pull && docker compose up -d
```
Migrations apply automatically on the next start (the app opens SQLite via `packages/db`'s `createDb`, which calls `migrate()` during boot).
## Hotfix path
```bash
git checkout -b hotfix/some-fix main
git commit -m "fix(mcp): reject empty Authorization header with 401"
git push -u origin hotfix/some-fix
# open a PR, merge it → release-please bumps server-vX.Y.(Z+1) on next push to main
```
## Manual recovery
`docker-publish.yml` exposes `workflow_dispatch` with a `tag` input for one-off rebuilds (e.g. base-image CVE patch without bumping server). `release-please.yml` also supports `workflow_dispatch` with `force_publish: true` to fire Docker publish for the current `main` even if release-please didn't cut a server release on the most recent push.