jig-hotfix-flow · git:20260831.4cf7503 · 2026-08-31 · sha256 173fb53227ab01cf
jig-hotfix-flow git:20260831.4cf7503A
Immutable. This exact content is served forever at /api/v1/blob/173fb53227ab01cf.
--- name: jig-hotfix-flow description: Use when a released defect on main must ship before the work already sitting on develop. Branches from main, lands one squashed commit on main through the guard-allowed hotfix push, tags and publishes, then merges main back into develop so the next release still fast-forwards. --- # Hotfix Flow Use this repository skill only when a defect already released on `main` must be fixed **before** the work waiting on `develop` is ready to ship. Every other fix is an ordinary task. Use `develop-task-flow` and let the fix ride the next release. ## When This Applies All three must hold. If any one fails, this is not a hotfix. 1. The defect is reachable from `main`, so people already have it. 2. Harm keeps accumulating for as long as the released state stands. See Hotfix Triggers — the user calling it urgent is not that. 3. `develop` holds work that must **not** ship yet. When `develop` has nothing unreleased, there is no hotfix: fix it with `develop-task-flow` and release normally. ## Hotfix Triggers Whether a defect deserves to bypass the `develop` queue is a judgement, and it is made while something is on fire. So the judgement is not made here: the project writes the conditions in advance and this run only matches against them. Resolve the list from the `## Hotfix Triggers` section of the rubric file, found in the same order the grade uses: 1. `JIG_VERSION_RUBRIC` environment variable. 2. `git config --local --get jig.versionRubric` (repository override). 3. `.jig/versioning.md` (the convention). Accept either spelling of the section title: `## Hotfix Triggers` or `## 핫픽스 트리거`. The section holds an anchoring question on a `>` line and the conditions answering it that the project has already recognised. When the rubric has no such section, use this default and say in the report that it was the default: > Does harm keep occurring and accumulating for as long as the released state stands? - The released artifact does not install or start - Data is lost or corrupted - A credential or secret is exposed - A published security vulnerability is exploitable - A released capability is entirely broken for most users Apply it in this order: 1. **A listed condition matches.** Name it, record it as a `Hotfix-Trigger:` trailer next to `Release-Grade:`, and continue. 2. **Nothing matches.** Put the anchoring question to the user, in terms of what keeps happening while the release stands. A `no` ends the run: hand the fix to `develop-task-flow` and say why. 3. **Nothing matches but the answer is `yes`.** The list is short by one condition. Propose that condition, add it to the `## Hotfix Triggers` section, and commit that change on its own before the hotfix starts. Then continue from step 1 with the condition now listed. Step 3 is not a formality that lets a hotfix through. It is what stops the list from silently becoming whatever the current run needs: the addition is a policy change, committed separately, readable later next to every other one. Urgency asserted by the user is not an answer to the question. Neither is a deadline, a demo, or a release that feels overdue — those describe a schedule, not something the released state keeps doing. ## Why the Model Needs a Separate Flow `main` normally only moves by the release fast-forward `git push origin develop:main`, and both jig guards block anything else. A hotfix is the one operation that puts a commit on `main` that `develop` does not have. That divergence is the whole risk. Once `main` holds a commit `develop` lacks, `git push origin develop:main` stops fast-forwarding and `github-release` refuses to run — correctly, because the alternative is a force push. **So the back-merge is part of this procedure, not a follow-up.** A hotfix that lands on `main` without returning to `develop` leaves the repository unable to release. ## Landing on develop: Merge, Never Cherry-Pick Bring the hotfix back with `git merge main` into `develop`. Do not cherry-pick it. A cherry-pick copies the change under a new SHA, so `main` stays outside `develop`'s history and every later `develop:main` push fails the fast-forward check. The merge is what makes `main` an ancestor of `develop` again. The merge commit itself never reaches the release notes: `github-release` reads `git log <previous>..<version> --no-merges`. The hotfix commit is already behind the hotfix tag, so it is not repeated in the next release either. ## Release Grade Grade the hotfix against the repository's version rubric, resolved in this order: 1. `JIG_VERSION_RUBRIC` environment variable. 2. `git config --local --get jig.versionRubric` (repository override). 3. `.jig/versioning.md` (the convention). Apply its `## Decision Order` to this fix alone, stopping at the first match, then its `## Hard Rules`. Use `git diff --name-only origin/main...HEAD` as evidence, and read the floor from `## Interface Paths` when the rubric has that table. Record the verdict as a trailer on the squash commit body: ```text Release-Grade: patch Hotfix-Trigger: The released artifact does not install or start ``` `version-rubric` owns the rubric file. Never edit it from here. ## Version - Compute from the latest `vX.Y.Z` tag reachable from `origin/main` (`git describe --tags --abbrev=0 origin/main`), using the graded bump and the rubric's `## Version Format` when it has one. - A hotfix is usually `patch`. It is not automatically `patch`: grade it, and if the fix changes behavior silently the rubric says so. - The tag is created on the commit that landed on `main`, not on a `develop` commit. ## Safety Rules - Do not force push. - Do not bypass git hooks: never pass `--no-verify` to any push. - Push `main` only as `git push origin hotfix/<slug>:main`, and only when it fast-forwards. If it is rejected, stop and report; someone else moved `main`. - **Stop when `git rev-list --count origin/main..origin/develop` is `0`.** With nothing unreleased there is no work to protect, so a hotfix only adds steps. Use `develop-task-flow` and release normally. - Do not start a hotfix from `develop`. The branch point is `origin/main`, or the fix carries unreleased work with it. The `pre-push` guard enforces this: a hotfix push must be exactly one commit ahead of `main`, and a branch taken from `develop` is many. - Do not include anything beyond the defect. A hotfix that also carries a refactor is an ordinary task. - **Do not widen the trigger list inside a hotfix commit.** Adding a condition is allowed and expected over time, but it is proposed to the user and committed on its own before the fix begins, never folded into the fix. A list that grows inside the run it is gating has stopped being a gate. - Do not leave `main` ahead of `develop`. The back-merge runs in the same session, before the report. - Do not resolve a failed `develop:main` fast-forward by force-pushing; that is the state this skill exists to prevent. - Do not delete branches without explicit user confirmation. - Do not edit the rubric file. - Preserve unrelated user changes. ## Procedure 1. Inspect and confirm the three conditions in When This Applies: - `git status --short --branch` - `git fetch origin --prune --tags` - `git rev-list --count origin/main..origin/develop` — **`0` ends the run.** Report that this is an ordinary task and hand it to `develop-task-flow` - resolve `## Hotfix Triggers` per Hotfix Triggers and name the matching item. **No match ends the run.** - `git log --oneline origin/main..origin/develop` — name the unreleased work that must not ship, so the user can see what the hotfix is protecting 2. Create the branch from the released state: `git checkout -b hotfix/<slug> origin/main`. 3. Implement only the fix. 4. Run the most relevant focused test, then the repository's broader validation when practical. Report any gap. 5. Reduce the branch to one commit on top of `origin/main`: - `git reset --soft origin/main` - commit once with a `fix:` subject, user-facing body bullets, and the `Release-Grade` and `Hotfix-Trigger` trailers 6. Land it: `git push origin hotfix/<slug>:main`. This must fast-forward, and the guard additionally requires it to be exactly one commit ahead of `main`. A rejection there means the branch did not start from `origin/main`; do not work around it. 7. Tag the commit that is now `main` and publish: - `git tag <version> $(git rev-parse hotfix/<slug>)` - `git push origin <version>` - `gh release create <version> --title "<version> 🌈" --notes-file <draft>` - Resolve the GitHub profile the way `github-release` does before any `gh` command. 8. **Return to develop in the same session:** - `git checkout develop` - `git pull --ff-only origin develop` - `git merge main` — resolve conflicts here, never by dropping the fix - `git push origin develop` 9. Verify the invariant is restored: `git merge-base --is-ancestor origin/main origin/develop` must succeed. Until it does, the next release cannot run. 10. Report. ## Release Notes Follow the `github-release` note format: `## Changes` with the section its commit prefix selects (`fix:` → `### 🐛 Fixes`), then a `### Summary` in the language the repository already uses. Say plainly that this release came from `main` and that `develop` work is not included. Someone reading two adjacent tags should not have to guess why the second one carries less. ## Final Report - Branch, and the `origin/main` commit it started from - Files changed and tests run - The trigger that matched, and whether it came from the project's list or the shipped default - Graded bump, the rubric question that decided it, and the recorded `Release-Grade` - Version tagged and the release URL - Back-merge result and the `merge-base --is-ancestor` verification - Unreleased `develop` work that was deliberately left out - Commands that could not run and why