Skip to content

feat(#48): pre-push drift hook + branch-scan CLI subcommand#117

Merged
Knapp-Kevin merged 4 commits into
BicameralAI:devfrom
Knapp-Kevin:feat/48-pre-push-drift-hook
Apr 29, 2026
Merged

feat(#48): pre-push drift hook + branch-scan CLI subcommand#117
Knapp-Kevin merged 4 commits into
BicameralAI:devfrom
Knapp-Kevin:feat/48-pre-push-drift-hook

Conversation

@Knapp-Kevin

Copy link
Copy Markdown
Collaborator

Summary

  • New bicameral-mcp branch-scan console subcommand prints a terminal-friendly summary of drifted decisions for HEAD. Calls link_commit under the hood; exit codes (0/1/2) drive the pre-push hook's prompt-or-block logic.
  • New opt-in git pre-push hook installed via bicameral-mcp setup --with-push-hook. Surfaces drift warnings before git push completes — when the developer can still amend or annotate. Non-blocking by default (TTY prompt or non-TTY warn-only); BICAMERAL_PUSH_HOOK_BLOCK=1 forces hard-block.
  • Path C maintainer call: graceful skip when no ~/.bicameral/ledger.db exists. Hook short-circuits on [ -d .bicameral ] || exit 0 first line, so repos without bicameral configured see zero impact.
  • Stdlib-only — no new runtime dependencies. Bash hook is ~17 lines; cli/branch_scan.py is 177 LOC pure functions.

Linked issues

Closes #48

Refs:

Plan / Audit / Seal

Test plan

  • pytest tests/test_branch_scan_cli.py -q (7/7 — Phase 0 renderer + exit-code matrix)
  • pytest tests/test_setup_pre_push_hook.py -q (5/5 — Phase 1 install/idempotent/permissions; 1 chmod test skipped on Windows non-POSIX, runs on Linux CI)
  • pytest tests/test_drift_report_*.py -q (16/16 — regression on PR feat(#49): sticky PR-comment drift report — GitHub Action + renderer + poster #113's drift-report renderer/poster/integration; both modules sit in cli/ and need to coexist cleanly)
  • ruff check cli/branch_scan.py setup_wizard.py server.py tests/test_branch_scan_cli.py tests/test_setup_pre_push_hook.py — clean
  • ruff format --check — clean
  • mypy cli/branch_scan.py — no issues
  • End-to-end smoke: python -m server branch-scan dispatches via cli_maincli.branch_scan.main → graceful skip with exit 0 (no ledger configured locally). Confirmed the dispatch chain works.
  • Operator QC pass (post-merge): in a real repo with bicameral configured + a known drifted decision, run bicameral-mcp setup --with-push-hook, git push, observe warning + prompt, decline → push aborts; accept → push proceeds. Non-TTY (git push 2>&1 | cat) does not block. Qualitative gate, not CI-blocking.

What this PR is NOT

Soft dependencies

Workflow security

  • Hook reads /dev/tty for the Push anyway? [y/N] prompt. Input matched against fixed regex [yY]|[yY][eE][sS]; no shell expansion; no command construction from user input.
  • Hook calls bicameral-mcp branch-scan from PATH — same trust model as the existing post-commit hook (and any other system tool).
  • File mode 0o755 (executable, world-readable). No secrets in hook content.
  • Hook short-circuits on first line ([ -d .bicameral ] || exit 0) when no ledger configured — zero impact on repos without bicameral.
  • No pull_request_target triggers introduced.

Files changed (8)

File Change LOC
cli/branch_scan.py NEW (renderer + CLI entry) 177
tests/test_branch_scan_cli.py NEW (7 tests) 144
tests/test_setup_pre_push_hook.py NEW (5 tests; 1 Windows-skipped) 92
docs/guides/pre-push-drift-hook.md NEW (user guide) 129
setup_wizard.py MODIFIED (+_GIT_PRE_PUSH_HOOK + _install_git_pre_push_hook + step 7b) +50
server.py MODIFIED (+branch-scan subparser, +--with-push-hook flag) +14
CHANGELOG.md MODIFIED ([Unreleased] entry added)
plan-48-pre-push-drift-hook.md NEW (the plan, audit-PASSED) 366

Plus governance (META_LEDGER #17 + #18 on this branch's chain, SYSTEM_STATE.md #48 snapshot).

Reviewer ask

@jinhongkuan — risk grade L2 (new CLI subcommand surface; modifies setup_wizard + server.py; consumes existing handle_link_commit unchanged). Single-reviewer gate per DEV_CYCLE.md §4.4.

Three specific decisions worth a sanity check:

  1. Q1 placement — renderer lives in cli/branch_scan.py (sibling of cli/drift_report.py and cli/classify.py). Audit accepted as the natural home for CLI-invoked tools. If you'd prefer a tools/ or root-level location, this is the place to say so.

  2. Q2 latent bug flagged — the existing post-commit hook command bicameral-mcp link_commit HEAD doesn't appear to be a registered cli_main subcommand. The hook's || true swallows the argparse error, which means the post-commit hook may be silently no-op'ing today. This PR deliberately doesn't model on that pattern; branch-scan is registered correctly. If you want me to file a separate issue for the post-commit hook bug, say so and I'll do it post-merge.

  3. Q3 scope — v1 is HEAD-only. Multi-commit-range push walk is a documented v2 enhancement. If you'd rather have multi-commit from day one, this PR can be expanded; otherwise the v2 issue files itself naturally when someone hits the limitation.

…plan

Three phases: Phase 0 — branch-scan CLI subcommand (cli/branch_scan.py
+ server.py wiring); Phase 1 — setup_wizard --with-push-hook flag +
.git/hooks/pre-push template; Phase 2 — CHANGELOG + user guide.

Five open questions surfaced for audit:

- Q1: branch-scan CLI placement — cli/branch_scan.py mirroring the
  cli/classify.py + cli/drift_report.py pattern from PRs BicameralAI#107 and BicameralAI#113.
  Wired via server.py:cli_main subparser.
- Q2: existing post-commit hook may be silently no-op'ing — its
  `bicameral-mcp link_commit HEAD` command isn't a registered subcommand.
  Flagged for separate follow-up issue; NOT in scope for BicameralAI#48.
- Q3: v1 = HEAD-only drift scan; multi-commit-range walk is v2.
- Q4: TTY/no-TTY/no-ledger graceful behaviors specified.
- Q5: setup_wizard pattern modeled exactly on existing
  _install_git_post_commit_hook idempotent install.

Risk grade L2 (new CLI subcommand surface; modifies setup_wizard +
server.py; no MCP tool changes, no schema, no contracts).

Branched off BicameralAI/dev tip 77b9ee3 post-BicameralAI#113. SG-PLAN-GROUNDING-DRIFT
mitigated: ran `ls -d */` and confirmed cli/ exists, setup_wizard.py
exists, no fictional package/file claims in plan.
…attempt

GATE TRIBUNAL entry for plan-48-pre-push-drift-hook.md. Verdict:
PASS first-attempt — no remediation cycle needed. Chain hash
bf890347 extends from BicameralAI#16 (BicameralAI#44 SEAL, 567170e0) on dev.

Three non-blocking observations recorded:
- O1: run_setup parameter-name cosmetic nit (functionally fine).
- O2: latent post-commit-hook bug — bicameral-mcp link_commit HEAD
  is not a registered subcommand. Recommend separate issue. Out
  of scope for BicameralAI#48.
- O3: two-renderer modules (cli/drift_report.py for PR comments
  vs cli/branch_scan.py for terminal hooks) accepted as parallel
  non-duplication; different output formats and exit semantics.

SG-PLAN-GROUNDING-DRIFT instance BicameralAI#4 prevented — first plan this
session where author-side grounding mitigation worked rather than
audit-side catching. Issue BicameralAI#114 (CI lint enforcement) remains the
durable countermeasure.

Plan PASS at 79abcc2; chain to /qor-implement.
Phase 0 — branch-scan CLI subcommand:
- cli/branch_scan.py (177 LOC): pure-function render_terminal_summary
  + main() CLI entry. Lazy-imports handle_link_commit; graceful skip
  when no ~/.bicameral/ledger.db. Exit codes 0/1/2 documented in
  module header.
- server.py: add `branch-scan` subparser to cli_main, dispatch to
  cli.branch_scan:main.
- tests/test_branch_scan_cli.py (144 LOC, 7 tests): renderer shape +
  exit-code matrix (no drift, drift+block-env, drift+non-TTY, etc.)

Phase 1 — setup_wizard pre-push hook:
- setup_wizard.py: new _GIT_PRE_PUSH_HOOK constant + _install_git_pre_push_hook
  function modeled on _install_git_post_commit_hook. Idempotent install
  with append-on-existing-hook-without-bicameral semantics.
- run_setup() gains keyword-only `with_push_hook: bool = False`. Setup
  wizard step 7b conditionally installs the hook.
- server.py setup_parser gains `--with-push-hook` flag.
- tests/test_setup_pre_push_hook.py (92 LOC, 5 tests): fresh-install,
  idempotent re-install, append-when-non-bicameral, no-git-root path,
  executable-bit (POSIX-only).

Phase 2 — docs:
- CHANGELOG.md [Unreleased] entry
- docs/guides/pre-push-drift-hook.md (129 LOC): What/When/Quickstart/
  Reference/Pitfalls/See-also user guide

Validation:
- 11 new tests + 8 BicameralAI#49 regression = 19/20 green (1 Windows-only chmod
  test skipped on this platform)
- ruff check + format: clean on all 5 changed/new source files
- mypy on cli/branch_scan.py: no issues
- End-to-end smoke: `bicameral-mcp branch-scan` dispatches via cli_main
  → cli.branch_scan.main → graceful skip with exit 0 (no ledger)

Razor: cli/branch_scan.py 177 LOC (≤250); all entry funcs ≤25 LOC;
helpers ≤20 LOC; nesting ≤2; zero nested ternaries.

Maintainer-locked design (audit-PASS at META_LEDGER BicameralAI#17, chain
bf890347): Q1 cli/ placement, Q2 deliberate non-modeling on possibly-
broken post-commit-hook predecessor, Q3 HEAD-only v1, Q4-Q6 TTY/skip
behaviors all per plan.

Audit's separate-issue recommendation (post-commit hook command not
registered as CLI subcommand) NOT addressed in this PR — out of scope.

Closes BicameralAI#48

Plan: plan-48-pre-push-drift-hook.md (audit PASS, chain hash bf890347).
Implementation chains to seal in /qor-substantiate.
Substantiation seal for plan-48-pre-push-drift-hook.md (Issue BicameralAI#48,
audit PASS at META_LEDGER BicameralAI#17, chain hash bf890347).

Verification gates (10 of 12 passed; 2 advisory skipped per
capability shortfalls):
- Reality vs Promise: ✓ all 4 new + 3 modified files exist; zero
  plan deviations
- Test audit: 27/28 (11 new + 16 regression on PR BicameralAI#113 drift_report
  tests; 1 chmod test skipped on Windows non-POSIX)
- Razor final: cli/branch_scan.py 177 LOC (≤250); entry funcs ≤25;
  helpers ≤20; nesting ≤2; zero nested ternaries
- Skill file integrity: N/A (no MCP tool changes)
- SYSTEM_STATE.md synced
- Merkle seal computed: eacc6f89f707ce958fa2485177c9706808fdfeb32
- Step 4.6 reliability sweep: skipped (qor/reliability/ absent)
- Step 7.5 version bump: skipped (per maintainer direction)

This is the first implementation in the session with ZERO plan
deviations — plan was thorough enough that implementation was
direct. Pairs with the prior turn's first-attempt audit PASS
(Entry BicameralAI#17). Both ends of the QOR cycle clean.

Audit's separate-issue recommendation (post-commit hook command
not a registered subcommand) tracked but NOT addressed in this PR
— separate workstream.

Chain: 18 entries on this branch; integrity VALID. Next:
/qor-document.
@Knapp-Kevin Knapp-Kevin added enhancement New feature or request flow:feature Standard feature/fix PR targeting BicameralAI/dev (the default flow) labels Apr 29, 2026
@Knapp-Kevin Knapp-Kevin requested a review from jinhongkuan April 29, 2026 21:13
@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14891180-6bcc-4445-9717-b31a11fa0e33

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Knapp-Kevin Knapp-Kevin merged commit 2e9a842 into BicameralAI:dev Apr 29, 2026
5 of 6 checks passed
Knapp-Kevin added a commit to Knapp-Kevin/bicameral-mcp that referenced this pull request May 21, 2026
Phase 0 — plan-grounding lint (Check A, blocking):
- scripts/lint_plan_grounding.py (212 LOC, 8 tests): walks plan-*.md
  for backtick-wrapped path tokens, verifies each resolves on the
  working tree. Markers (**new**, (planned)/(future)/(v2)/
  (nonexistent)/(example)) exempt the line. Skips HTML comments,
  blockquotes, multi-word tokens, glob patterns.
- .github/workflows/lint-and-typecheck.yml: new step lints only
  plan-*.md files modified in the current PR (git diff against
  base ref). Blocks merge if any modified plan has unresolved paths.

Phase 1 — PR-body refs lint (Check B, advisory):
- .github/scripts/lint_pr_body_refs.py (162 LOC, 6 tests): warns
  on bare #NUMBER mentions outside Closes/Refs/etc keywords or
  Linked-issues section. SECURITY: --from-env PR_BODY reads via
  os.environ directly (no shell interpolation; OWASP A03 mitigation
  per BicameralAI#114 audit v1).
- .github/workflows/pr-body-refs-lint.yml: advisory workflow on
  pull_request: [opened, edited, reopened]. continue-on-error: true.

Phase 2 — CI integration (above).

Phase 3 — docs:
- docs/DEV_CYCLE.md §2.1: plan-grounding lint callout
- docs/DEV_CYCLE.md §4.3: PR-body keyword discipline (Closes/Refs/
  Linked-issues section)
- CHANGELOG.md [Unreleased] entry

Validation:
- 29/29 tests green (8 + 6 + 15 regression on BicameralAI#117/BicameralAI#113)
- Self-test: scripts/lint_plan_grounding.py against
  plan-114-grounding-lint.md exits 0 (clean) — the plan that
  builds the lint passes the lint
- ruff check + format: clean on all 4 new files
- mypy: clean on both new modules
- Razor: scripts/lint_plan_grounding.py 212 LOC; .github/scripts/
  lint_pr_body_refs.py 162 LOC; entry funcs ≤30 LOC; helpers ≤25.

Plan: plan-114-grounding-lint.md (audit v1 VETO at a5e6a05 on
OWASP A03; v2 PASS at 4ea06be after --from-env remediation;
chain bf890347 → 850ec57f). Implementation chains to seal in
/qor-substantiate.

Closes BicameralAI#114
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request flow:feature Standard feature/fix PR targeting BicameralAI/dev (the default flow)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant