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.