feat(gitflow): push-mode verb; /close reports the push state instead of pushing
Run C1 of manual-push mode (BDR-111/BDR-112).
- lib/gitflow.sh: `gitflow.sh push-mode` prints auto | manual | invalid
(rc 0; an invalid value is named on stderr). It is the one reader a
skill may call: the bare `git config … gitflow.*` read is denied to
Claude since run B. Ignores GITFLOW_NO_PUSH by design (documented).
- skills/capitalize/SKILL.md STEP 5C: the explicit `git push origin
develop` is gone — `finish` has pushed develop itself since BDR-095,
mode-aware since run A. 5C is now three separate read-only calls
(finish; push-mode; `git rev-list --count origin/develop..develop`)
and prose outcomes keyed on the real ahead count: pushed / manual push
mode, you push / not on origin / push FAILED / invalid value named,
plus a finish-failure outcome (merge vs delete rc distinguished).
STEP 6 closing lines and the recap carry every outcome; the
`--no-push` line reads the branch's own ahead count ("this disk only"
only when true). Invariant: no `git push` inside any Bash call; the
user hints are prose.
- skills/close/SKILL.md, lib/gitflow-aiguillage.md: "push" claims
qualified "in auto-push mode".
- lib/gitflow-test.sh T11b: six cases for the verb (default, true,
false, non-boolean with stderr + rc 0, corrupt config, usage).
Polish items from the gates are listed in TODO.md (C1 polish).
This commit is contained in:
@@ -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.
|
skill-driven one now branches to `chore/*` first.
|
||||||
|
|
||||||
**Integration is human-gated by default** — these flows commit, they do not merge.
|
**Integration is human-gated by default** — these flows commit, they do not merge.
|
||||||
EXCEPTION: `/capitalize` + `/close` auto-persist their memory-only commit (finish →
|
EXCEPTION: `/capitalize` + `/close` auto-persist their memory-only commit (finish → develop; the lib pushes develop in auto-push mode only)
|
||||||
develop + push) when THEY branched a `chore/*` off develop this run (BDR-068 — a
|
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`
|
scoped [[LRN-069]] exception; see the capitalize skill's STEP 5C). `/prune-memory`
|
||||||
+ `/reconcile` stay fully human-gated: never run `gitflow finish` from them.
|
+ `/reconcile` stay fully human-gated: never run `gitflow finish` from them.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
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 ]'
|
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)"
|
echo "T12 — finish arg-guard (named branch must equal current, else refuse)"
|
||||||
newrepo finargs; echo a>a; hookon; gitflow_init >/dev/null 2>&1
|
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
|
gitflow_start feature standon >/dev/null 2>&1; echo w>w.txt; git add w.txt; git commit -q -m w
|
||||||
|
|||||||
+28
-1
@@ -68,6 +68,32 @@ gitflow_release_open() {
|
|||||||
[ -n "$(git for-each-ref --format='%(refname:short)' 'refs/heads/release/*')" ]
|
[ -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 ────────────────────────────────────────────────────────────────────
|
# ── start ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
# rc 0 when pushing is off: GITFLOW_NO_PUSH=1 (throwaway test repos) or
|
# 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 "$@" ;;
|
delete) gitflow_delete "$@" ;;
|
||||||
merged) [ -n "${1:-}" ] || { echo "usage: gitflow.sh merged <branch>" >&2; exit 2; }
|
merged) [ -n "${1:-}" ] || { echo "usage: gitflow.sh merged <branch>" >&2; exit 2; }
|
||||||
gitflow_merged_into_base "$1" ;;
|
gitflow_merged_into_base "$1" ;;
|
||||||
|
push-mode) gitflow_push_mode ;;
|
||||||
hooks) printf '%s\n' "${GITFLOW_HOOKS[@]}" ;;
|
hooks) printf '%s\n' "${GITFLOW_HOOKS[@]}" ;;
|
||||||
init) gitflow_init "$@" ;;
|
init) gitflow_init "$@" ;;
|
||||||
reconcile) gitflow_reconcile_gitignore "$@" ;;
|
reconcile) gitflow_reconcile_gitignore "$@" ;;
|
||||||
@@ -606,6 +633,6 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
|||||||
global-hooks) gitflow_global_hooks "$@" ;;
|
global-hooks) gitflow_global_hooks "$@" ;;
|
||||||
emit-hook) _gitflow_emit_hook "${1:-pre-commit}" \
|
emit-hook) _gitflow_emit_hook "${1:-pre-commit}" \
|
||||||
|| { echo "gitflow.sh emit-hook {$(IFS='|'; echo "${GITFLOW_HOOKS[*]}")}" >&2; exit 2; } ;;
|
|| { 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 <br>|merged <br>|init|reconcile|purge-transient|install-hook|reconcile-hooks|global-hooks <dir> [value]|hooks|emit-hook <name>}" >&2; exit 2 ;;
|
*) echo "usage: gitflow.sh {type|protected-base|base-for|release-open|start|finish|delete <br>|merged <br>|push-mode|init|reconcile|purge-transient|install-hook|reconcile-hooks|global-hooks <dir> [value]|hooks|emit-hook <name>}" >&2; exit 2 ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|||||||
+25
-13
@@ -10,7 +10,7 @@ description: |
|
|||||||
Triggers: "capitalize", "before clear/compact", "flush memory", "don't
|
Triggers: "capitalize", "before clear/compact", "flush memory", "don't
|
||||||
lose this", "avant de clear/compact", "capitalise ce qui manque",
|
lose this", "avant de clear/compact", "capitalise ce qui manque",
|
||||||
"close", "fin de journée", "checkpoint memory".
|
"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/<name>: 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/<name>: pushed to origin by the hooks in auto-push mode, NOT merged (finish skipped), merge when ready; default = auto-finish into develop)"
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Read
|
- Read
|
||||||
- Edit
|
- Edit
|
||||||
@@ -332,17 +332,22 @@ pre-BDR-068 behavior):
|
|||||||
branch the memory already rides feature/bugfix — never auto-merge it), AND
|
branch the memory already rides feature/bugfix — never auto-merge it), AND
|
||||||
- `--no-push` was NOT passed (the hold escape hatch).
|
- `--no-push` was NOT passed (the hold escape hatch).
|
||||||
|
|
||||||
Then, from the `chore/<name>` 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 <name> # merge → develop, delete branch
|
Otherwise, from the `chore/<name>` 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.
|
||||||
git push origin develop
|
|
||||||
|
|
||||||
- **finish + push OK** → surface `develop <short> pushed` in STEP 6.
|
1. `bash "$HOME/.claude/lib/gitflow.sh" finish chore <name>` — 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/<name>` 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/<name> develop` and report `merged, branch not deleted (rc <n>)` when it holds, `NOT merged` otherwise. Never say "merged" without that check.
|
||||||
- **push fails** (offline / rejected) → the merge to develop ALREADY happened
|
2. `bash "$HOME/.claude/lib/gitflow.sh" push-mode` → `auto | manual | invalid` (stderr names an invalid value).
|
||||||
locally; report `merged to develop, push FAILED — push manually`. Do NOT retry
|
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).
|
||||||
or reset the merge.
|
|
||||||
- **`--no-push` / WORKING branch / rc 3** → skip this step; the commit stays where
|
Outcomes, evaluated IN THIS ORDER (all require finish rc 0):
|
||||||
it is. STEP 6 prints the manual-merge note.
|
|
||||||
|
- **push mode `invalid`** → `merged to develop — gitflow.autopush=<value from stderr> is not a boolean: the lib and hooks still push on an invalid value until run D (origin/develop is <ahead> commit(s) behind, or unknown); fix the value by hand`.
|
||||||
|
- **`ahead` = 0** → `develop <short> 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
|
## STEP 6 — FINAL OUTPUT + HANDOFF
|
||||||
|
|
||||||
@@ -355,7 +360,7 @@ CAPITALIZE COMPLETE — <YYYY-MM-DD> (<pre-wipe flush | session-close>)
|
|||||||
TODO.md : checked <N>, added <M>
|
TODO.md : checked <N>, added <M>
|
||||||
journal.md : +1 line under ## <date>
|
journal.md : +1 line under ## <date>
|
||||||
committed : <mem_hash> (chore(memory): …) | ⚠️ NOT committed (rc 3 — see closing line)
|
committed : <mem_hash> (chore(memory): …) | ⚠️ NOT committed (rc 3 — see closing line)
|
||||||
persisted : develop <short> pushed | on chore/<name>, not merged (--no-push) | merged, push FAILED
|
persisted : develop <short> pushed | merged, manual push mode: not pushed | merged, not on origin (no origin/develop) | merged, push FAILED | merged, gitflow.autopush invalid (<ahead> behind) | finish rc <n>, not merged | merged, branch not deleted (rc <n>) | on chore/<name>, not merged (--no-push)
|
||||||
dropped as already-captured: LRN-023, BLK-006
|
dropped as already-captured: LRN-023, BLK-006
|
||||||
ignored as noise: push/tag release
|
ignored as noise: push/tag release
|
||||||
```
|
```
|
||||||
@@ -364,8 +369,15 @@ Then the closing line — pick by the STEP 5C persist result (`<mode>` = `Contex
|
|||||||
flushed` for pre-wipe, `Session closed` for ritual):
|
flushed` for pre-wipe, `Session closed` for ritual):
|
||||||
|
|
||||||
- **auto-persisted (default — branched off develop, pushed)** → `✅ <mode> + persisted to origin/develop (<short>). Next session: read .claude/memory/ at startup.`
|
- **auto-persisted (default — branched off develop, pushed)** → `✅ <mode> + persisted to origin/develop (<short>). Next session: read .claude/memory/ at startup.`
|
||||||
- **--no-push (held on branch)** → `✅ <mode> + committed on chore/<name> — 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/<name>..chore/<name> 2>/dev/null || echo unknown` (`branch_ahead`). `<push mode>` below is the verb's word.
|
||||||
|
|
||||||
|
- **--no-push, `branch_ahead` = 0** → `✅ <mode> + committed on chore/<name> — pushed to origin by the hooks (auto-push mode), NOT merged (--no-push). Merge when ready.`
|
||||||
|
- **--no-push, `branch_ahead` > 0 or unknown** → `✅ <mode> + committed on chore/<name> — this disk only, not pushed (<push mode manual | no origin/chore ref>), NOT merged. You: ! git push -u origin chore/<name>; merge when ready.` With push mode `invalid`, append ` gitflow.autopush=<value> is not a boolean: fix it by hand`.
|
||||||
|
- **manual (merged, `ahead` > 0)** → `✅ <mode> + merged to develop — manual push mode: not pushed. You: ! git push origin develop`
|
||||||
|
- **not on origin (merged, `ahead` unknown)** → `✅ <mode> + merged to develop — not on origin (no origin/develop ref).`
|
||||||
|
- **invalid (merged)** → `⚠️ <mode> + merged to develop — gitflow.autopush=<value> is not a boolean; lib/hooks still push on it until run D (origin/develop <ahead> behind). Fix the value by hand.`
|
||||||
- **push failed after merge** → `✅ <mode> + merged to develop — ⚠️ push FAILED (<reason>); merged locally, push manually.`
|
- **push failed after merge** → `✅ <mode> + merged to develop — ⚠️ push FAILED (<reason>); merged locally, push manually.`
|
||||||
|
- **finish failed** → `⚠️ <mode> + finish rc <n>: <stderr> — chore/<name> kept, NOT merged (or: merged, branch not deleted); resolve by hand.`
|
||||||
- **WORKING branch (rode a feature branch)** → `✅ <mode> + committed <mem_hash> on <branch>. Integrates when the branch merges.`
|
- **WORKING branch (rode a feature branch)** → `✅ <mode> + committed <mem_hash> on <branch>. Integrates when the branch merges.`
|
||||||
- **commit skipped (rc 3)** → keep the ✅ on the WRITE but make the gap loud, never
|
- **commit skipped (rc 3)** → keep the ✅ on the WRITE but make the gap loud, never
|
||||||
buried: `✅ <mode> — ⚠️ NOT committed (<reason: detached/merge/non-git>); entries safe on disk, commit manually.`
|
buried: `✅ <mode> — ⚠️ NOT committed (<reason: detached/merge/non-git>); 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.
|
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
|
- **Auto-persist the flush (STEP 5C, BDR-068)** — a memory-only commit on a
|
||||||
`chore/<name>` branch THIS run created off develop auto-finishes → develop +
|
`chore/<name>` 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
|
WORKING branch (memory rides feature/bugfix) or rc 3 skips it. NEVER auto-finish
|
||||||
a branch the run did not create.
|
a branch the run did not create.
|
||||||
- **Skip trivial** for the 4 ID registries; journal excepted.
|
- **Skip trivial** for the 4 ID registries; journal excepted.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ description: |
|
|||||||
(that is /prune-memory).
|
(that is /prune-memory).
|
||||||
Triggers: "close", "end session", "ferme la session", "session close",
|
Triggers: "close", "end session", "ferme la session", "session close",
|
||||||
"checkpoint memory", "what did we learn", "retro rapide", "fin de journée".
|
"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/<name>: 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/<name>: pushed to origin by the hooks in auto-push mode, NOT merged (finish skipped), merge when ready; default = auto-finish into develop)"
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Read
|
- Read
|
||||||
- Edit
|
- Edit
|
||||||
@@ -28,7 +28,7 @@ allowed-tools:
|
|||||||
Invoke the `capitalize` skill now and run it in **ritual mode**: the full
|
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
|
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
|
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),
|
`--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
|
PLUS STEP 1B's explicit 3-question reflection (what did you decide / learn
|
||||||
/ block).
|
/ block).
|
||||||
|
|||||||
Reference in New Issue
Block a user