Add a release skill: version, changelog, pre-deploy checks, no-ff promotion

Seto Elkahfi committed Jul 5, 2026 at 23:46 UTC c51370ea65da5cd32e98a2b09b46c137f4cb0864
1 file changed +180
.agents/skills/release/SKILL.md new
+180
@@ -0,0 +1,180 @@
1 +---
2 +name: release
3 +description: How to cut a sigit.si release — bump the version, write the changelog entry, run the full pre-deploy check, promote development to main (no-ff), deploy, and verify. Use this whenever the user asks to release, ship, deploy, promote development/main, bump the version, add a changelog entry, or asks "is this safe to deploy?" — even if they only mention one of those pieces.
4 +---
5 +
6 +# Releasing sigit.si
7 +
8 +> Server mechanics (SSH, Puma, hook internals, Postgres admin) live in the
9 +> [deployment skill](../deployment/SKILL.md). This skill is the release
10 +> process around them: what a release *is* in this repo, the checks that gate
11 +> it, and the order of operations.
12 +
13 +## The shape of a release
14 +
15 +Features land on `development` through PRs. A release is:
16 +
17 +1. A **release prep commit** on `development` — version bump + changelog entry.
18 +2. The **full pre-deploy check** (below) against the `development` tip.
19 +3. A **no-fast-forward merge** of `development` into `main`.
20 +4. **Deploy**: push `main` to the prod remote.
21 +5. **Post-deploy verification**.
22 +
23 +Two conventions to respect:
24 +
25 +- **Promotions to main are explicit merge commits**, named for the release:
26 + `Merge development into main for the vX.Y.Z release`. This is convention,
27 + not git config — don't fast-forward even though git would let you.
28 +- **Merging to main and deploying are deliberate operator actions.** Prepare
29 + everything, but get an explicit go-ahead before pushing `main` anywhere.
30 +
31 +## 1. Release prep commit (on development)
32 +
33 +Two files change; nothing else needs wiring.
34 +
35 +**`lib/sigitsi/version.rb`** — bump `Sigitsi::VERSION` per semver (the file
36 +comment spells it out: MAJOR incompatible, MINOR features, PATCH fixes). The
37 +value surfaces in the site footer.
38 +
39 +**`config/changelog/vX.Y.Z.md`** — one new file per release. The `Changelog`
40 +service (`app/services/changelog.rb`) auto-discovers files matching
41 +`v<major>.<minor>.<patch>.md`, sorts numerically by version (so 1.10.0 >
42 +1.9.0 — file dates and mtimes don't matter), and renders them at `/changelog`.
43 +Malformed files are silently skipped, so verify your entry actually appears
44 +(e.g. `bin/rails runner 'puts Changelog.latest&.version'`). Format:
45 +
46 +```markdown
47 +---
48 +date: 2026-07-05
49 +title: Short release name
50 +headline: One sentence shown on the changelog index.
51 +---
52 +
53 +Markdown body. Look at existing entries in config/changelog/ for the voice:
54 +plain prose, feature sections with ## headings, concrete over promotional.
55 +```
56 +
57 +Commit both together (`Release vX.Y.Z: <one-line summary>` works as a
58 +message), push to `development` (via PR or directly, matching however the
59 +user is currently working).
60 +
61 +## 2. Full pre-deploy check
62 +
63 +Run all of these against the exact tip being released. They mirror CI plus
64 +the production-only failure modes CI can't see. Don't skip the boring ones —
65 +each earned its place by failing for real at least once.
66 +
67 +```bash
68 +bundle install # lockfile sane, native gems build
69 +
70 +# Schema drift: schema.rb must match the migration set exactly. Rebuild from
71 +# migrations on an EMPTY db and diff. Two traps: (a) if schema.rb is present
72 +# during db:prepare/migrate it can mark migrations "up" without running them
73 +# (assume_migrated_upto), so move it aside first; (b) the dev database is
74 +# shared across worktrees/branches — other branches' tables can leak into a
75 +# dump. A from-scratch migrate on a dropped DB avoids both.
76 +mv db/schema.rb /tmp/schema.committed.rb
77 +RAILS_ENV=test bin/rails db:drop db:create db:migrate
78 +diff /tmp/schema.committed.rb db/schema.rb # empty diff or explain why not
79 +git checkout db/schema.rb
80 +
81 +# Full suite on a freshly loaded test DB (stale test DBs cause phantom
82 +# uniqueness failures). Tailwind must be built or view specs fail on assets.
83 +bundle exec rails tailwindcss:build
84 +RAILS_ENV=test bin/rails db:drop db:create db:schema:load
85 +bundle exec rspec # expect 0 failures
86 +
87 +# The four CI gates, locally:
88 +bin/brakeman --no-pager
89 +bin/bundler-audit check --update
90 +bin/importmap audit
91 +bin/rubocop
92 +
93 +# Production boot check. This is the one that catches site-down bugs: any
94 +# initializer that raises in production (missing keys, required env) fails
95 +# here, exactly as it would under the deploy hook. Supply dummies for values
96 +# the initializers merely require to be present.
97 +RAILS_ENV=production SECRET_KEY_BASE=dummy bin/rails assets:precompile
98 +```
99 +
100 +### Config/environment audit
101 +
102 +New code often needs new production config, and the deploy hook will not
103 +tell you it's missing — Puma just fails to boot, or a feature silently 500s.
104 +
105 +```bash
106 +# What did this release add?
107 +git diff origin/main -- .env.example # documented new vars
108 +git diff origin/main --stat -- config/initializers/ # new boot-time requirements
109 +
110 +# Is prod ready for them? (names/presence only — never print secret values)
111 +ssh smb1-deploy 'sudo -u git bash -lc "printenv | grep -oE \"^SOME_NEW_VAR[A-Z_]*\""'
112 +```
113 +
114 +Notes that save time:
115 +
116 +- The `git` user's `~/.profile` is the env source; the post-receive hook
117 + starts with `. ~/.profile`, so vars set there do reach `db:prepare`, Puma,
118 + and `bin/jobs`. Rails credentials are the alternative for multi-line
119 + secrets (private keys) — see the runbooks under `docs/`.
120 +- Beware shell false positives when checking remotely: `printenv | grep -c X`
121 + can match `SUDO_COMMAND` (your own command line), and `pgrep -f X` over ssh
122 + matches its own invocation. Anchor patterns (`^X=`) and exclude the
123 + grep/ssh process before trusting a count.
124 +
125 +### Production database state
126 +
127 +```bash
128 +# Pending migrations on prod (expected: the release's new ones, nothing odd)
129 +ssh smb1-deploy 'sudo -u git bash -lc "cd /home/git/apps/sigitsi && bin/rails db:migrate:status | tail"'
130 +
131 +# Solid Queue: the queue DB must hold solid_queue_* tables (db:prepare loads
132 +# db/queue_schema.rb; verify rather than assume — see deployment skill
133 +# "Known issues")
134 +ssh smb1-deploy 'sudo -u postgres psql sigitsi_production_queue -c "\dt"'
135 +```
136 +
137 +## 3. Promote development to main
138 +
139 +Only after the user says go:
140 +
141 +```bash
142 +git checkout main && git pull origin main
143 +git merge --no-ff development -m "Merge development into main for the vX.Y.Z release"
144 +git push origin main
145 +```
146 +
147 +## 4. Deploy
148 +
149 +```bash
150 +git push smbcloud main # the bare-repo post-receive hook does the rest
151 +```
152 +
153 +Remember the hook's sharp edges (deployment skill has the full list): no
154 +`set -e`, migration output goes to the pushing terminal — **read the push
155 +output**, it is the only record of `db:prepare`'s success — and `bin/jobs
156 +start` stacks a new Solid Queue supervisor on every deploy unless the hook
157 +has been amended with a `pkill -f solid_queue || true` line first.
158 +
159 +## 5. Post-deploy verification
160 +
161 +```bash
162 +# Schema arrived
163 +ssh smb1-deploy 'sudo -u git bash -lc "cd /home/git/apps/sigitsi && bin/rails db:migrate:status | tail -5"'
164 +
165 +# App serves (allow_browser rejects old/absent User-Agents with 403 — always
166 +# curl with a modern UA or a healthy site looks broken)
167 +curl -s -o /dev/null -w "%{http_code}\n" -A "Mozilla/5.0 (Chrome/130)" https://sigit.si/
168 +curl -s -o /dev/null -w "%{http_code}\n" -A "Mozilla/5.0 (Chrome/130)" https://sigit.si/changelog
169 +
170 +# New release visible
171 +curl -s -A "Mozilla/5.0 (Chrome/130)" https://sigit.si/changelog | grep -o "vX.Y.Z"
172 +
173 +# Jobs supervisor: exactly one, and the log is quiet
174 +ssh smb1-deploy 'ps aux | grep -E "bin/jobs|solid.queue" | grep -v grep'
175 +ssh smb1-deploy 'sudo -u git tail -20 /home/git/apps/sigitsi/output-jobs.log'
176 +```
177 +
178 +Feature-specific smoke tests belong here too — hit the endpoints the release
179 +added (webhooks, new pages) and check their runbooks under `docs/` for
180 +what "working" looks like.