fix(hook-base-ref): I-phase — pre-push base-ref via git stdin (F1-F4, ADAPT pre-commit precedent) - #288
Merged
Merged
Conversation
… ADAPT pre-commit precedent) The pre-push hook guessed a static `origin/staging` base, which silently no-op'd on any consumer repo without a `staging` branch (install.sh ships the bash fallback, not pre-push.ts) and mis-scoped prod-promotion pushes to `main`. Read the base git already hands the hook on stdin instead. - git.ts: parsePushRefs() + Z40 + commitsNotOnRemotes() (pure, unit-tested). - pre-push.ts: resolveBase() — precedence env > stdin remote_sha (Z40 → commits-not-on-remotes) > origin/staging default; visible warning, never a silent skip (F2). F1: route the stranded `origin/main...HEAD` §6 guard through the resolver, reconcile 3-dot → 2-dot. - pre-push.fallback.sh: same resolver (consumer-facing channel; dual-impl §5). - 19 paired-negative tests across git.test.ts (+6 commitsNotOnRemotes, +9 parsePushRefs/Z40), prepush-upstream-ref.test.sh (+5), and the new prepush-fallback-base-ref.test.sh (5); CI registers the fallback test. Implements the R-phase (#287) recommended design — Option A (read stdin, not a trunk-detection guess). Maintainer's reserved fork (incremental vs full-PR dev-side scope) resolved to A: stdin is a strict improvement given the per-commit trailer invariant + CI full-PR backstop; Option B documented as the falsifier in SSOT #85. F3 verified: dispatcher uses `exec`, git forwards pre-push stdin natively (no husky npm dep). Prior-art: prior-art-evaluations.md#85 (pre-push base-ref via git stdin; pre-commit precedent, verdict ADAPT — reused the stdin-parsing primitive, scope semantics differ per the row's T16 analysis). Prior-art: pre-commit/pre-commit (DeepWiki 2026-05-29) — stdin remote_sha as diff base; Z40 new-branch via rev-list --remotes fallback. The mature reference this design ADAPTs. Prior-art: research-patches/2026-05-29-hook-base-ref-detection.md (R-phase patch, PR #287) — the prior-art survey + recommended design this commit implements.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
I-phase implementing the hook-base-ref-detection R-phase (PR #287 research patch). The pre-push hook guessed a static
origin/stagingbase, which silently no-op'd on any consumer repo without astagingbranch (install.shships the bash fallback, which had the same default) and mis-scoped prod-promotion pushes tomain. The fix reads the base git already hands the hook on stdin (<local_ref> <local_sha> <remote_ref> <remote_sha>) — eliminating the guess entirely.Verdict: ADAPT pre-commit's stdin-parsing primitive (SSOT #85). Read base for dev; env-override for CI.
What changed (F1–F4)
parsePushRefs()+Z40+commitsNotOnRemotes()(pure, unit-tested)packages/core/hooks/utils/git.ts:44,64,89resolveBase()— precedence env > stdinremote_sha(Z40 → commits-not-on-remotes) >origin/stagingdefaultpackages/core/hooks/pre-push.ts:95origin/main...HEAD(3-dot) §6 guard → routed through resolver, reconciled to 2-dotpackages/core/hooks/pre-push.ts:386-405packages/core/hooks/pre-push.ts:123(warnSkip).husky/pre-pushusesexec, git forwards pre-push stdin natively; no husky npm depdocs/meta-factory/prior-art-evaluations.md:153pre-push.fallback.sh(the channelinstall.shships) gets the same resolverpackages/core/hooks/pre-push.fallback.sh:28Reserved fork resolved to Option A (stdin). The R-phase left "incremental vs full-PR dev-side scope" to the maintainer. Resolved to A: Option B reintroduces the trunk-guess the finding's root cause (
#own-stack-blind-spot) condemns; stdin is a strict improvement given the per-commit trailer invariant + CI's full-PR backstop. B is documented as the SSOT #85 falsifier — a reversal callable later, not a live toss-up.Verification
commitsNotOnRemotes, 9 newparsePushRefs/Z40)prepush-upstream-ref.test.sh8/8 (5 new stdin scenarios),prepush-fallback-base-ref.test.sh5/5 (new) — RED-first paired-negatives (4/5/7/8 + FB1/FB2 confirmed failing pre-fix)§1.7 Forward-check applied
The change complies with every active discipline layer:
packages/core/hooks/pre-push.ts:95(resolveBaseuses onlyupstreamExists/commitsNotOnRemotes) andpackages/core/hooks/pre-push.fallback.sh:28(resolve_commits, puregit rev-parse/rev-list).docs/meta-factory/prior-art-evaluations.md:153(docs(audit): refresh AUDIT-CHECKLIST/AUDIT-PROMPT post-C-1 + packages/ layout #85, verdict ADAPT, pre-commit), with explicit T16 problem-class match (~60%) + falsifier.packages/core/hooks/pre-push.fallback.sh:24mirrorspackages/core/hooks/pre-push.ts:95precedence (env > stdin > default), proven by parallel paired-negative suites.docs/meta-factory/prior-art-evaluations.md:153(docs(audit): refresh AUDIT-CHECKLIST/AUDIT-PROMPT post-C-1 + packages/ layout #85 appended after docs(skills): sync shipped prose to C-1 resolution (close DN-1) #84, ID-ordered).§1.7 Backward-check applied
Complete sweep of every base-resolving surface the R-phase enumerated — all four reconciled, none silently superseded:
packages/core/hooks/pre-push.ts:95—upstreamRef()(old static default) →resolveBase().packages/core/hooks/pre-push.fallback.sh:28— oldUPSTREAM_REF=…:-origin/staging→resolve_commits().packages/core/hooks/pre-push.ts:386-405— F1 strandedorigin/main...HEAD3-dot guard → resolver-routed 2-dot..github/workflows/audit-self.yml:312,316— CIPREPUSH_UPSTREAM_REF: origin/${{ github.base_ref }}preserved unchanged.Explicit non-supersession / out-of-scope notes:
pre-push.test.ts > invokes the remaining audit-self self-tests by literal path— NOT touched. It asserts stale.test.shliterals left by the Wave 10.4.tsmigration; fails identically on pristineorigin/staging; not in the CI gate (test:principles=principles/only). Separate stale-assertion fix per CLAUDE.md PR strategy → Observation 1.install.shship-list NOT touched — the fix lands in bothpre-push.fallback.sh(shipped) andpre-push.ts, so the consumer fallback now resolves the base from stdin and no longer silently no-ops. Shippingpre-push.tsto consumers for the substance arms is a separate enhancement → Observation 2.audit-self.yml:307still fails loudly when the PR base is unresolvable; the new dev-sidewarnSkipfires only when neither stdin nor env resolves (CI always supplies env, so it never reaches that path).Observations (surfaced, not acted on — PR-scope discipline)
pre-push.test.tsstale.test.shassertion (above) — separate fix.install.shships only the fallback, notpre-push.ts— bug is closed (fallback now works), but full TS substance arms for consumers = separate enhancement umbrella.🟢 Простыми словами
Что починили: pre-push-хук раньше угадывал, с какой веткой сравнивать пуш — жёстко
origin/staging. На любом чужом репозитории без веткиstagingпроверка молча ничего не делала (а консьюмерам устанавливается именно bash-fallback с тем же дефолтом). Теперь хук читает базу, которую git сам передаёт ему на stdin при каждом пуше — работает на любом репозитории с любым именем транка, гадать больше не нужно. Новая ветка (Z40) → проверяются «коммиты, которых ещё нет ни на одном remote».Чем CI остался прежним: CI как и раньше передаёт
PREPUSH_UPSTREAM_REF=origin/<base_ref>и громко падает, если база не резолвится (audit-self.yml:307) — полный PR-scope. Изменился только dev-side: вместо тихого пропуска теперь либо stdin-база, либо видимое предупреждение. Приоритет: env (CI) > stdin (dev) > старый дефолт. Backstop в CI не тронут.Base:
staging. Closes the hook-base-ref-detection I-phase.