fix(gitflow): run C polish — 5C coherence, sanitized verb stderr, hermetic suite
Closes the non-gap observations the gates left on runs C1/C2: - capitalize STEP 5C/6: heading no longer says "+ push"; the --no-push fact read is its own paragraph and scoped to that path; the auto-persisted line requires finish rc 0 AND ahead = 0; rc 5/2/6 (merged, branch not deleted) still report the push state; the "not on origin" line carries the once-a-remote-exists hint. - gitflow.sh push-mode: the raw config value echoed on stderr is reduced to printable characters (LC_ALL=C, BSD tr safe) and capped at 64. - gitflow-test.sh exports the hermetic git config env in the file, so a bare run on a global-manual machine stays green. - client-handover-writer: the branch allowlist refuses a leading dash.
This commit is contained in:
@@ -571,7 +571,7 @@ If `PENDING_CHANGES` non-empty → invoke /commit-change skill via subagent:
|
|||||||
`git branch --show-current` → `<br>`;
|
`git branch --show-current` → `<br>`;
|
||||||
`git remote get-url origin >/dev/null 2>&1 && echo origin || echo no-origin`;
|
`git remote get-url origin >/dev/null 2>&1 && echo origin || echo no-origin`;
|
||||||
`git rev-list --count origin/<br>..<br> 2>/dev/null || echo unknown` →
|
`git rev-list --count origin/<br>..<br> 2>/dev/null || echo unknown` →
|
||||||
`ahead`. Validate `<br>` against `^[A-Za-z0-9._/-]+$` before using it
|
`ahead`. Validate `<br>` against `^[A-Za-z0-9._/][A-Za-z0-9._/-]*$` before using it
|
||||||
anywhere (git accepts shell metacharacters in branch names). On mismatch:
|
anywhere (git accepts shell metacharacters in branch names). On mismatch:
|
||||||
state = `unknown (branch name contains characters this pipeline refuses to
|
state = `unknown (branch name contains characters this pipeline refuses to
|
||||||
interpolate: push by hand after renaming the branch)`, interpolate NOTHING,
|
interpolate: push by hand after renaming the branch)`, interpolate NOTHING,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
# (the chk helper EVALs its second arg; single-quoted assertion strings are
|
# (the chk helper EVALs its second arg; single-quoted assertion strings are
|
||||||
# intentional — they must not expand at definition time.)
|
# intentional — they must not expand at definition time.)
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
# Do NOT override GITFLOW_GITIGNORE_TEMPLATE: the lib self-resolves it from its
|
# 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.
|
# own location (../templates), which is correct in both the repo and installed.
|
||||||
|
|||||||
+2
-1
@@ -81,7 +81,8 @@ gitflow_push_mode() {
|
|||||||
case "$rc:$val" in
|
case "$rc:$val" in
|
||||||
0:false) echo manual ;;
|
0:false) echo manual ;;
|
||||||
0:true|1:*) echo auto ;;
|
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
|
if [ -n "$raw" ]; then
|
||||||
echo "gitflow.sh push-mode: gitflow.autopush='$raw'" \
|
echo "gitflow.sh push-mode: gitflow.autopush='$raw'" \
|
||||||
"is not a boolean (git rc $rc)" >&2
|
"is not a boolean (git rc $rc)" >&2
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ journal-only example.
|
|||||||
Surgical scope is the helper's (stages ONLY `.claude/memory` + `.claude/tasks`,
|
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.
|
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
|
Memory's value is cross-session persistence — a commit stranded on an unmerged
|
||||||
`chore/<name>` branch is invisible to the next session sitting on develop, so the
|
`chore/<name>` 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/<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.
|
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.
|
||||||
|
|
||||||
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.
|
1. `bash "$HOME/.claude/lib/gitflow.sh" finish chore <name>` — 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/<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.
|
||||||
2. `bash "$HOME/.claude/lib/gitflow.sh" push-mode` → `auto | manual | invalid` (stderr names an invalid value).
|
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).
|
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 <n>) —` instead of `merged to develop —`):
|
||||||
|
|
||||||
- **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`.
|
- **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` = 0** → `develop <short> pushed` (auto-push mode did it).
|
||||||
@@ -368,16 +368,17 @@ CAPITALIZE COMPLETE — <YYYY-MM-DD> (<pre-wipe flush | session-close>)
|
|||||||
Then the closing line — pick by the STEP 5C persist result (`<mode>` = `Context
|
Then the closing line — pick by the STEP 5C persist result (`<mode>` = `Context
|
||||||
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 (5C: finish rc 0 AND `ahead` = 0)** → `✅ <mode> + persisted to origin/develop (<short>). 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/<name>..chore/<name> 2>/dev/null || echo unknown` (`branch_ahead`). `<push mode>` below is the verb's word.
|
|
||||||
|
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/<name>..chore/<name> 2>/dev/null || echo unknown` (`branch_ahead`). `<push mode>` below is the verb's word. The WORKING-branch and rc 3 paths have no `chore/<name>` and never use the mode.
|
||||||
|
|
||||||
- **--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** → `✅ <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`.
|
- **--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`
|
- **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).`
|
- **not on origin (merged, `ahead` unknown)** → `✅ <mode> + 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)** → `⚠️ <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.`
|
- **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.`
|
- **finish failed** → `⚠️ <mode> + finish rc <n>: <stderr> — chore/<name> 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 <n>)` prefix, so the push state is still reported.)
|
||||||
- **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.`
|
||||||
|
|||||||
Reference in New Issue
Block a user