forked from bchanot/claude
Merge bugfix/gitflow-finish-args into develop
This commit is contained in:
@@ -34,6 +34,7 @@ rules:
|
||||
| BLK-012 | 2026-06-29 | gitflow_init half-applied: socle-commit failure swallowed → hook activated on partial run → re-run self-blocks | resolved |
|
||||
| BLK-013 | 2026-06-30 | `make plugin` Error 127 — npm absent on apt-`nodejs` host (Step 4 gsd-pi aborts, Steps 5-10 + residual cleanup never run) | resolved (env) |
|
||||
| BLK-014 | 2026-07-01 | `make install` aborts npm EEXIST on `~/.local/bin/claude` when claude already installed via native installer — no presence guard | resolved |
|
||||
| BLK-015 | 2026-07-03 | `gitflow_finish` ignored its `<type> <name>` args → merged the CHECKED-OUT branch not the one named → wrong-branch merge (audit LOT3) | resolved |
|
||||
|
||||
---
|
||||
|
||||
@@ -179,3 +180,12 @@ rules:
|
||||
- **Status**: resolved. Fix `8dc4027`, branch `bugfix/install-claude-idempotent`, pending merge validation.
|
||||
- **Reference**: [[BLK-013]] npm prefix `~/.local` = contributing factor (npm bin over native bin). install-plugins.sh already pointed to code.claude.com (native) — install.sh was the npm outlier. Fresh-machine `elif npm` branch channel-consistency = open design question (potential BDR). Pattern → [[LRN-085]].
|
||||
- **Update 2026-07-01**: MERGED `2393ca5` (bugfix/install-claude-idempotent → develop), pushed — supersedes "pending merge validation". The open channel-consistency question is RESOLVED by [[BDR-046]] (fresh install → native installer, npm dropped for claude); install.sh has no `elif npm` branch → nothing left to trancher.
|
||||
|
||||
## BLK-015 — `gitflow_finish` ignored its args, merged the CURRENT branch not the one asked
|
||||
|
||||
- **Date**: 2026-07-03
|
||||
- **Friction**: audit 2026-07-02 — `gitflow.sh finish bugfix audit-bugs` run while checked out on `feature/audit-tokens` merged audit-tokens (LOT3), NOT audit-bugs. Final develop state identical (disjoint hunks) so no data damage, but the merge order was silently wrong. UX trap: the command LOOKS like it targets `bugfix/audit-bugs`.
|
||||
- **Real cause**: CLI dispatch (`lib/gitflow.sh:257` `finish) gitflow_finish "$@"`) forwards args, but the function derived its source from `HEAD` (`git symbolic-ref`) and NEVER read `$1/$2` → the `<type> <name>` were silently dropped. Merge source = ambient state (checked-out branch), not the named target. Design intended finish to always operate on HEAD (human gate = "be on the branch"), but nothing enforced that passed args, if any, MATCH the branch you're on.
|
||||
- **Solution**: `gitflow_finish [<type> <name>]` — args now an optional safety ASSERTION: present AND `"$req_type/$req_name" != "$br"` → error `operates on the current branch 'X', but you asked 'Y' — checkout 'Y' first`, rc 2. No args = behavior unchanged (only real caller `skills/gitflow/SKILL.md:36` + every test pass none → zero regression). +7 regression assertions (`gitflow-test.sh` T12, numbered to dodge collision with reconcile's own T6c).
|
||||
- **Status**: resolved. Commit `d9fdd4c`, branch `bugfix/gitflow-finish-args`.
|
||||
- **Reference**: journal 2026-07-02 (trap noted, not fixed) → fixed 2026-07-03. Pattern → [[LRN-089]] (pass-through wrapper deriving target from ambient state = silent contract violation).
|
||||
|
||||
@@ -302,3 +302,9 @@ rules:
|
||||
- #11 rtk auto-allow DROPPED — permission control back in settings.json (rtk registry was a parallel authority bypassing deny/ask). #10 rules/context7.md deleted (−493 tok; find-docs survives, stable — regen keyed on its absence); faulty examples → upstream issue draft (upstash/context7, gh unauthenticated). plugin-dev uninstalled + dropped from installer.
|
||||
- Incidents: magic API key printed into transcript from ~/.claude.json → rotated, [[BDR-026]] update (copies of secrets); gitflow_finish ignores its args (operates on CURRENT branch, lib/gitflow.sh:104) → LOT 3 merged first by mistake, final develop state identical (disjoint hunks) — UX trap noted, not fixed.
|
||||
- Residuals (flagged, not built): doctor "Cargo not found (RTK unavailable)" parenthesis now misleading; doctor symlink-check false-warns on dir-level symlinks; doctor token constants stale; find-docs faulty examples ctx7-owned.
|
||||
|
||||
## 2026-07-03
|
||||
- bugfix/gitflow-finish-args: `gitflow_finish` contract fix — args now optional safety ASSERTION (present + ≠ current branch → refuse rc2 "operates on current branch X, you asked Y — checkout Y first"); no-args unchanged (only real caller SKILL.md:36 + all tests pass none → zero regression). +7 T12 assertions. [[BLK-015]], [[LRN-089]]. Off-by-one caught at capitalize: next free BLK = 015 not 016 (gate proposal said 016) → gitflow.sh comment corrected pre-finish via soft-reset+redo of the 3 commits.
|
||||
- Same branch, 3 doctor false-warns fixed ([[LRN-047]] corrob — a doctor that cries false is ignored): cargo "(RTK unavailable)" → optional info (RTK prebuilt, detect_rtk); check_symlink passes children of dir-level symlinks (hooks/session-start.sh); gstack counts 34 per-skill symlinks not a mythical skills/gstack link (link.sh removes it); token budget vs 200k context window not bogus 11k "session budget" → killed false "92% CRITICAL" (measured ~11.4k [[LRN-088]]; 200k confirmed by user — 1M pin revoked at audit #7, calibrate on default not the exceptional session).
|
||||
- Suites green: gitflow 71/71 (+7), deterministic 13, doc-commit 32, doc-shape 19, reconcile 20, deploy-commit 13, release-candidate 5/5 tag-mode. doctor: 0 false-warn (1 legit survivor = gstack tracks branch=main advisory). shellcheck clean. T12 named to dodge collision with reconcile's own T6c (darwin path, audit #3).
|
||||
- 3 atomic commits (fix gitflow / fix doctor / docs changelog Unreleased) + memory. finish bugfix→develop on GO; user pushes develop.
|
||||
|
||||
@@ -108,6 +108,7 @@ rules:
|
||||
| LRN-086 | 2026-07-02 | External-tool-generated skill: prove provenance by mtime (not repo grep), gitignore + regen via install-step; guard regen on ABSENCE when the tool co-writes a user-editable config | any untracked skill/dir a tool (ctx7, etc.) drops into the repo |
|
||||
| LRN-087 | 2026-07-02 | presence-flag ≠ capability — rtk silently dead after .bashrc wipe; emitted commands need ABSOLUTE bin paths (they run in another shell); integrity pin = live machinery, re-pin on hook edit | any PATH-dependent capability + hand-managed shell profile; hooks emitting commands for another shell |
|
||||
| LRN-088 | 2026-07-02 | token-cutting intuition inverts under measurement — verbosity beats cardinality (gstack 34 skills ≈ 592 tok vs pr-review 6 agents ≈ 2,183) | any "disable X to save tokens" — measure per-item bytes first; profiles toggle skills, not plugin payloads |
|
||||
| LRN-089 | 2026-07-03 | pass-through wrapper (CLI `"$@"` → fn deriving target from ambient state: HEAD/cwd/env) silently ignores its args = silent contract violation; guard = args are an ASSERTION, refuse when they disagree with state | any dispatcher forwarding args to a callee that reads ambient state instead of the args |
|
||||
|
||||
---
|
||||
|
||||
@@ -956,3 +957,11 @@ rules:
|
||||
- **actions taken**: pr-review-toolkit OFF by default (−2,183; audit.profile keeps it = reactivation channel), 10 fattest personal descriptions compressed 6,416→4,243 chars (−~540), context7 rule dropped for the find-docs skill (−493; skill body loads on-demand, stable — regen keyed on find-docs absence). Total ≈ −3.2k/session ≈ −22%.
|
||||
- **future application**: before any "disable X to save tokens" → measure per-item bytes FIRST (frontmatter extraction, plugin cache); expect the fat where descriptions are hand-written rich, not where items are many. Profiles toggle SKILLS only — plugin payloads (agents/skills in cache) need `enabledPlugins`. [[LRN-080]] measure-first corroborated on a new axis (cost, not behavior).
|
||||
- **Reference**: audit 2026-07-02 measurement + branch feature/audit-tokens. See [[BDR-014]], [[LRN-043]].
|
||||
|
||||
## LRN-089 — a pass-through wrapper whose callee reads ambient state silently ignores its args
|
||||
|
||||
- **Date**: 2026-07-03
|
||||
- **pattern**: a CLI/dispatcher that forwards `"$@"` to a function which derives its TARGET from ambient state (HEAD, cwd, env, "current X") rather than from those args → the args are silently dropped. The call SITE looks parameterized (`finish bugfix audit-bugs`) but the callee acts on whatever state it's standing in → wrong-target action, NO error. `gitflow_finish` read `HEAD`, never `$1/$2`; `finish bugfix X` from another branch merged that other branch.
|
||||
- **context**: audit 2026-07-02, `lib/gitflow.sh:257` `finish) gitflow_finish "$@"` passed args the function never consulted. Surfaced when a finish "for" one branch merged another (LOT3). [[BLK-015]].
|
||||
- **future application**: any wrapper/dispatcher forwarding args to a callee that resolves its target from ambient state — either (a) make the callee USE the args as the target, or (b) if the ambient-state contract is deliberate, treat passed args as an ASSERTION and refuse loudly when they disagree with the state. Never let forwarded args be silently dropped: silent-drop = the caller believes they steered, the callee ignored them. Sibling of "presence-flag ≠ capability" [[LRN-087]] — both = a visible signal lying about the real behavior.
|
||||
- **Reference**: `lib/gitflow.sh` gitflow_finish arg-guard, `lib/gitflow-test.sh` T12. [[BLK-015]].
|
||||
|
||||
@@ -393,3 +393,20 @@ Tension réelle proactif vs intrusif. Auto-déclencher feat/bugfix sur intention
|
||||
[done 2026-07-01 : unconditional npm guard after Node block (corepack enable npm → distro `install npm` fallback → fatal exit 1 w/ clear msg). Catches node>=22-present-but-npm-absent (NODE_OK short-circuit). shellcheck clean, bash -n OK. Fresh-apt live validation pending (no npm-less host to hand). branch bugfix/install-plugins-npm-guard.]
|
||||
- [x] (b) Re-baseline darwin on the 5 ex-broken gstack skills (`benchmark-models`, `context-restore`, `context-save`, `make-pdf`, `plan-tune`) — now repaired and back in scope ([[BDR-043]], trigger cleared). Verify `results.tsv` still marks them `status=error` first. (Promoted from BDR-043's action-field — not an item the user authored.)
|
||||
[resolved-MOOT 2026-06-30 : won't-run. BDR-043 cleared only motif (a) of BDR-015's TWO exclusion grounds (symlinks repaired ✅); motif (b) external-ownership INTACT — the 5 resolve to skills-external/gstack/ (submodule), darwin optimizes by EDITING SKILL.md → would dirty the submodule (forbidden [[LRN-070]]). Re-baseline = unactionable score. + results.tsv gone (wiped by 23/06 make-plugin reinstall) → not even a re-baseline, a fresh-from-zero one. Geometric trigger lifted, value trigger intact — twin of --help [[LRN-080]]. See [[LRN-082]]. Not "done", not "open": MOOT.]
|
||||
|
||||
## 2026-07-03 — bugfix/gitflow-finish-args (contract fix + doctor false-warns)
|
||||
Root: audit 2026-07-02 residuals. `gitflow_finish` ignores its args (merges CHECKED-OUT
|
||||
branch) → LOT3 mis-merge trap; + 3 doctor false-warns (LRN-047 class).
|
||||
- [x] (1) lib/gitflow.sh gitflow_finish — optional <type> <name>; error rc2 if != current
|
||||
branch ("operates on current branch X, you asked Y — checkout Y first"). No-args unchanged.
|
||||
Commit d9fdd4c. [[BLK-015]] [[LRN-089]].
|
||||
- [x] (2) lib/gitflow-test.sh — T12 arg-guard: arg-mismatch → nonzero + message names both;
|
||||
arg-match → merges as before. +7 assertions (71/71). T12 (not T6c — reconcile collision).
|
||||
- [x] (3) doctor.sh cargo line — false "(RTK unavailable)" → optional info (RTK prebuilt).
|
||||
- [x] (4) doctor.sh check_symlink — PASS iff canonical path under $REPO (direct OR via
|
||||
symlinked ancestor dir); hooks/session-start.sh false-warn gone. Commit 6778b9f.
|
||||
- [x] (5) doctor.sh §2 gstack — counts 34 per-skill symlinks; mythical [ -L skills/gstack ] dropped.
|
||||
- [x] (6) doctor.sh token § — denominator 11000→CONTEXT_WINDOW=200000, thresholds 15/25,
|
||||
comment anchored to measured ~11.4k (LRN-088). False "92% CRITICAL" → ~5% comfortable.
|
||||
- [x] Verify — suites green (71/13/32/19/20/13 + RC 5/5); doctor 0 false-warn; shellcheck clean.
|
||||
+docs(changelog) Unreleased entry (706abff). Gate passed on GO 2026-07-03. Finish pending.
|
||||
|
||||
@@ -6,6 +6,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- `gitflow_finish` ignored its `<type> <name>` arguments and always merged the checked-out branch — naming a different branch silently merged the wrong one. The arguments are now an optional safety assertion: if given and not equal to the current branch, `finish` refuses with a clear error instead of merging. No-argument calls (the only real caller) are unchanged.
|
||||
- `doctor.sh` false-warnings removed (a check that cries wolf is one you learn to ignore): `cargo` absence no longer claims "RTK unavailable" (RTK ships as a prebuilt binary); `check_symlink` no longer flags files reached through directory-level symlinks (e.g. `hooks/session-start.sh`); the GStack check counts the per-skill symlinks instead of a `skills/gstack` link that `link.sh` deliberately removes; the token-budget estimate is measured against the ~200k context window instead of a mis-framed "~11k session budget" that produced a false "92% CRITICAL".
|
||||
|
||||
## [4.0.0] — 2026-06-30
|
||||
|
||||
### Added
|
||||
|
||||
@@ -42,18 +42,24 @@ check_symlink() {
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -L "$target" ]; then
|
||||
# readlink -f is not available on macOS BSD — use -f with fallback
|
||||
local real
|
||||
real=$(readlink -f "$target" 2>/dev/null) || real=$(readlink "$target")
|
||||
if [ ! -e "$real" ]; then
|
||||
fail "$HOME/.claude/$name → $real — BROKEN SYMLINK"
|
||||
else
|
||||
pass "$HOME/.claude/$name"; _LINK_PASS=$((_LINK_PASS + 1))
|
||||
fi
|
||||
else
|
||||
warn "$HOME/.claude/$name exists but is NOT a symlink (expected symlink to repo)"
|
||||
# Broken symlink: points at a target that no longer exists.
|
||||
if [ -L "$target" ] && [ ! -e "$target" ]; then
|
||||
fail "$HOME/.claude/$name → $(readlink "$target") — BROKEN SYMLINK"
|
||||
return
|
||||
fi
|
||||
|
||||
# Correctly wired iff the canonical path lands inside the repo. This is true
|
||||
# for a direct symlink (CLAUDE.md, settings.json) AND for a real file reached
|
||||
# through a symlinked ANCESTOR dir (hooks/, skills/, agents/, lib/, templates/
|
||||
# are dir-level symlinks — their children are real files under $REPO). A stray
|
||||
# real copy in ~/.claude resolves to itself (outside $REPO) → still flagged as
|
||||
# drift. (LRN-047: the dir-symlink layout is legitimate, must not false-warn.)
|
||||
local real
|
||||
real=$(readlink -f "$target" 2>/dev/null) || real="$target"
|
||||
case "$real" in
|
||||
"$REPO"/*) pass "$HOME/.claude/$name"; _LINK_PASS=$((_LINK_PASS + 1)) ;;
|
||||
*) warn "$HOME/.claude/$name resolves to $real (outside repo — expected a link into $REPO)" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_symlink "CLAUDE.md"
|
||||
@@ -83,24 +89,17 @@ else
|
||||
warn "GStack submodule missing — run: git submodule update --init"
|
||||
fi
|
||||
|
||||
if [ -L "$HOME/.claude/skills/gstack" ]; then
|
||||
real=$(readlink -f "$HOME/.claude/skills/gstack" 2>/dev/null || readlink "$HOME/.claude/skills/gstack")
|
||||
if [ -d "$real" ]; then
|
||||
pass "Symlink OK → $real"
|
||||
# Check for skills/ subdirectory (referenced by plugin-advisor PHASE 1).
|
||||
# `|| echo 0` is required because under `set -o pipefail`, a missing
|
||||
# gstack/skills/ dir makes find exit non-zero, killing the script.
|
||||
gstack_skills_count=$( { find "$HOME/.claude/skills/gstack/skills/" -maxdepth 1 -mindepth 1 2>/dev/null || true; } | wc -l | tr -d ' ')
|
||||
if [ "${gstack_skills_count:-0}" -gt 0 ]; then
|
||||
pass "GStack: ${gstack_skills_count} skills available"
|
||||
else
|
||||
warn "GStack symlink OK but no skills/ subdirectory found — may need: cd skills-external/gstack && ./setup"
|
||||
fi
|
||||
else
|
||||
fail "Symlink broken → $real"
|
||||
fi
|
||||
# GStack skills are exposed as PER-SKILL symlinks directly under skills/ (browse,
|
||||
# cso, review, …) pointing into skills-external/gstack/ — there is NO single
|
||||
# skills/gstack symlink (link.sh deliberately removes it: it duplicated the
|
||||
# top-level gstack SKILL.md alongside the per-skill entries). The bin/ +
|
||||
# browse/dist/ helper links under skills/gstack/ are checked in §7 Consistency.
|
||||
# `|| true` guards pipefail if skills/ is unexpectedly absent (checked above).
|
||||
gstack_skill_links=$( { find "$HOME/.claude/skills/" -maxdepth 1 -type l -lname '*skills-external/gstack/*' 2>/dev/null || true; } | wc -l | tr -d ' ')
|
||||
if [ "${gstack_skill_links:-0}" -gt 0 ]; then
|
||||
pass "GStack: ${gstack_skill_links} skills linked (per-skill symlinks)"
|
||||
else
|
||||
warn "GStack not symlinked — run: bash link.sh"
|
||||
warn "GStack skills not linked — run: cd skills-external/gstack && ./setup"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
@@ -136,7 +135,10 @@ fi
|
||||
if command -v cargo &>/dev/null; then
|
||||
pass "Cargo $(cargo --version | awk '{print $2}')"
|
||||
else
|
||||
warn "Cargo not found (RTK unavailable)"
|
||||
# Cargo does NOT gate RTK: RTK ships as a prebuilt binary and detect_rtk finds
|
||||
# it via ~/.cargo/bin or ~/.local/bin (RTK status is shown under Plugins).
|
||||
# Cargo is only the Rust toolchain to BUILD RTK from source → optional, info.
|
||||
info "Cargo not found (optional — only needed to build RTK from source)"
|
||||
fi
|
||||
|
||||
if command -v python3 &>/dev/null; then
|
||||
@@ -239,8 +241,12 @@ echo ""
|
||||
# 6. Token budget estimate
|
||||
# ────────────────────────────────────────────────────────────
|
||||
echo "── Token budget estimate ──"
|
||||
# Reference: Claude Code Pro plan ~11k tokens/5h session (session budget, not context window).
|
||||
# Seuils: WARNING >15%, CRITICAL >30% of session budget.
|
||||
# The passive footprint (CLAUDE.md + skill descriptions + plugin session-injects)
|
||||
# loads into the CONTEXT WINDOW every session — it competes with the ~200k default
|
||||
# context, NOT a per-session token quota (the old "~11k/5h budget" denominator was
|
||||
# a category error → false "92% CRITICAL", LRN-047). Measured ~11.4k post-audit
|
||||
# 2026-07-02 (LRN-088); the chars/4 sum below is a coarse proxy of that footprint.
|
||||
# Thresholds: WARNING >15% of context (~30k), CRITICAL >25% (~50k).
|
||||
|
||||
CLAUDE_MD_CHARS=$(wc -c < "$REPO/CLAUDE.md" 2>/dev/null || echo 0)
|
||||
CLAUDE_MD_TOKENS=$((CLAUDE_MD_CHARS / 4))
|
||||
@@ -264,25 +270,25 @@ if detect_context7 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 200));
|
||||
if detect_graphifyy 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 300)); fi
|
||||
|
||||
TOTAL_TOKENS=$((CLAUDE_MD_TOKENS + SKILL_DESC_TOKENS + PLUGIN_TOKENS))
|
||||
SESSION_BUDGET=11000
|
||||
PCT=$((TOTAL_TOKENS * 100 / SESSION_BUDGET))
|
||||
CONTEXT_WINDOW=200000 # Claude Code default context window (conservative; 1M is opt-in)
|
||||
PCT=$((TOTAL_TOKENS * 100 / CONTEXT_WINDOW))
|
||||
|
||||
echo ""
|
||||
echo " CLAUDE.md: ~${CLAUDE_MD_TOKENS}t"
|
||||
echo " Skill descriptions: ~${SKILL_DESC_TOKENS}t (${SKILL_COUNT} skills)"
|
||||
echo " Plugin passive cost: ~${PLUGIN_TOKENS}t (active plugins)"
|
||||
echo " ─────────────────────────────────────────"
|
||||
info " Total: ~${TOTAL_TOKENS}t"
|
||||
info " Session budget (Pro): ${SESSION_BUDGET}t"
|
||||
info " Usage: ~${PCT}%"
|
||||
info " Total: ~${TOTAL_TOKENS}t (measured ~11.4k post-audit, LRN-088)"
|
||||
info " Context window: ${CONTEXT_WINDOW}t (default; 1M opt-in)"
|
||||
info " Usage: ~${PCT}% of context"
|
||||
echo ""
|
||||
|
||||
if [ "$PCT" -gt 30 ]; then
|
||||
warn "CRITICAL: ${PCT}% of session budget — /plugin-check to disable unused plugins"
|
||||
if [ "$PCT" -gt 25 ]; then
|
||||
warn "CRITICAL: ~${PCT}% of the ${CONTEXT_WINDOW}t context — /plugin-check to disable unused plugins"
|
||||
elif [ "$PCT" -gt 15 ]; then
|
||||
warn "WARNING: ${PCT}% of session budget — consider disabling unused toggle plugins"
|
||||
warn "WARNING: ~${PCT}% of the ${CONTEXT_WINDOW}t context — consider disabling unused toggle plugins"
|
||||
else
|
||||
pass "Budget: ${PCT}% (comfortable)"
|
||||
pass "Budget: ~${PCT}% of context (comfortable)"
|
||||
fi
|
||||
|
||||
# Per-file breakdown (skill bodies — loaded on demand, shown for awareness)
|
||||
|
||||
@@ -156,6 +156,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 "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
|
||||
# mismatch: standing on feature/standon but asking to finish bugfix/other → refuse
|
||||
# shellcheck disable=SC2034 # mism_out/mism_rc are used in the deferred chk eval strings
|
||||
mism_out="$(gitflow_finish bugfix other 2>&1)"; mism_rc=$?
|
||||
chk "arg-mismatch → nonzero rc" "[ $mism_rc -ne 0 ]"
|
||||
chk "arg-mismatch → HEAD untouched" '[ "$(git symbolic-ref --short HEAD)" = feature/standon ]'
|
||||
chk "arg-mismatch → branch kept" 'git rev-parse --verify -q refs/heads/feature/standon >/dev/null'
|
||||
chk "arg-mismatch → develop NOT merged" '! git log develop --oneline | grep -q "Merge feature/standon into develop"'
|
||||
chk "arg-mismatch → message names both" 'printf "%s" "$mism_out" | grep -q "current branch" && printf "%s" "$mism_out" | grep -q "bugfix/other"'
|
||||
# match: naming the current branch explicitly finishes exactly like the no-arg path
|
||||
gitflow_finish feature standon >/dev/null 2>&1
|
||||
chk "arg-match → merged into develop" 'git log develop --oneline | grep -q "Merge feature/standon into develop"'
|
||||
chk "arg-match → branch deleted" '! git rev-parse --verify -q refs/heads/feature/standon >/dev/null'
|
||||
|
||||
echo
|
||||
echo "==== RESULT: $PASS passed, $FAIL failed ===="
|
||||
[ "$FAIL" -eq 0 ]
|
||||
|
||||
+16
-3
@@ -97,11 +97,24 @@ _gitflow_delete() { # <branch>
|
||||
git branch -q -d "$br" || { echo "gitflow: '$br' not fully merged — branch kept" >&2; return 5; }
|
||||
}
|
||||
|
||||
# gitflow_finish → directed merge of the CURRENT branch per its type, then delete.
|
||||
# WHEN to call this is the human gate (SKILL.md). This only performs the merge.
|
||||
# gitflow_finish [<type> <name>] → directed merge of the CURRENT branch per its
|
||||
# type, then delete. WHEN to call this is the human gate (SKILL.md).
|
||||
#
|
||||
# The merge source is ALWAYS the checked-out branch (HEAD) — that is the contract.
|
||||
# The optional <type> <name> is a SAFETY ASSERTION, not a target selector: if you
|
||||
# name a branch it MUST equal the current one, else finish refuses loudly instead
|
||||
# of silently merging whatever you happen to be standing on. (Guards the audit UX
|
||||
# trap: `finish bugfix audit-bugs` run from feature/audit-tokens merged the wrong
|
||||
# branch — args were silently ignored. See BLK-015 / LRN-089.) No args = unchanged.
|
||||
gitflow_finish() {
|
||||
local br type
|
||||
local br type req_type="${1:-}" req_name="${2:-}"
|
||||
br="$(git symbolic-ref --short -q HEAD)" || { echo "gitflow_finish: detached HEAD" >&2; return 3; }
|
||||
if [ -n "$req_type" ] || [ -n "$req_name" ]; then
|
||||
[ "$req_type/$req_name" = "$br" ] || {
|
||||
echo "gitflow_finish: operates on the current branch '$br', but you asked '$req_type/$req_name' — checkout '$req_type/$req_name' first (or run finish with no args)." >&2
|
||||
return 2
|
||||
}
|
||||
fi
|
||||
type="$(gitflow_branch_type "$br")"
|
||||
case "$type" in
|
||||
feature|bugfix|chore)
|
||||
|
||||
Reference in New Issue
Block a user