diff --git a/lib/gitflow-aiguillage.md b/lib/gitflow-aiguillage.md index 196f4b9..35235f7 100644 --- a/lib/gitflow-aiguillage.md +++ b/lib/gitflow-aiguillage.md @@ -37,8 +37,8 @@ exemption still lets a *manual* memory commit through on a protected base, but a skill-driven one now branches to `chore/*` first. **Integration is human-gated by default** — these flows commit, they do not merge. -EXCEPTION: `/capitalize` + `/close` auto-persist their memory-only commit (finish → -develop + push) when THEY branched a `chore/*` off develop this run (BDR-068 — a +EXCEPTION: `/capitalize` + `/close` auto-persist their memory-only commit (finish → develop; the lib pushes develop in auto-push mode only) +when THEY branched a `chore/*` off develop this run (BDR-068 — a scoped [[LRN-069]] exception; see the capitalize skill's STEP 5C). `/prune-memory` + `/reconcile` stay fully human-gated: never run `gitflow finish` from them. diff --git a/lib/gitflow-test.sh b/lib/gitflow-test.sh index 3579b83..0c2d82d 100644 --- a/lib/gitflow-test.sh +++ b/lib/gitflow-test.sh @@ -172,6 +172,22 @@ if bash "$HERE/gitflow.sh" protected-base main; then ok "cli protected-bas if bash "$HERE/gitflow.sh" protected-base feature/x; then no "cli protected-base feature (rc0?)"; else ok "cli protected-base feature → rc1"; fi chk "cli base-for hotfix=main" '[ "$(bash "$HERE/gitflow.sh" base-for hotfix)" = main ]' +echo "T11b — push-mode verb (the sanctioned reader for skills, BDR-112)" +newrepo pm; echo a>a +bash "$HERE/gitflow.sh" init >/dev/null 2>&1 +chk "cli push-mode default auto" '[ "$(bash "$HERE/gitflow.sh" push-mode)" = auto ]' +git config gitflow.autopush true +chk "cli push-mode true auto" '[ "$(bash "$HERE/gitflow.sh" push-mode)" = auto ]' +git config gitflow.autopush false +chk "cli push-mode manual" '[ "$(bash "$HERE/gitflow.sh" push-mode)" = manual ]' +git config gitflow.autopush flase +pm_out=$(bash "$HERE/gitflow.sh" push-mode 2>"$WORK/pm.err"); pm_rc=$? +chk "cli push-mode invalid, rc 0, value on stderr" "[ $pm_rc -eq 0 ] && [ \"$pm_out\" = invalid ] && grep -q flase \"$WORK/pm.err\"" +printf '[gitflow\n' >> .git/config +pm2_out=$(bash "$HERE/gitflow.sh" push-mode 2>/dev/null); pm2_rc=$? +chk "cli push-mode corrupt config → invalid, rc 0" "[ $pm2_rc -eq 0 ] && [ \"$pm2_out\" = invalid ]" +chk "cli usage lists push-mode" 'grep -q push-mode <<<"$(bash "$HERE/gitflow.sh" nope 2>&1)"' + echo "T12 — finish arg-guard (named branch must equal current, else refuse)" newrepo finargs; echo a>a; hookon; gitflow_init >/dev/null 2>&1 gitflow_start feature standon >/dev/null 2>&1; echo w>w.txt; git add w.txt; git commit -q -m w diff --git a/lib/gitflow.sh b/lib/gitflow.sh index 1ef2cd1..6732258 100644 --- a/lib/gitflow.sh +++ b/lib/gitflow.sh @@ -68,6 +68,32 @@ gitflow_release_open() { [ -n "$(git for-each-ref --format='%(refname:short)' 'refs/heads/release/*')" ] } +# gitflow_push_mode -> stdout auto | manual | invalid, rc 0 always. The ONE +# reader skills may call: `git config ... gitflow.*` is statically denied to +# Claude (BDR-112). manual = key reads false; auto = true or unset; invalid = +# anything else (unparseable value, git failure); the raw value goes to +# stderr so the caller can name it. Reads only. Ignores GITFLOW_NO_PUSH (a +# test-repo switch, not a mode): a caller that pushes must not rely on this +# verb alone, the lib's own push sites use _gitflow_push_off. +gitflow_push_mode() { + local val rc raw + val=$(git config --bool gitflow.autopush 2>/dev/null); rc=$? + case "$rc:$val" in + 0:false) echo manual ;; + 0:true|1:*) echo auto ;; + *) raw=$(git config gitflow.autopush 2>/dev/null) + if [ -n "$raw" ]; then + echo "gitflow.sh push-mode: gitflow.autopush='$raw'" \ + "is not a boolean (git rc $rc)" >&2 + else + echo "gitflow.sh push-mode: could not read" \ + "gitflow.autopush (git rc $rc)" >&2 + fi + echo invalid ;; + esac + return 0 +} + # ── start ──────────────────────────────────────────────────────────────────── # rc 0 when pushing is off: GITFLOW_NO_PUSH=1 (throwaway test repos) or @@ -597,6 +623,7 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then delete) gitflow_delete "$@" ;; merged) [ -n "${1:-}" ] || { echo "usage: gitflow.sh merged " >&2; exit 2; } gitflow_merged_into_base "$1" ;; + push-mode) gitflow_push_mode ;; hooks) printf '%s\n' "${GITFLOW_HOOKS[@]}" ;; init) gitflow_init "$@" ;; reconcile) gitflow_reconcile_gitignore "$@" ;; @@ -606,6 +633,6 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then global-hooks) gitflow_global_hooks "$@" ;; emit-hook) _gitflow_emit_hook "${1:-pre-commit}" \ || { echo "gitflow.sh emit-hook {$(IFS='|'; echo "${GITFLOW_HOOKS[*]}")}" >&2; exit 2; } ;; - *) echo "usage: gitflow.sh {type|protected-base|base-for|release-open|start|finish|delete
|merged
|init|reconcile|purge-transient|install-hook|reconcile-hooks|global-hooks [value]|hooks|emit-hook }" >&2; exit 2 ;; + *) echo "usage: gitflow.sh {type|protected-base|base-for|release-open|start|finish|delete
|merged
|push-mode|init|reconcile|purge-transient|install-hook|reconcile-hooks|global-hooks [value]|hooks|emit-hook }" >&2; exit 2 ;; esac fi diff --git a/skills/capitalize/SKILL.md b/skills/capitalize/SKILL.md index 74b96fb..b421f33 100644 --- a/skills/capitalize/SKILL.md +++ b/skills/capitalize/SKILL.md @@ -10,7 +10,7 @@ description: | Triggers: "capitalize", "before clear/compact", "flush memory", "don't lose this", "avant de clear/compact", "capitalise ce qui manque", "close", "fin de journée", "checkpoint memory". -argument-hint: "[--ritual] [--no-push] (scans conversation + git + TODO against .claude/memory/; --ritual adds the 3-question reflection; --no-push holds memory on chore/: pushed to origin by the hooks, NOT merged (finish skipped), merge when ready; default = auto-finish into develop)" +argument-hint: "[--ritual] [--no-push] (scans conversation + git + TODO against .claude/memory/; --ritual adds the 3-question reflection; --no-push holds memory on chore/: pushed to origin by the hooks in auto-push mode, NOT merged (finish skipped), merge when ready; default = auto-finish into develop)" allowed-tools: - Read - Edit @@ -332,17 +332,22 @@ pre-BDR-068 behavior): branch the memory already rides feature/bugfix — never auto-merge it), AND - `--no-push` was NOT passed (the hold escape hatch). -Then, from the `chore/` branch: +Skip this step entirely (go to STEP 6, which prints the hold note) on +`--no-push`, on a WORKING branch, or when STEP 5B returned rc 3. - bash "$HOME/.claude/lib/gitflow.sh" finish chore # merge → develop, delete branch - git push origin develop +Otherwise, from the `chore/` branch, THREE separate Bash calls, never combined. INVARIANT: no `git push` inside any Bash call of this skill (push-guard reads command text; the lib pushes develop itself in auto-push mode). The hints that tell the USER what to type (`! git push …`) are prose, kept on single lines. -- **finish + push OK** → surface `develop pushed` in STEP 6. -- **push fails** (offline / rejected) → the merge to develop ALREADY happened - locally; report `merged to develop, push FAILED — push manually`. Do NOT retry - or reset the merge. -- **`--no-push` / WORKING branch / rc 3** → skip this step; the commit stays where - it is. STEP 6 prints the manual-merge note. +1. `bash "$HOME/.claude/lib/gitflow.sh" finish chore ` — merge → develop, delete branch, push develop in auto-push mode. rc≠0 → skip calls 2-3, go to STEP 6 with the `finish failed` line: rc 4 = conflict, develop mid-merge, `chore/` kept, NOT merged; rc 1 = checkout failed, NOT merged; rc 5/2/6 come from the delete AFTER the merge: check `git merge-base --is-ancestor chore/ develop` and report `merged, branch not deleted (rc )` when it holds, `NOT merged` otherwise. Never say "merged" without that check. +2. `bash "$HOME/.claude/lib/gitflow.sh" push-mode` → `auto | manual | invalid` (stderr names an invalid value). +3. `git rev-list --count origin/develop..develop 2>/dev/null || echo unknown` → `ahead` (0 = on origin; `unknown` = no origin/develop ref, e.g. no origin remote). + +Outcomes, evaluated IN THIS ORDER (all require finish rc 0): + +- **push mode `invalid`** → `merged to develop — gitflow.autopush= is not a boolean: the lib and hooks still push on an invalid value until run D (origin/develop is commit(s) behind, or unknown); fix the value by hand`. +- **`ahead` = 0** → `develop pushed` (auto-push mode did it). +- **`ahead` = unknown** → `merged to develop — not on origin (no origin/develop ref; no remote or never fetched)`; push mode manual → add `You: ! git push origin develop once a remote exists`. +- **`ahead` > 0, push mode `manual`** → `merged to develop — manual push mode: not pushed. You: ! git push origin develop`. +- **`ahead` > 0, push mode `auto`** → `merged to develop — push FAILED (see finish stderr); push manually`. Do NOT retry or reset the merge. ## STEP 6 — FINAL OUTPUT + HANDOFF @@ -355,7 +360,7 @@ CAPITALIZE COMPLETE — () TODO.md : checked , added journal.md : +1 line under ## committed : (chore(memory): …) | ⚠️ NOT committed (rc 3 — see closing line) - persisted : develop pushed | on chore/, not merged (--no-push) | merged, push FAILED + persisted : develop pushed | merged, manual push mode: not pushed | merged, not on origin (no origin/develop) | merged, push FAILED | merged, gitflow.autopush invalid ( behind) | finish rc , not merged | merged, branch not deleted (rc ) | on chore/, not merged (--no-push) dropped as already-captured: LRN-023, BLK-006 ignored as noise: push/tag release ``` @@ -364,8 +369,15 @@ Then the closing line — pick by the STEP 5C persist result (`` = `Contex flushed` for pre-wipe, `Session closed` for ritual): - **auto-persisted (default — branched off develop, pushed)** → `✅ + persisted to origin/develop (). Next session: read .claude/memory/ at startup.` -- **--no-push (held on branch)** → `✅ + committed on chore/ — pushed to origin by the hooks, NOT merged (--no-push: finish skipped). Merge when ready.` +On the `--no-push` path (and on any 5B-committed path where 5C did not run) read TWO facts first, each its own Bash call: `bash "$HOME/.claude/lib/gitflow.sh" push-mode` and `git rev-list --count origin/chore/..chore/ 2>/dev/null || echo unknown` (`branch_ahead`). `` below is the verb's word. + +- **--no-push, `branch_ahead` = 0** → `✅ + committed on chore/ — pushed to origin by the hooks (auto-push mode), NOT merged (--no-push). Merge when ready.` +- **--no-push, `branch_ahead` > 0 or unknown** → `✅ + committed on chore/ — this disk only, not pushed (), NOT merged. You: ! git push -u origin chore/; merge when ready.` With push mode `invalid`, append ` gitflow.autopush= is not a boolean: fix it by hand`. +- **manual (merged, `ahead` > 0)** → `✅ + merged to develop — manual push mode: not pushed. You: ! git push origin develop` +- **not on origin (merged, `ahead` unknown)** → `✅ + merged to develop — not on origin (no origin/develop ref).` +- **invalid (merged)** → `⚠️ + merged to develop — gitflow.autopush= is not a boolean; lib/hooks still push on it until run D (origin/develop behind). Fix the value by hand.` - **push failed after merge** → `✅ + merged to develop — ⚠️ push FAILED (); merged locally, push manually.` +- **finish failed** → `⚠️ + finish rc : — chore/ kept, NOT merged (or: merged, branch not deleted); resolve by hand.` - **WORKING branch (rode a feature branch)** → `✅ + committed on . Integrates when the branch merges.` - **commit skipped (rc 3)** → keep the ✅ on the WRITE but make the gap loud, never buried: `✅ — ⚠️ NOT committed (); entries safe on disk, commit manually.` @@ -400,7 +412,7 @@ manual commit (rc 3). always produces a commit; only an unsafe git state (rc 3) skips it. - **Auto-persist the flush (STEP 5C, BDR-068)** — a memory-only commit on a `chore/` branch THIS run created off develop auto-finishes → develop + - pushes; a scoped exception to LRN-069. `--no-push` holds it on the branch; a + pushes (the lib pushes develop in auto-push mode only; manual mode merges and leaves the push to the user); a scoped exception to LRN-069. `--no-push` holds it on the branch; a WORKING branch (memory rides feature/bugfix) or rc 3 skips it. NEVER auto-finish a branch the run did not create. - **Skip trivial** for the 4 ID registries; journal excepted. diff --git a/skills/close/SKILL.md b/skills/close/SKILL.md index af68e3b..28e2192 100644 --- a/skills/close/SKILL.md +++ b/skills/close/SKILL.md @@ -9,7 +9,7 @@ description: | (that is /prune-memory). Triggers: "close", "end session", "ferme la session", "session close", "checkpoint memory", "what did we learn", "retro rapide", "fin de journée". -argument-hint: "[--no-push] (runs capitalize in ritual mode; --no-push holds memory on chore/: pushed to origin by the hooks, NOT merged (finish skipped), merge when ready; default = auto-finish into develop)" +argument-hint: "[--no-push] (runs capitalize in ritual mode; --no-push holds memory on chore/: pushed to origin by the hooks in auto-push mode, NOT merged (finish skipped), merge when ready; default = auto-finish into develop)" allowed-tools: - Read - Edit @@ -28,7 +28,7 @@ allowed-tools: Invoke the `capitalize` skill now and run it in **ritual mode**: the full pipeline (STEP 0 precheck → STEP 1 auto-scan → STEP 2 dedup → STEP 2B TODO reconcile → STEP 3 approval gate → STEP 4 write → STEP 5 journal → STEP 5B -memory commit → STEP 5C auto-persist: finish + push, BDR-068 — pass +memory commit → STEP 5C auto-persist: finish (push rides it in auto-push mode), BDR-068 — pass `--no-push` through to hold the chore branch instead → STEP 6 handoff), PLUS STEP 1B's explicit 3-question reflection (what did you decide / learn / block).