Files
claude/.claude/tasks/contracts/2026-10-06-manual-push-mode-1632.md
T

7.9 KiB
Raw Blame History

CONTRACT — manual-push-mode

  • date: 2026-10-06 | flow: feat | branch: feature/manual-push-mode (run A of 2; run B = push-guard hook + banner)
  • status: active

REQUEST (verbatim — IMMUTABLE)

User (fr): "est-ce qu'on a un moyen de regler le flow automatique de git. Activer / desactiver le fait que ca pousse tout seul, que ca ne merge pas tout seul etc. Q'il y ai forcement la demande ou l'authorisation humaine pour cela ? Il faut pouvoir le toggle on ou toggle off" User (fr): "ok donc ou sera la cle gitflow.mode ? Pour expliaquer, c'est pour pouvoir utiliser la config au taff. Il faut tout faire pareil, juste rien push seul. Mais faire les branches locale,ment, faire les commits localements etc. Juste il faut pas push. seulement manuel" /feat args: Manual-push mode via the existing gitflow.autopushgit-config key (no new key). Scope: (1) lib/gitflow.sh_gitflow_push_branchmust honourgitflow.autopush=falselike the hooks and_gitflow_delete_remotedo (todaystart/finishpush regardless, bug); (2) guard-bash: whengit config --bool --default true gitflow.autopushis false in the cwd repo, deny anygit pushfrom Claude with a message pointing to! git push` (human runs it); (3) hooks/unpushed-guard.sh: in manual mode, SessionStart emits "push manuel : N commit(s) à pousser" info only, Stop emits nothing; (4) hooks/session-start.sh banner shows push mode (auto/manual); (5) CLAUDE.global.md: one line in the gitflow section, autopush=false → unpushed work is expected, never push unless the user asks; (6) tests updated (guard-bash.test.sh, unpushed-guard.test.sh, gitflow-test.sh). User decisions already taken: mechanical block of git push (chosen), guard info at SessionStart only (chosen).

CLARIFICATIONS

Q: Mechanical block of git push when autopush=false? / A: yes, block (user, pre-flow) [gated 2026-10-06] Q: unpushed-guard behaviour in manual mode? / A: info at SessionStart only, silent at Stop (user, pre-flow) [gated 2026-10-06] Q: scope split — request spans ~10 files (> /feat max 5) / A: run A (this contract) = items 1, 3, 5 + their tests; run B = items 2, 4 as hooks/push-guard.sh + test + settings.json wiring + banner. hooks/guard-bash.sh does not exist (BLK-022), so item 2 lands in a new dedicated hook, and guard-bash.test.sh (spec of an absent hook) is left untouched. [gated 2026-10-06, orchestrator — scope class, surfaced to user in pass B] Q: manual-mode SessionStart message language / A: English, consistent with the hook family. Exact line: ℹ manual push mode: <N> commit(s) on '<branch>' to push by hand (git push); no-upstream variant: ℹ manual push mode: '<branch>' has no upstream (<N> commit(s) on this disk only), push by hand: git push -u origin <branch>; the existing ; <d> uncommitted change(s) in <cwd> clause follows when the tree is dirty. [gated 2026-10-06] Q: run B hook name / A: hooks/push-guard.sh + lib/tests/push-guard.test.sh [gated 2026-10-06] Q: challenge r1 — skills push on their own (skills/capitalize/SKILL.md:338 git push origin develop after the BDR-068 auto-finish; skills/client-handover/SKILL.md:48 + agents/client-handover-writer.md:586 git push; skills/release-candidate/SKILL.md:96 and skills/tour/SKILL.md:273 claim the branch is already on origin) and settings.json environment prose (lines ~480, ~499) says unpushed = defect / A: out of run A's 5-file scope. Run B (settings.json: hook wiring + widen the gitflow.* deny to git config * gitflow.*, git -c gitflow.*, GIT_CONFIG_COUNT=* + prose) and run C (the 5 skill/agent files: gate each push on git config --bool --default true gitflow.autopush, report manual push mode: <ref> not pushed). DEPLOYMENT ORDER: gitflow.autopush false is not to be set on the work machine before B and C are merged. [gated 2026-10-06, orchestrator — scope class, surfaced to the user] Q: challenge r1 — manual-mode count scope / A: all local branches (--branches --not --remotes), listing the ahead branches; the gated sentence shape stays (ℹ manual push mode: <n> commit(s) not on origin (<b1>, <b2>), push by hand: git push -u origin <branch>). Auto mode unchanged. [gated 2026-10-06, orchestrator — refinement of the chosen wording, surfaced to the user]

ACCEPTANCE CRITERIA

  1. _gitflow_push_branch returns without pushing when gitflow.autopush is false: gitflow start under autopush=false creates the branch locally and origin has no copy; gitflow finish under autopush=false merges locally and origin's develop tip is unchanged. A branch whose upstream lags (pushed once by hand, then committed to) is still deleted by finish (rc 0): --unset-upstream before -d (LRN-161). Skipped remote delete says left in place. A base that cannot fast-forward from origin warns behind origin/<base>; offline stays silent. Locked by the new isolated gitflow-test block T18i–T18n. CHECK: out=$(make test suite=lib/gitflow-test.sh 2>&1); printf '%s' "$out" | grep -q ' FAIL ' && exit 1; for t in T18m0 T18i T18j T18k T18o T18n T18l; do printf '%s' "$out" | grep -q "ok $t" || exit 1; done; echo GITFLOW-MANUAL-OK EXPECT: GITFLOW-MANUAL-OK EVIDENCE: MET exit=0 marker-found :: GITFLOW-MANUAL-OK
  2. Auto mode unchanged: existing T18a–T18h, T24a–T24f and the T19 installed==emitted drift gate stay green (no hook emitter touched). CHECK: out=$(make test suite=lib/gitflow-test.sh 2>&1); printf '%s' "$out" | grep -q ' FAIL ' && exit 1; for t in T18a T18b T18c T18h T18f T19a T19b T19c T22i T22j T24b T24f; do printf '%s' "$out" | grep -q "ok $t" || exit 1; done; echo GITFLOW-AUTO-OK EXPECT: GITFLOW-AUTO-OK EVIDENCE: MET exit=0 marker-found :: GITFLOW-AUTO-OK
  3. hooks/unpushed-guard.sh in manual mode (gitflow.autopush=false in the cwd repo): Stop emits nothing even with unpushed commits; SessionStart emits the manual-mode info line (ℹ manual push mode: <n> commit(s) not on origin (<branches>), push by hand: …) counting every local branch, silent at n=0 with a clean tree, plus the existing uncommitted-changes clause; an invalid gitflow.autopush value is named at SessionStart and treated as auto; no "⚠ unpushed work" wording in manual mode. Auto mode output unchanged (T1–T9). Locked by new test cases T10–T16. CHECK: out=$(make test suite=lib/tests/unpushed-guard.test.sh 2>&1); printf '%s' "$out" | grep -q '^FAIL' && exit 1; printf '%s' "$out" | grep -qE 'PASS=(1[6-9]|[2-9][0-9]) FAIL=0' && echo GUARD-OK EXPECT: GUARD-OK EVIDENCE: MET exit=0 marker-found :: GUARD-OK
  4. CLAUDE.global.md gitflow section gains one statement: gitflow.autopush false = manual-push mode, unpushed work is expected there, Claude never pushes unless the user asks; the "ahead of its upstream is a defect" sentence is scoped to auto mode. File stays within the 320-line density budget. CHECK: grep -q 'autopush false' CLAUDE.global.md && grep -qi 'manual' CLAUDE.global.md && [ "$(wc -l < CLAUDE.global.md)" -le 320 ] && echo DOCTRINE-OK EXPECT: DOCTRINE-OK EVIDENCE: MET exit=0 marker-found :: DOCTRINE-OK
  5. shellcheck clean on the two touched scripts. CHECK: shellcheck lib/gitflow.sh hooks/unpushed-guard.sh && echo SHELLCHECK-OK EXPECT: SHELLCHECK-OK EVIDENCE: MET exit=0 marker-found :: SHELLCHECK-OK
  6. Full hermetic suite green. CHECK: make test >/dev/null 2>&1 && echo SUITE-GREEN EXPECT: SUITE-GREEN EVIDENCE: NOT-MET exit=2 (nonzero) ::
  7. No new git-config key, no new env var, no change to GITFLOW_NO_PUSH semantics, no edit to hook emitters (_gitflow_emit_*) or to githooks//.githooks/.
  8. shellcheck stays clean on lib/gitflow-test.sh and lib/tests/unpushed-guard.test.sh too (Health Stack shellcheck lib/*.sh). CHECK: shellcheck lib/gitflow-test.sh lib/tests/unpushed-guard.test.sh && echo SHELLCHECK-TESTS-OK EXPECT: SHELLCHECK-TESTS-OK EVIDENCE: MET exit=0 marker-found :: SHELLCHECK-TESTS-OK

FILE SCOPE

lib/gitflow.sh · hooks/unpushed-guard.sh · CLAUDE.global.md · lib/gitflow-test.sh · lib/tests/unpushed-guard.test.sh