Files
claude_mac/.claude/tasks/plans/2026-10-07-manual-push-prose-d3-1530.md
T

25 lines
7.2 KiB
Markdown

# PLAN — manual-push-prose-d3 — REVISED r2 (3 lenses + correctness confirmation)
Contract: .claude/tasks/contracts/2026-10-07-manual-push-prose-d3-1530.md
## Context
After D1 and D2 every reader of `gitflow.autopush` in THIS checkout (lib, emitted hooks, unpushed-guard, push-guard, banner) treats an unparseable value as manual: nothing pushes, and the stop is named. Two caveats remain: (a) onboarded projects keep their committed `.githooks/` until a session-start reconcile refreshes them, so a stale per-repo hook can still push on an invalid value — the ahead count tells; (b) the soft_deny backstop names only `gitflow.autopush false` (settings.json is outside D3; TODO). The skill prose written during run C still says "the lib and hooks still push on an invalid value until run D". Two labels say "COMMIT + PUSH" although the step reads the push state. The release executor trusts the dispatcher's version regex alone.
## Checklist
- [ ] PRECONDITION (executor's first step): `grep -l -- '--default true gitflow.autopush' lib/gitflow.sh hooks/unpushed-guard.sh hooks/session-start.sh githooks/post-commit githooks/post-merge .githooks/post-commit .githooks/post-merge` must print nothing (D1 + D2 landed); any hit → STATUS: BLOCKED, nothing edited.
- [ ] skills/capitalize/SKILL.md — line ~346 (5C invalid outcome) becomes TWO lines, both single-line bullets, placed where the one line is (still evaluated first):
`- **push mode \`invalid\`, \`ahead\` > 0 or unknown** → \`merged to develop — <verb stderr line verbatim>: treated as manual push mode by every reader, nothing pushed (origin/develop is <ahead> commit(s) behind, or unknown). Fix the value by hand, then: ! git push origin develop\` (append \` once a remote exists\` when \`ahead\` is unknown).`
`- **push mode \`invalid\`, \`ahead\` = 0** → \`merged to develop — <verb stderr line verbatim>: pushed anyway, likely a stale fail-open hook (a session-start reconcile refreshes a stale .githooks/; commit the refresh) or a manual push. Fix the value by hand.\``
Line ~379 (STEP 6 invalid closing line) becomes two matching lines: `- **invalid (merged, ahead > 0 or unknown)** → \`⚠️ <mode> + merged to develop — <verb stderr line verbatim>: treated as manual push mode by every reader, nothing pushed (origin/develop <ahead> behind). Fix the value by hand, then: ! git push origin develop\` (+ \` once a remote exists\` when unknown)` and `- **invalid (merged, ahead = 0)** → \`⚠️ <mode> + merged to develop — <verb stderr line verbatim>: pushed anyway, likely a stale fail-open hook (refreshed by the next session-start reconcile; commit the refresh) or a manual push. Fix the value by hand.\``. STEP 6 is picked by the 5C result, not evaluated in order, so disambiguate the neighbours: line ~371 `**auto-persisted (5C: finish rc 0 AND \`ahead\` = 0)**` → `**auto-persisted (push mode \`auto\`, finish rc 0 AND \`ahead\` = 0)**`; line ~378 `**not on origin (merged, \`ahead\` unknown)**` → `**not on origin (push mode not \`invalid\`, merged, \`ahead\` unknown)**`. Recap line ~363: `merged, gitflow.autopush invalid (<ahead> behind)` → `merged, autopush invalid, nothing pushed (<ahead> behind) | merged, autopush invalid, pushed anyway (stale hook or manual push)`. Line ~375 (`--no-push`, `branch_ahead` = 0): add after the template, as an instruction like the :376 precedent: `With push mode \`invalid\`, replace \`(auto-push mode)\` with \`(<verb stderr line verbatim>: pushed anyway, likely a stale fail-open hook or a manual push; fix the value by hand, commit the .githooks refresh)\`.` Line ~376 (`--no-push`, `branch_ahead` > 0 or unknown): its existing `With push mode \`invalid\`, append \` gitflow.autopush=<value> is not a boolean: fix it by hand\`` → `With push mode \`invalid\`, append \` <verb stderr line verbatim>: treated as manual push mode, nothing pushed; fix the value by hand\``. No "until run D" text remains; no templated `<value>`: the verb's stderr line is quoted verbatim (it may say "could not read"). No period right after a `! git …` command.
- [ ] agents/client-handover-writer.md — heading `## STEP 5 — COMMIT + PUSH (only if files changed)` → `## STEP 5 — COMMIT + PUSH STATE READ (only if files changed)`. Residual push claims in the same step — the current text WRAPS across lines and carries bold markers; use the Read tool and multi-line old_strings: ~545-546 `Before any commit or push,⏎confirm this is a gitflow repo:` → `Before any commit, confirm this is a gitflow repo (the pipeline never pushes):`; ~553-554 `**do NOT commit,⏎do NOT push.**` → `**do NOT commit (and never push).**`; ~555-556 `Commit/push skipped — no gitflow model in this repo; publish the⏎listed changes manually before deploy.` → `Commit skipped — no gitflow model in this repo; publish the listed changes by hand before deploy.` (re-wrap as the file does). Line ~700 (C2-qualified "mini-commit; push state read, never assumed") stays. Verify with the Grep tool (pattern `push`), never a Bash grep carrying the push word.
- [ ] skills/client-handover/SKILL.md — step 4 label `**COMMIT + PUSH**` → `**COMMIT + PUSH STATE READ**`; in the same sentence `otherwise (manual push mode, or a hook push that failed)` → `otherwise (manual push mode, an invalid gitflow.autopush, or a hook push that failed)`.
- [ ] agents/release-executor.md — prep span, `### Input` paragraph: after "never bump it." add on its own line: `Format check only, by reading the string (never inside a Bash command): <X.Y.Z> must match ^[0-9]+\.[0-9]+\.[0-9]+$ (literal regex text, single backslashes); anything else → STATUS: BLOCKED, nothing created.` (prep only: finish's existing `### Preconditions` already requires the `release/<X.Y.Z>` branch that only prep creates). Lines ~80-83: `in manual push mode they stay local` → `in manual push mode, or with an invalid gitflow.autopush, they stay local` — keep `invalid gitflow.autopush` and `by reading the string` unbroken on one physical line each (line-based greps).
## Edge cases
- Label rename: repo-wide grep for "COMMIT + PUSH" (skills, agents, lib, hooks, rules, README, USAGE, templates, CLAUDE.global.md, CHANGELOG, docs) → only the two renamed lines; in-file references are by step number.
- "Pushed anyway" diagnosis: after D1, an invalid value with `ahead` = 0 can only come from a stale per-repo hook (or a human push); the line says so instead of asserting "nothing pushes".
- Doc-sync afterwards owns: CHANGELOG `[Unreleased]` three sites ("treated as auto", "for this hook a non-boolean value reads as manual", "still push on it as auto"), SETTINGS Push discipline "still push on it as auto; only push-guard fails closed", with the stale-`.githooks/` scope note.
- TODO (outside D3): settings.json soft_deny names only `gitflow.autopush false` — add "or an unparseable value" (restriction only) in a later settings run.
## Disposition
- honors BDR-114 (fail-closed everywhere → prose must stop saying otherwise, but never claims more than the facts: the ahead count decides), BDR-100/LRN-113 (label rename with citer grep; precondition grep for D1/D2 before any prose change), LRN-104, LRN-198 (version string checked by reading, never interpolated into a check command), BDR-042 (dispatcher decides the number; the executor only formats-checks).