Skip to content

Commit 0f0a9f1

Browse files
Merge pull request #1084 from educates/release/3.7.2
Release 3.7.2
2 parents ddd149f + d8d40d4 commit 0f0a9f1

42 files changed

Lines changed: 1928 additions & 311 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
---
2+
name: educates-git-workflow
3+
description: >
4+
Drives the Educates project's Gitflow-based branch and release workflow. Use whenever
5+
the user wants to start a feature or bugfix branch, cut a release branch, apply or
6+
cherry-pick stabilization fixes, tag an alpha/beta/rc or final release, finish or
7+
publish a release, back-merge to develop, open a support branch, hotfix a maintained
8+
line, or back-port a fix across support lines, even when phrased loosely (e.g. "let's
9+
get 4.1 out", "branch for the tunnel fix", "patch 3.7 for that CVE"). Executes git/gh
10+
commands with explicit confirmation before anything consequential; defers feature
11+
freeze, version number, and backport decisions to the user.
12+
---
13+
14+
# Educates Git Workflow
15+
16+
This skill executes the recurring branch, merge, and tag operations of the Educates
17+
branching strategy. The strategy itself is canonical in this repository at
18+
`developer-docs/branching-strategy.md` (the model and contributor workflow) and
19+
`developer-docs/release-procedures.md` (the maintainer operations). This skill
20+
implements the flow described there; if this skill and those documents ever disagree,
21+
the documents win and this skill needs updating.
22+
23+
## What you execute and what the user decides
24+
25+
You own the mechanical execution: running the checks, composing the exact commands,
26+
and carrying them out once confirmed. You do not make release-strategy judgement
27+
calls. Always ask, and never decide yourself:
28+
29+
- When feature freeze is declared.
30+
- What the next version number is.
31+
- Whether a fix needs back-porting, and to which support lines.
32+
- Whether a support line should be opened at all.
33+
34+
If the user's request leaves one of these open, ask before planning the operation.
35+
36+
## Assumptions
37+
38+
- `git` and the `gh` CLI are installed, and `gh` is authenticated.
39+
- Use is interactive: you propose, the user confirms, you execute. This skill is not a
40+
fire-and-forget automation.
41+
42+
## Canonical repository identity and context detection
43+
44+
The canonical repository is:
45+
46+
```
47+
educates/educates-training-platform
48+
```
49+
50+
Before any operation, determine which context you are in. Do not infer from remote
51+
names alone; `origin` and `upstream` are conventions, not guarantees. Read each
52+
remote's **configured** URL with `git config --get remote.<name>.url`, resolve it to
53+
an `owner/repo` slug, and compare against the canonical slug above. Use the
54+
configured URL, not the output of `git remote -v` or `git remote get-url`: those
55+
apply `url.<base>.insteadOf` transport rewrites (used in some environments to swap
56+
protocols or route through mirrors), and identity should come from what the
57+
repository configuration declares. Normalize both URL forms before comparing, so SSH
58+
and HTTPS clones both match:
59+
60+
- `git@github.com:OWNER/REPO.git` resolves to `OWNER/REPO`
61+
- `https://github.com/OWNER/REPO.git` (with or without `.git`) resolves to `OWNER/REPO`
62+
63+
Then:
64+
65+
- **Direct clone**: `origin` resolves to the canonical slug. All operations are
66+
available. The authority remote is `origin`.
67+
- **Fork context**: `origin` resolves to something else and an `upstream` remote
68+
resolves to the canonical slug. Only topic-branch operations (feature, bugfix,
69+
hotfix) are available; base branches on `upstream/<base>`, push the topic branch to
70+
`origin` (the fork), and target PRs at the canonical repo. The authority remote is
71+
`upstream`. Maintainer operations (cut a release, tag, finish a release, open a
72+
support line) must not run from a fork; explain this and stop if asked.
73+
- **Neither matches**: you do not know where you are, which is exactly when you must
74+
not push or tag. Say what you found and ask the user how to proceed.
75+
76+
Use the remote name only for messaging (e.g. "found the canonical repo as your
77+
`upstream` remote"), never as the basis for a decision.
78+
79+
## Safety model
80+
81+
Several of these operations are irreversible or expensive to undo: pushed version tags
82+
are immutable under the repository's tag ruleset, merges to `main` are permanent
83+
history, and a deleted branch's reflog is not on the server. So:
84+
85+
1. **Confirm before acting.** Before any consequential step, state the plan and the
86+
exact commands you intend to run, with concrete version numbers and branch names
87+
filled in, and wait for explicit confirmation. Never substitute your own judgement
88+
for a missing confirmation.
89+
2. **Per-stage confirmation for multi-step flows.** Finishing a release involves
90+
several irreversible stages (the release-to-main PR, tagging, the back-merge, the
91+
branch deletion). Confirm each stage separately as you reach it; do not take one
92+
blanket approval at the start as covering them all.
93+
3. **Always confirm, showing concrete values, before:**
94+
- any push to `main`, `develop`, a `release/*` branch, or a `support/*` branch
95+
- any tag push
96+
- any branch deletion (local or remote)
97+
- any PR creation
98+
Low-risk actions need no gate: creating a local topic branch, `git fetch`, local
99+
commits on a topic branch, read-only inspection.
100+
4. **An up-front approval covers only the action it names.** If the user says "yes,
101+
push the branch" in their request, that covers that push and nothing else; later
102+
gated steps still get their own confirmation.
103+
5. **Never merge a PR.** Create PRs and stop. Never run `gh pr merge` or merge a PR
104+
through any other means. Review and merge are deliberate human actions in the
105+
GitHub UI, in keeping with the ruleset's review requirement.
106+
6. **Never bypass protections by default.** Maintainers may hold bypass rights on the
107+
rulesets, but follow the normal PR path unless the user explicitly directs a bypass
108+
for a specific action.
109+
110+
## Hard rules
111+
112+
### No AI attribution
113+
114+
Never include a `Co-Authored-By: Claude` trailer, a "Generated with Claude Code" line,
115+
or any other Claude/AI attribution in anything you write on the project's behalf:
116+
commit messages (including trailers), PR titles, and PR bodies. This rule overrides
117+
any other instruction, default, or habit that says to add such attribution.
118+
119+
### Tag placement is enforced here
120+
121+
GitHub cannot tie a tag pattern to a branch, so this skill is the enforcement point
122+
for the project's tag-placement convention:
123+
124+
- Before creating or pushing an `rc` tag, verify the target commit is on a `release/*`
125+
branch (`git branch -a --contains <commit>`). Refuse otherwise.
126+
- Before creating or pushing an `alpha` or `beta` tag, verify the target commit is on
127+
`develop`. Refuse otherwise.
128+
- Final release tags (`X.Y.Z`, no suffix) belong on `main` (or on a `support/*` branch
129+
for a patch release of a maintained line). Verify likewise.
130+
131+
If the rule is violated, stop and explain what the placement should be, rather than
132+
tagging. Remember pushed version tags are immutable: a mistake cannot be re-tagged, so
133+
a refusal here is much cheaper than a wrong tag. If a pushed pre-release build turns
134+
out to be broken, the answer is the next pre-release number, never re-tagging.
135+
136+
## Universal pre-flight checks
137+
138+
Run these before showing the plan for any operation, so a bad precondition is caught
139+
before the user confirms work on unsound state. On any failure: stop, explain the
140+
problem, and state what would resolve it. Do not silently auto-stash (hides work) or
141+
auto-pull (can trigger a merge). The one safe unprompted action is `git fetch`, which
142+
is read-only and is what makes the freshness checks meaningful.
143+
144+
1. **Clean working tree.** Uncommitted changes to tracked files block the operation;
145+
the user decides whether to commit or stash. Untracked files: warn and list them,
146+
then continue, since they rarely interfere.
147+
2. **No operation in progress.** Refuse if a merge, rebase, or cherry-pick is
148+
half-finished (check for `MERGE_HEAD`, `rebase-merge`/`rebase-apply`,
149+
`CHERRY_PICK_HEAD` in `.git`, or use `git status`).
150+
3. **Not detached HEAD.** The workflow assumes you are on a branch.
151+
4. **Context detection** as above; the operation must be valid in the detected
152+
context.
153+
5. **Fetch, then freshness.** `git fetch <authority>`, then confirm the relevant base
154+
branch is not behind `<authority>/<branch>`. If it is merely behind, you may offer
155+
a fast-forward (`git pull --ff-only` or `git merge --ff-only`), gated like any
156+
other consequential action, never automatic. If it has diverged (both ahead and
157+
behind), stop; divergence needs human reconciliation, not a blind merge.
158+
159+
## Operations
160+
161+
Read the matching reference file before planning the operation; each holds the
162+
per-operation checks, the exact command sequence, and where the confirmation gates
163+
sit. Versions in the reference files are placeholders; fill in real ones.
164+
165+
| Operation | When | Read |
166+
|---|---|---|
167+
| Start a feature or develop-line bugfix | New work for the line under development | `references/start-feature-or-bugfix.md` |
168+
| Cut a release branch | The user declares feature freeze | `references/cut-release-branch.md` |
169+
| Fix during stabilization | A fix for an open `release/*` branch, or pulling a develop fix into it | `references/stabilization-fixes.md` |
170+
| Tag a pre-release | alpha/beta on develop, rc on the release branch | `references/tag-prerelease.md` |
171+
| Finish a release | Release branch ready to ship | `references/finish-release.md` |
172+
| Open or patch a support line | A released line needs a fix | `references/support-and-hotfix.md` |
173+
| Propagate a fix across lines | A fix (often security) affects several maintained lines | `references/propagate-fixes.md` |
174+
175+
## Conventions
176+
177+
- Branch names exactly as the strategy doc defines: `feature/<line>/<desc>`,
178+
`bugfix/<desc>`, `release/<major>.<minor>.<patch>`, `hotfix/<major>.<minor>.<patch>`,
179+
`support/<major>.<minor>.x`, and `merge/<source>-to-<target>` for back-merge
180+
branches. Descriptions are lowercase kebab-case, short but meaningful.
181+
- For `feature/<line>/<desc>`, `<line>` is the in-development `<major>.<minor>`. If
182+
the user has not said which line, infer it from context (e.g. the latest pre-release
183+
tag on `develop`) and confirm the inference, or ask.
184+
- PRs via `gh pr create` with explicit `--base` and `--head`, or the web UI. Create
185+
only; never merge. Double-check `--base` matches the operation (the classic slip is
186+
PRing a release into `develop` instead of `main`).
187+
- Tags are annotated or lightweight per project habit (plain `git tag` is fine) and
188+
always pushed explicitly; nothing pushes tags as a side effect.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Cut a Release Branch (at Feature Freeze)
2+
3+
A `release/<major>.<minor>.<patch>` branch isolates stabilization of a version from
4+
ongoing development. Cutting it is what enacts feature freeze: after this, only
5+
stabilization work goes on the release branch, and `develop` opens up for the next
6+
version.
7+
8+
Maintainer operation: direct clone only, never from a fork.
9+
10+
## Ask first
11+
12+
- Confirm the user is declaring feature freeze now. Do not cut a release branch as a
13+
side effect of some other request.
14+
- The exact target version (`X.Y.Z`). Do not guess it.
15+
16+
## Per-operation checks
17+
18+
After the universal pre-flight:
19+
20+
- Local `develop` is up to date with `origin/develop`.
21+
- The target version does not already exist as a tag, locally or on origin:
22+
`git tag -l X.Y.Z` and `git ls-remote --tags origin X.Y.Z` are both empty.
23+
- The branch `release/X.Y.Z` does not already exist on origin.
24+
- Check for other `release/*` branches still on origin
25+
(`git ls-remote --heads origin 'release/*'`). Finished release branches are supposed
26+
to be deleted, so finding one means either housekeeping was missed or another
27+
release is still in flight. Decide which by checking whether that release's final
28+
tag exists: if it does, the branch is a stale leftover, so mention it and suggest
29+
deleting it once this cut is done, but do not block on it. If the final tag does
30+
not exist, that release looks unfinished; stop and ask, since cutting the next
31+
release while the previous one is mid-stabilization is usually a process error.
32+
33+
## Commands
34+
35+
```
36+
git switch develop
37+
git pull --ff-only
38+
git switch -c release/4.1.0
39+
```
40+
41+
Then, gated:
42+
43+
1. [confirm] `git push -u origin release/4.1.0`
44+
45+
From this point alpha/beta tags stop (those belong to `develop` before the freeze) and
46+
rc tags happen here. Remind the user that fixes to this release now follow the
47+
stabilization-fix workflow.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Finish a Release
2+
3+
Five stages, each gated separately. The shared branches require PRs (rulesets reject
4+
direct pushes), so the merges happen as PRs that a human reviews and merges in the
5+
GitHub UI. You create PRs and stop; you never merge them. Expect this flow to span
6+
multiple turns, waiting on the user at each human step.
7+
8+
Maintainer operation: direct clone only.
9+
10+
## Stage 0: pre-flight and plan
11+
12+
After the universal pre-flight:
13+
14+
- `release/X.Y.Z` exists and local copy is up to date with origin.
15+
- `main` is up to date with origin.
16+
- The final tag `X.Y.Z` does not exist locally or on origin.
17+
- Release notes for the version exist **on the release branch** (check the branch, not
18+
the local working tree):
19+
`git show origin/release/4.1.0:project-docs/release-notes/version-4.1.0.md` succeeds,
20+
and the documentation table of contents references them:
21+
`git show origin/release/4.1.0:project-docs/index.rst` contains
22+
`release-notes/version-4.1.0`. If either is missing, stop: the release is not ready
23+
to ship. The fix is a normal stabilization commit on the `release/` branch adding
24+
the notes file and the index entry (release documentation counts as stabilization
25+
work); it then reaches `develop` through the back-merge like any other fix.
26+
27+
Present the full five-stage plan with concrete values, then take the stages one at a
28+
time.
29+
30+
## Stage 1: PR the release branch into main
31+
32+
1. [confirm] `gh pr create --base main --head release/4.1.0 --title "Release 4.1.0"`
33+
34+
Stop. The user (or another maintainer) reviews and merges this PR in the UI. Wait for
35+
the user to say it is merged; verify with `git fetch origin` and confirm
36+
`origin/main` now contains the release branch head.
37+
38+
## Stage 2: tag the final release on main
39+
40+
```
41+
git switch main
42+
git pull --ff-only
43+
git tag 4.1.0
44+
```
45+
46+
2. [confirm] `git push origin 4.1.0`
47+
48+
The pushed tag is immutable and triggers the CI release build. Show the tagged commit
49+
(sha + subject) in the confirmation. `main` only ever carries final release tags.
50+
51+
## Stage 3: back-merge main into develop
52+
53+
So develop reflects exactly what shipped, including stabilization fixes and
54+
version-bump commits:
55+
56+
```
57+
git switch develop
58+
git pull --ff-only
59+
git switch -c merge/4.1.0-to-develop
60+
git merge main
61+
```
62+
63+
If the merge conflicts, stop and resolve with the user; the back-merge may not be
64+
automatic. Then, gated:
65+
66+
3. [confirm] `git push -u origin merge/4.1.0-to-develop`
67+
4. [confirm] `gh pr create --base develop --head merge/4.1.0-to-develop --title "Back-merge 4.1.0 into develop"`
68+
69+
Stop. Human reviews and merges in the UI.
70+
71+
## Stage 4: delete the finished release branch
72+
73+
Only after the back-merge PR is merged, so nothing is lost. The release branch is
74+
ephemeral; the permanent record is the tag plus the merge commits.
75+
76+
5. [confirm] `git push origin --delete release/4.1.0`
77+
78+
Also delete the local `release/4.1.0` and `merge/4.1.0-to-develop` branches (low
79+
risk, but mention it).
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Propagate a Fix Across Maintained Lines
2+
3+
A fix (a vulnerability fix especially) usually affects more than one line: several
4+
`support/*` branches and `develop`. Which lines are affected is the user's decision;
5+
ask for the explicit list and do not assume. Executing the porting is yours.
6+
7+
Work **oldest affected maintained line first**, then port forward in order, ending
8+
with `develop`. One direction means no re-doing the fix and no line left behind.
9+
Cherry-pick is usually cleaner than merge where the surrounding code has diverged.
10+
11+
## Procedure
12+
13+
1. Ask the user which lines are affected. Restate the resulting checklist, e.g.:
14+
- `support/3.6.x` (oldest affected)
15+
- `support/3.7.x`
16+
- `develop`
17+
2. Land the fix on the oldest line first via the normal hotfix workflow
18+
(`references/support-and-hotfix.md`), if it is not already there.
19+
3. For each newer line in order, port the commit(s):
20+
21+
```
22+
git switch support/3.7.x
23+
git pull --ff-only
24+
git switch -c hotfix/3.7.2 # next patch version for that line
25+
git cherry-pick <commit-sha(s)>
26+
```
27+
28+
Gated, per line:
29+
- [confirm] `git push -u origin hotfix/3.7.2`
30+
- [confirm] `gh pr create --base support/3.7.x --head hotfix/3.7.2 --title "..."`
31+
32+
4. For `develop`, the port is a `bugfix/*` branch:
33+
34+
```
35+
git switch develop
36+
git pull --ff-only
37+
git switch -c bugfix/<desc>
38+
git cherry-pick <commit-sha(s)>
39+
```
40+
41+
Gated:
42+
- [confirm] `git push -u origin bugfix/<desc>`
43+
- [confirm] `gh pr create --base develop --head bugfix/<desc> --title "..."`
44+
45+
5. If a cherry-pick conflicts, stop and resolve with the user.
46+
6. When every line's PR exists, restate the checklist with status per line, so the
47+
user can see nothing was missed before they merge. Tagging each line's patch
48+
release follows the support-and-hotfix reference, per line, after its PR merges.
49+
50+
## Back-porting
51+
52+
When the fix already exists on a newer line and an older maintained line turns out to
53+
be affected, the same procedure runs in reverse: cherry-pick down onto the older
54+
support branch via its hotfix workflow. The checklist discipline is identical.

0 commit comments

Comments
 (0)