Skip to content

Commit c31137e

Browse files
committed
feat: automate tag and release creation
Establish a release process for the bot: manual on the decision, automatic on the mechanics. The version bump in pyproject.toml and the matching CHANGELOG.md entry go through the normal pull request review. On merge to main, release.yml derives the tag from tool.poetry.version, extracts the corresponding changelog section and publishes the annotated tag plus the GitHub Release. Checking for the tag's existence instead of diffing pyproject.toml against HEAD^ keeps the workflow idempotent, so re-runs, workflow_dispatch and bumps that arrive alongside other changes in the same squash commit all behave the same. Tags are plain X.Y.Z, matching the already published 0.1.0 baseline. No new secrets: the default GITHUB_TOKEN with contents: write is enough, because branch protection does not cover tag and release creation. Nothing is ever pushed to main. closes #104
1 parent c7b64d7 commit c31137e

4 files changed

Lines changed: 170 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
name: release
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "pyproject.toml"
8+
- "CHANGELOG.md"
9+
workflow_dispatch:
10+
inputs:
11+
dry_run:
12+
description: "Only print the release notes; do not create the tag or the release"
13+
type: boolean
14+
default: false
15+
16+
permissions: {}
17+
18+
concurrency:
19+
group: release
20+
cancel-in-progress: false
21+
22+
jobs:
23+
release:
24+
name: "Tag and Publish Release"
25+
if: github.repository == 'marcieltorres/safe-chat-slack-bot'
26+
runs-on: ubuntu-latest
27+
permissions:
28+
contents: write
29+
steps:
30+
- name: Check out repository code
31+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32+
with:
33+
fetch-depth: 0
34+
35+
- name: Read the version from pyproject.toml
36+
id: version
37+
run: |
38+
python3 - <<'PY' >> "$GITHUB_OUTPUT"
39+
import re
40+
import tomllib
41+
42+
with open("pyproject.toml", "rb") as handler:
43+
version = tomllib.load(handler)["tool"]["poetry"]["version"]
44+
45+
if not re.fullmatch(r"\d+\.\d+\.\d+", version):
46+
raise SystemExit(f"::error::invalid version in pyproject.toml: {version!r}")
47+
48+
print(f"version={version}")
49+
PY
50+
51+
- name: Check whether the tag already exists
52+
id: tag
53+
env:
54+
VERSION: ${{ steps.version.outputs.version }}
55+
run: |
56+
if git rev-parse -q --verify "refs/tags/${VERSION}" > /dev/null; then
57+
echo "exists=true" >> "$GITHUB_OUTPUT"
58+
echo "::notice::tag ${VERSION} already exists, nothing to release"
59+
else
60+
echo "exists=false" >> "$GITHUB_OUTPUT"
61+
fi
62+
63+
- name: Extract the release notes from CHANGELOG.md
64+
if: steps.tag.outputs.exists == 'false'
65+
env:
66+
VERSION: ${{ steps.version.outputs.version }}
67+
run: |
68+
python3 - <<'PY'
69+
import os
70+
import pathlib
71+
import re
72+
73+
version = os.environ["VERSION"]
74+
changelog = pathlib.Path("CHANGELOG.md").read_text(encoding="utf-8")
75+
section = re.search(
76+
rf"^## \[{re.escape(version)}\].*?$\n(?P<body>.*?)(?=^## \[|^\[[^\]]+\]:|\Z)",
77+
changelog,
78+
re.MULTILINE | re.DOTALL,
79+
)
80+
81+
if section is None:
82+
raise SystemExit(f"::error::CHANGELOG.md has no '## [{version}]' section")
83+
84+
body = section.group("body").strip()
85+
if not body:
86+
raise SystemExit(f"::error::the '## [{version}]' section is empty")
87+
88+
pathlib.Path("release-notes.md").write_text(f"{body}\n", encoding="utf-8")
89+
PY
90+
cat release-notes.md
91+
92+
- name: Create and push the tag
93+
if: steps.tag.outputs.exists == 'false' && !inputs.dry_run
94+
env:
95+
VERSION: ${{ steps.version.outputs.version }}
96+
run: |
97+
git config user.name "github-actions[bot]"
98+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
99+
git tag -a "${VERSION}" -m "${VERSION}"
100+
git push origin "${VERSION}"
101+
102+
- name: Publish the GitHub Release
103+
if: steps.tag.outputs.exists == 'false' && !inputs.dry_run
104+
env:
105+
VERSION: ${{ steps.version.outputs.version }}
106+
GH_TOKEN: ${{ github.token }}
107+
run: |
108+
gh release create "${VERSION}" \
109+
--title "${VERSION}" \
110+
--notes-file release-notes.md \
111+
--verify-tag

AGENTS.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,22 @@ User-facing strings must go through `language.translate("...")`. Add the msgid t
9999
Reference it with `closes #NN`.
100100
- Make sure lint and tests pass locally before opening the PR.
101101

102+
## Releases
103+
104+
The tag and the GitHub Release are created automatically by
105+
[`.github/workflows/release.yml`](.github/workflows/release.yml) when a version without a tag lands
106+
on `main`. [CONTRIBUTING.md](CONTRIBUTING.md) has the full process.
107+
108+
- **Never create a tag or a GitHub Release by hand** — the workflow owns both.
109+
- Tags are plain `X.Y.Z`, without a `v` prefix, always derived from `tool.poetry.version` in
110+
[pyproject.toml](pyproject.toml).
111+
- A PR that changes the bot's behaviour must bump `version` in `pyproject.toml` **and** add the
112+
matching `## [X.Y.Z] - YYYY-MM-DD` section to [CHANGELOG.md](CHANGELOG.md). Without both, the
113+
release never fires.
114+
- A PR that only touches dependencies, docs or CI does not bump the version.
115+
- The release notes are the changelog section verbatim, so the section must not be empty — the
116+
workflow fails loudly if it is missing or blank.
117+
102118
## Security
103119

104120
This bot handles credentials and PII by definition. Treat these as hard rules:

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
## [0.1.0] - 2026-08-08
11+
12+
### Added
13+
14+
- Detection of Brazilian CPF, email addresses and Brazilian phone numbers in channel messages.
15+
- In-thread reply asking the author not to share sensitive data.
16+
- Support for edited messages (`message_changed` subtype).
17+
- Internationalization via gettext (`en`, `pt_BR`).
18+
19+
[Unreleased]: https://github.com/marcieltorres/safe-chat-slack-bot/compare/0.1.0...HEAD
20+
[0.1.0]: https://github.com/marcieltorres/safe-chat-slack-bot/releases/tag/0.1.0

CONTRIBUTING.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,29 @@ I | [isort](https://pypi.org/project/isort/)
4545
N | [pep8-naming](https://pypi.org/project/pep8-naming/)
4646
S | [flake8-bandit](https://pypi.org/project/flake8-bandit/)
4747

48+
# Releasing
49+
50+
Releases are cut from `main`. The decision is manual, the mechanics are automated: you bump the version and write the changelog entry inside your pull request, and [`release.yml`](.github/workflows/release.yml) creates the tag and publishes the GitHub Release on merge.
51+
52+
1. In the same pull request as your change, bump `version` in [pyproject.toml](pyproject.toml) following semver.
53+
2. Move the `## [Unreleased]` content in [CHANGELOG.md](CHANGELOG.md) into a new `## [X.Y.Z] - YYYY-MM-DD` section.
54+
3. Update the comparison links at the bottom of the changelog.
55+
4. Merge. The tag and the release show up on their own in about a minute.
56+
5. If something goes wrong, fix `CHANGELOG.md` and re-run `release` from **Actions → release → Run workflow**. Running it with `dry_run: true` prints the notes without creating anything.
57+
58+
Tags are plain `X.Y.Z`, without a `v` prefix. Never create a tag or a release by hand — the workflow owns both, and it derives the tag from `pyproject.toml` so the two cannot drift apart.
59+
60+
When to bump:
61+
62+
Change | Bump
63+
--- | ---
64+
New detection rule, new listener, new locale | minor
65+
Regex fix, bug fix, translation tweak | patch
66+
Change to `manifest.json` scopes, breaking configuration change | major
67+
Dependency bump (Dependabot), docs, CI | none
68+
69+
The release notes are the changelog section verbatim, so write the entry for whoever reads the release page, not for the diff.
70+
4871
# AI Coding Agents
4972

5073
If you use an AI coding agent (Claude Code, Cursor, Copilot, Codex and friends) to contribute, point it at [AGENTS.md](AGENTS.md). It follows the [agents.md](https://agents.md/) convention and covers setup, commands, code style, testing conventions, commit format and the security rules that apply to this bot — it handles Slack tokens and PII, so those rules are not optional.

0 commit comments

Comments
 (0)