diff --git a/agents/client-handover-writer.md b/agents/client-handover-writer.md index c9a7271..0273722 100644 --- a/agents/client-handover-writer.md +++ b/agents/client-handover-writer.md @@ -571,7 +571,7 @@ If `PENDING_CHANGES` non-empty → invoke /commit-change skill via subagent: `git branch --show-current` → `
`; `git remote get-url origin >/dev/null 2>&1 && echo origin || echo no-origin`; `git rev-list --count origin/
..
2>/dev/null || echo unknown` → -`ahead`. Validate `
` against `^[A-Za-z0-9._/-]+$` before using it +`ahead`. Validate `
` against `^[A-Za-z0-9._/][A-Za-z0-9._/-]*$` before using it anywhere (git accepts shell metacharacters in branch names). On mismatch: state = `unknown (branch name contains characters this pipeline refuses to interpolate: push by hand after renaming the branch)`, interpolate NOTHING, diff --git a/lib/gitflow-test.sh b/lib/gitflow-test.sh index 0c2d82d..c6bdd67 100644 --- a/lib/gitflow-test.sh +++ b/lib/gitflow-test.sh @@ -6,6 +6,7 @@ # (the chk helper EVALs its second arg; single-quoted assertion strings are # intentional — they must not expand at definition time.) set -uo pipefail +export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null HERE="$(cd "$(dirname "$0")" && pwd)" # Do NOT override GITFLOW_GITIGNORE_TEMPLATE: the lib self-resolves it from its # own location (../templates), which is correct in both the repo and installed. diff --git a/lib/gitflow.sh b/lib/gitflow.sh index 6732258..db008b6 100644 --- a/lib/gitflow.sh +++ b/lib/gitflow.sh @@ -81,7 +81,8 @@ gitflow_push_mode() { case "$rc:$val" in 0:false) echo manual ;; 0:true|1:*) echo auto ;; - *) raw=$(git config gitflow.autopush 2>/dev/null) + *) raw=$(git config gitflow.autopush 2>/dev/null | LC_ALL=C tr -cd '[:print:]' 2>/dev/null) + raw=${raw:0:64} if [ -n "$raw" ]; then echo "gitflow.sh push-mode: gitflow.autopush='$raw'" \ "is not a boolean (git rc $rc)" >&2 diff --git a/skills/capitalize/SKILL.md b/skills/capitalize/SKILL.md index b421f33..1045007 100644 --- a/skills/capitalize/SKILL.md +++ b/skills/capitalize/SKILL.md @@ -317,7 +317,7 @@ journal-only example. Surgical scope is the helper's (stages ONLY `.claude/memory` + `.claude/tasks`, changed-paths-filtered, never `git add -A`). Do NOT hand-roll git here. -## STEP 5C — AUTO-PERSIST THE MEMORY (finish + push) +## STEP 5C — AUTO-PERSIST THE MEMORY (finish; the lib pushes in auto-push mode) Memory's value is cross-session persistence — a commit stranded on an unmerged `chore/` branch is invisible to the next session sitting on develop, so the @@ -337,11 +337,11 @@ Skip this step entirely (go to STEP 6, which prints the hold note) on 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. -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. +1. `bash "$HOME/.claude/lib/gitflow.sh" finish chore ` — merge → develop, delete branch, push develop in auto-push mode. rc≠0 → go to STEP 6 with the `finish failed` line (rc 1/4 skip calls 2-3; rc 5/2/6 with the ancestor check true still run them so the push state is reported): 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): +Outcomes, evaluated IN THIS ORDER (finish rc 0, or rc 5/2/6 with the branch merged — the wording then starts with `merged, branch not deleted (rc ) —` instead of `merged to develop —`): - **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). @@ -368,16 +368,17 @@ CAPITALIZE COMPLETE — () Then the closing line — pick by the STEP 5C persist result (`` = `Context 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.` -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. +- **auto-persisted (5C: finish rc 0 AND `ahead` = 0)** → `✅ + persisted to origin/develop (). Next session: read .claude/memory/ at startup.` + +On the `--no-push` path ONLY 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. The WORKING-branch and rc 3 paths have no `chore/` and never use the mode. - **--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).` +- **not on origin (merged, `ahead` unknown)** → `✅ + merged to develop — not on origin (no origin/develop ref).` Push mode manual → add `You: ! git push origin develop once a remote exists`. - **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.` +- **finish failed** → `⚠️ + finish rc : — chore/ kept, NOT merged; resolve by hand.` (rc 1/4 only; rc 5/2/6 with the branch merged use the outcome lines above with the `merged, branch not deleted (rc )` prefix, so the push state is still reported.) - **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.`