feat(gitflow): chore branch type + aiguillage for standalone memory/doc skills
Standalone /capitalize /close /prune-memory /reconcile no longer lean on the .claude/** hook exemption when run on main/develop: the aiguillage branches them to chore/* off develop before writing. New chore type (base develop, finish->develop) added to the lib; hook unchanged (chore/* non-protected). Closes the leak where standalone memory work (memory IS the work, no code branch to follow) landed direct on a protected base. 64/64 gitflow-test green, shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNaYKPEkjH1jbgoX1TwKMX
This commit is contained in:
parent
53bd7beee8
commit
e8807a7333
18
CLAUDE.md
18
CLAUDE.md
@ -168,23 +168,27 @@ Every git action follows gitflow — inside a skill AND for ad-hoc commits made
|
||||
outside one on direct request. The model is universal across all projects.
|
||||
|
||||
### Branch model
|
||||
`main` (prod) · `develop` (integration, off main) · `feature/*` + `bugfix/*`
|
||||
(off develop → develop) · `release/*` (off develop → main + back-merge develop)
|
||||
· `hotfix/*` (off main → main + develop [+ any open release/*]). `master`→`main`
|
||||
everywhere.
|
||||
`main` (prod) · `develop` (integration, off main) · `feature/*` + `bugfix/*` +
|
||||
`chore/*` (off develop → develop; `chore/*` = memory/doc maintenance, e.g.
|
||||
standalone `/capitalize` `/prune-memory` `/reconcile`) · `release/*` (off develop →
|
||||
main + back-merge develop) · `hotfix/*` (off main → main + develop [+ any open
|
||||
release/*]). `master`→`main` everywhere.
|
||||
|
||||
### Rules for every git action
|
||||
- **Never commit code directly on `main` or `develop`.** Branch first from the
|
||||
correct base, named `<type>/<name>`. (`.claude/**` memory/config commits are
|
||||
exempt — they follow the work, not the code's gitflow.)
|
||||
hook-exempt — they follow the work; but *standalone* memory/doc skills branch to
|
||||
`chore/*` via the aiguillage rather than lean on that exemption.)
|
||||
- **Branch + merge via the lib, never by hand** — the directed-merge + hotfix
|
||||
fan-out logic lives there once:
|
||||
`bash ~/.claude/lib/gitflow.sh start <type> <name>` · `… finish`.
|
||||
- **`gitflow finish` (merge) only on an explicit human signal** ("merge it",
|
||||
"feature OK") — never because tests pass, a plan step says "merge", or a verb
|
||||
("ship") implied it.
|
||||
- **Assistance flows** (`/feat` `/bugfix` `/hotfix`) auto-branch on a protected
|
||||
base (the aiguillage); on a working branch they commit in place, never finish.
|
||||
- **Assistance flows** (`/feat` `/bugfix` `/hotfix`) AND **standalone memory/doc
|
||||
skills** (`/capitalize` `/close` `/prune-memory` `/reconcile`, type `chore`)
|
||||
auto-branch on a protected base (the aiguillage); on a working branch they commit
|
||||
in place, never finish.
|
||||
- **New/onboarded projects** get the model + the versioned pre-commit hook via
|
||||
`gitflow init` (init-project STEP 5f, onboard STEP 2.6).
|
||||
|
||||
|
||||
@ -1,26 +1,41 @@
|
||||
# Gitflow aiguillage — assistance flows branch on a protected base
|
||||
# Gitflow aiguillage — branch on a protected base before writing
|
||||
|
||||
Assistance flows (`/feat`, `/bugfix`, `/hotfix`) commit IN PLACE on a working
|
||||
branch — the frequent case, behavior unchanged. But they must NEVER commit code
|
||||
on a protected base (`main`/`develop`). Run this check **before editing any
|
||||
file**. The caller passes its TYPE: feat→`feature`, bugfix→`bugfix`,
|
||||
hotfix→`hotfix`.
|
||||
Flows that WRITE — code, OR standalone memory/doc work — must NEVER commit on a
|
||||
protected base (`main`/`develop`). Run this check **before editing any file**.
|
||||
|
||||
```bash
|
||||
bash "$HOME/.claude/lib/gitflow.sh" protected-base && echo PROTECTED || echo WORKING
|
||||
```
|
||||
|
||||
- **WORKING** (`feature/*`, `bugfix/*`, `hotfix/*`, or any non-protected branch)
|
||||
→ proceed; you commit in place on this branch. Nothing changes.
|
||||
- **WORKING** (`feature/*`, `bugfix/*`, `hotfix/*`, `chore/*`, or any non-protected
|
||||
branch) → proceed; you commit in place on this branch. Nothing changes.
|
||||
- **PROTECTED** (`main`/`develop`) → branch first, do NOT commit here:
|
||||
```bash
|
||||
bash "$HOME/.claude/lib/gitflow.sh" start <YOUR-TYPE> <short-kebab-name>
|
||||
```
|
||||
`<short-kebab-name>` derived from the request. Then do the work on the new branch.
|
||||
|
||||
**Never run `gitflow finish`** — assistance flows commit, they do not merge.
|
||||
Integration is a separate, human-gated step (the `gitflow` skill).
|
||||
The caller passes its TYPE:
|
||||
|
||||
Note: `hotfix` branches off **main** (prod) even when invoked from `develop` —
|
||||
that is the gitflow definition of a hotfix. For a dev-scoped small fix, use
|
||||
`/bugfix` (branches off develop).
|
||||
| Caller | TYPE | Base |
|
||||
|--------|------|------|
|
||||
| `/feat` | `feature` | develop |
|
||||
| `/bugfix` | `bugfix` | develop |
|
||||
| `/hotfix` | `hotfix` | main |
|
||||
| `/capitalize` · `/close` · `/prune-memory` · `/reconcile` | `chore` | develop |
|
||||
|
||||
The `chore` row = **standalone memory/doc work**: the registry / TODO / doc
|
||||
reconciliation & curation skills, run OUTSIDE an assistance flow. Inside `/feat`
|
||||
`/bugfix` `/hotfix` `/ship-feature` a working branch already exists (this check
|
||||
returns WORKING) and the memory commit rides it. The aiguillage only fires when
|
||||
such a skill is invoked directly on `main`/`develop` — i.e. memory IS the work,
|
||||
with no code branch to follow. That is the leak it closes: the `.claude/**` hook
|
||||
exemption still lets a *manual* memory commit through on a protected base, but a
|
||||
skill-driven one now branches to `chore/*` first.
|
||||
|
||||
**Never run `gitflow finish`** — these flows commit, they do not merge. Integration
|
||||
is a separate, human-gated step (the `gitflow` skill).
|
||||
|
||||
Note: `hotfix` branches off **main** (prod) even when invoked from `develop` — that
|
||||
is the gitflow definition of a hotfix. For a dev-scoped small fix, use `/bugfix`
|
||||
(branches off develop).
|
||||
|
||||
@ -32,6 +32,9 @@ chk "protected develop" 'gitflow_protected_base develop'
|
||||
chk "not protected feat" '! gitflow_protected_base feature/x'
|
||||
chk "base feature=develop" '[ "$(gitflow_base_for feature)" = develop ]'
|
||||
chk "base hotfix=main" '[ "$(gitflow_base_for hotfix)" = main ]'
|
||||
chk "type chore" '[ "$(gitflow_branch_type chore/x)" = chore ]'
|
||||
chk "base chore=develop" '[ "$(gitflow_base_for chore)" = develop ]'
|
||||
chk "not protected chore" '! gitflow_protected_base chore/x'
|
||||
|
||||
echo "T2 — init fresh (BLK-010 root commit)"
|
||||
newrepo fresh; echo scaffold > README.md; hookon
|
||||
@ -92,6 +95,16 @@ chk "merged into develop" 'git log develop --oneline | grep -q "Merge feature/f1
|
||||
chk "main untouched" "[ \"\$(git rev-parse main)\" = \"$main_before\" ]"
|
||||
chk "branch deleted" '! git rev-parse --verify -q refs/heads/feature/f1 >/dev/null'
|
||||
|
||||
echo "T6b — finish chore → develop only (standalone memory/doc maintenance)"
|
||||
newrepo finchore; echo a>a; hookon; gitflow_init >/dev/null 2>&1
|
||||
gitflow_start chore c1 >/dev/null 2>&1
|
||||
mkdir -p .claude/memory; echo m>.claude/memory/x.md; git add -A; git commit -q -m "chore(memory)"
|
||||
main_before="$(git rev-parse main)"
|
||||
gitflow_finish >/dev/null 2>&1
|
||||
chk "chore merged into develop" 'git log develop --oneline | grep -q "Merge chore/c1 into develop"'
|
||||
chk "chore main untouched" "[ \"\$(git rev-parse main)\" = \"$main_before\" ]"
|
||||
chk "chore branch deleted" '! git rev-parse --verify -q refs/heads/chore/c1 >/dev/null'
|
||||
|
||||
echo "T7 — finish hotfix → main + develop fan-out"
|
||||
newrepo finhot; echo a>a; hookon; gitflow_init >/dev/null 2>&1
|
||||
gitflow_start hotfix h1 >/dev/null 2>&1; echo p>patch.txt; git add patch.txt; git commit -q -m patch
|
||||
@ -119,7 +132,7 @@ chk "idempotent 2nd run" "[ \"$before\" = \"\$(md5sum .gitignore)\" ]"
|
||||
|
||||
echo "T10 — COHERENCE: hook verdict == lib predicate (drift detector, #4)"
|
||||
newrepo coh; echo a>a; hookon; gitflow_init >/dev/null 2>&1
|
||||
for br in main develop feature/x bugfix/y release/z hotfix/w master mainline qa; do
|
||||
for br in main develop feature/x bugfix/y release/z hotfix/w chore/m master mainline qa; do
|
||||
if gitflow_protected_base "$br"; then lib=protected; else lib=open; fi
|
||||
git checkout -q -B "$br" 2>/dev/null
|
||||
printf 'x\n' >> a; git add a
|
||||
|
||||
@ -21,7 +21,7 @@ GITFLOW_GITIGNORE_TEMPLATE="${GITFLOW_GITIGNORE_TEMPLATE:-$_GITFLOW_LIB_DIR/../t
|
||||
|
||||
# ── predicates / pure helpers ────────────────────────────────────────────────
|
||||
|
||||
# echo the gitflow type of a branch: feature|bugfix|release|hotfix|main|develop|other
|
||||
# echo the gitflow type of a branch: feature|bugfix|release|hotfix|chore|main|develop|other
|
||||
gitflow_branch_type() {
|
||||
local br="${1:-$(git symbolic-ref --short -q HEAD 2>/dev/null)}"
|
||||
case "$br" in
|
||||
@ -31,6 +31,7 @@ gitflow_branch_type() {
|
||||
bugfix/*) echo bugfix ;;
|
||||
release/*) echo release ;;
|
||||
hotfix/*) echo hotfix ;;
|
||||
chore/*) echo chore ;;
|
||||
*) echo other ;;
|
||||
esac
|
||||
}
|
||||
@ -46,7 +47,7 @@ gitflow_protected_base() {
|
||||
# echo the base a given type must fork from.
|
||||
gitflow_base_for() {
|
||||
case "$1" in
|
||||
feature|bugfix|release) echo "$GITFLOW_DEVELOP" ;;
|
||||
feature|bugfix|release|chore) echo "$GITFLOW_DEVELOP" ;;
|
||||
hotfix) echo "$GITFLOW_MAIN" ;;
|
||||
*) echo "gitflow: unknown type '$1'" >&2; return 2 ;;
|
||||
esac
|
||||
@ -103,7 +104,7 @@ gitflow_finish() {
|
||||
br="$(git symbolic-ref --short -q HEAD)" || { echo "gitflow_finish: detached HEAD" >&2; return 3; }
|
||||
type="$(gitflow_branch_type "$br")"
|
||||
case "$type" in
|
||||
feature|bugfix)
|
||||
feature|bugfix|chore)
|
||||
_gitflow_merge_into "$GITFLOW_DEVELOP" "$br" && _gitflow_delete "$br" ;;
|
||||
release)
|
||||
_gitflow_merge_into "$GITFLOW_MAIN" "$br" \
|
||||
|
||||
@ -50,6 +50,13 @@ Running `/capitalize` right after a ritual should propose (near) nothing.
|
||||
This skill is NOT `/prune-memory` (registry curation — merge, compress,
|
||||
mark-superseded). It only appends.
|
||||
|
||||
## Gitflow aiguillage (before any write)
|
||||
|
||||
Before STEP 4 writes anything, follow `$HOME/.claude/lib/gitflow-aiguillage.md`
|
||||
— this skill's TYPE = `chore`. On `main`/`develop` it branches to `chore/<name>`
|
||||
off develop, so the memory commit lands on a branch, never direct on a protected
|
||||
base; on a working branch it proceeds in place. Never `gitflow finish` (human-gated).
|
||||
|
||||
## STEP 0 — PRECHECK
|
||||
|
||||
```bash
|
||||
|
||||
@ -34,4 +34,7 @@ Ritual answers are deduped like any other candidate — a dup is dropped and its
|
||||
existing ID shown, not re-logged. This is the upgrade over the legacy `/close`,
|
||||
which wrote ritual answers fresh with no dedup.
|
||||
|
||||
The gitflow aiguillage (branch to `chore/*` on a protected base before writing)
|
||||
runs inside `capitalize` — not duplicated here.
|
||||
|
||||
→ Use the Skill tool to launch `capitalize` with argument `--ritual`.
|
||||
|
||||
@ -68,11 +68,13 @@ gives a **real-time, explicit go for THIS merge** — "merge it", "feature OK",
|
||||
|
||||
All of these mean: present the merge as a question, then wait for the explicit go.
|
||||
|
||||
## Aiguillage (assistance skills)
|
||||
## Aiguillage (assistance + standalone memory/doc skills)
|
||||
|
||||
On a protected base, assistance skills (`feat`/`bugfix`/`hotfix`) call
|
||||
`start <type>` to branch first; on a working branch they commit in place. Same
|
||||
`protected-base` predicate the out-of-skill hook uses.
|
||||
On a protected base, assistance skills (`feat`/`bugfix`/`hotfix`) AND the standalone
|
||||
memory/doc skills (`capitalize`/`close`/`prune-memory`/`reconcile`, TYPE `chore`)
|
||||
call `start <type>` to branch first; on a working branch they commit in place. Same
|
||||
`protected-base` predicate the out-of-skill hook uses. Caller→type map + rationale:
|
||||
`lib/gitflow-aiguillage.md`.
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
|
||||
@ -62,6 +62,13 @@ If working tree is dirty on any registry file → STOP with: "Commit or
|
||||
stash pending changes in `.claude/memory/` first. Skill writes in-place.
|
||||
Git is the only backup."
|
||||
|
||||
## STEP 0b — Gitflow aiguillage (after PRECHECK, before any write)
|
||||
|
||||
PRECHECK first (clean tree = the backup). Then follow
|
||||
`$HOME/.claude/lib/gitflow-aiguillage.md` — this skill's TYPE = `chore`. On
|
||||
`main`/`develop` it branches to `chore/<name>` off develop so the curation lands
|
||||
on a branch; on a working branch it proceeds in place. Never `gitflow finish`.
|
||||
|
||||
## STEP 1 — AUDIT (per registry)
|
||||
|
||||
For each target registry (filter by `$ARGUMENTS` or all 5):
|
||||
|
||||
@ -34,6 +34,8 @@ Not for: curating/compressing registries → `/prune-memory`. The skill never ed
|
||||
Plus **contradiction candidates** — `reconcile_contradiction_candidates`: accepted-BDR ⇄ open-chantier overlap, surfaced for human review.
|
||||
|
||||
## The gate (mandatory)
|
||||
**Before applying (A/B):** follow `$HOME/.claude/lib/gitflow-aiguillage.md` — TYPE `chore`. On `main`/`develop` the write-back branches to `chore/<name>` off develop first, so a reconciled TODO never lands direct on a protected base; on a working branch it applies in place. Never `gitflow finish` (human-gated).
|
||||
|
||||
Reconciling the TODO edits a tracked file → never silent. Show the proposed diff, then ask: **A** apply all · **B** select a subset · **C** touch nothing. Registries stay READ-ONLY (append-only; curation is `/prune-memory`).
|
||||
|
||||
## Honest limits (do not over-read the guarantee)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user