Skip to content

feat(p9): auto-merge actuator — close MERGE_READY → merge gap#2

Merged
broomva merged 1 commit into
mainfrom
feat/auto-merge
May 5, 2026
Merged

feat(p9): auto-merge actuator — close MERGE_READY → merge gap#2
broomva merged 1 commit into
mainfrom
feat/auto-merge

Conversation

@broomva
Copy link
Copy Markdown
Owner

@broomva broomva commented May 5, 2026

Summary

Closes the largest gap in the original P9 vision: "merges are pushed or automerged". Until now P9 only emitted `MERGE_READY`; merge had to be performed manually by the agent or human. This PR ships the actuator.

New subcommand: `p9 auto-merge `

```
p9 auto-merge [--repo OWNER/REPO] [--dry-run]
```

Flow:

  1. Load policy → bail if `auto_merge.enabled` is false (default false, opt-in per workspace).
  2. Verify PR is in `MERGE_READY` state.
  3. Fetch branch + touched paths via `gh pr view --json headRefName,files`.
  4. Match against `auto_merge.rules` — first-match-wins, with a pre-pass that always blocks `path_touched: require_human` rules (the governance-paths-always-block invariant from the original brainstorming).
  5. `action=auto` → `gh pr merge -- [--delete-branch]` + emit `MERGE_READY → MERGED` state event.
    `action=require_human`/`notify` → idempotent self-transition with `extra` payload indicating block reason; exit 7 (`EXIT_AUTO_MERGE_BLOCKED`).

Policy schema

New top-level `auto_merge:` block in `.control/policy.yaml`:

```yaml
auto_merge:
enabled: true
require_no_requested_changes: true
require_branch_up_to_date: true
merge_method: squash # squash | merge | rebase
delete_branch: true
rules:
- path_touched: CLAUDE.md # governance always blocks
action: require_human
- branch_pattern: "docs/*"
action: auto
# ...
default_action: notify # fail-safe default
```

Fail-safe defaults

  • `enabled: false` is the default → opt-in only.
  • `default_action: notify` ensures unknown branch classes never auto-merge.
  • Pre-pass on `require_human` paths means `CLAUDE.md`/`AGENTS.md`/`.control/policy.yaml` always block, regardless of rule order.

Implementation notes

  • Extended the minimal stdlib YAML loader to handle block-style list-of-dicts (`- key: val\n other: val2`) — required for the `rules:` list. Stdlib-only runtime guarantee preserved (no new deps).
  • 16 new tests across policy-parser (4), matcher (6), and command (6 — subprocess-mocked dry-run, auto, blocked, disabled-policy, external-merge-failure paths).

Test totals

Battery Count
unit 46
integration 15
chaos 6
auto-merge (new) 16
total 83 passing in ~4.0s

Follow-up

Workspace PR (next): adds the matching `auto_merge:` block to `.control/policy.yaml` and a 4th trigger to the P9 reflexive rule in AGENTS.md ("on `MERGE_READY` → invoke `p9 auto-merge` rather than manual `gh pr merge`").

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added auto-merge actuator with policy-gated control for automatic pull request merging.
    • New CLI subcommand to execute auto-merge with branch pattern and file path-based eligibility rules.
    • Configurable merge actions: auto-merge, require human review, or notify.
    • Dry-run mode to preview merge operations before execution.
  • Tests

    • Added comprehensive test suite for auto-merge policy parsing and command integration.

… merge

Closes the largest gap in the original P9 vision: "merges are pushed or
automerged". Until now P9 only emitted MERGE_READY; merge had to be
performed manually. This PR adds the actuator.

New subcommand: `p9 auto-merge <pr>`.

Flow:
  1. Load policy → bail if `auto_merge.enabled` is false (default false,
     so this is opt-in per workspace).
  2. Verify PR is in MERGE_READY state.
  3. Fetch branch + touched paths via `gh pr view --json headRefName,files`.
  4. Match against `auto_merge.rules` (first-match-wins, with a pre-pass
     that always blocks `path_touched: require_human` rules — the
     governance-paths-always-block invariant from the original
     brainstorming).
  5. action=auto → `gh pr merge --<method> [--delete-branch]` and emit
     MERGE_READY → MERGED state event.
     action=require_human/notify → idempotent self-transition with
     extra payload indicating the block reason; exit 7
     (EXIT_AUTO_MERGE_BLOCKED).

Policy schema (new top-level `auto_merge:` block in `.control/policy.yaml`):

  auto_merge:
    enabled: true
    require_no_requested_changes: true     # gh enforces these directly
    require_branch_up_to_date: true
    merge_method: squash                    # squash | merge | rebase
    delete_branch: true
    rules:
      - path_touched: CLAUDE.md             # governance always blocks
        action: require_human
      - branch_pattern: "docs/*"
        action: auto
      ...
    default_action: notify                  # fail-safe default

Implementation notes:

- Default `enabled: false` and default `default_action: notify` make the
  whole subsystem fail-safe — adding the block to policy.yaml is
  required to opt in. The matching policy block lands in the workspace
  repo as a follow-up PR.
- The minimal stdlib YAML loader was extended to handle block-style
  list-of-dicts (`- key: val\n  other: val2`), required for the rules
  list. Stdlib-only runtime guarantee preserved.
- 16 new tests across policy-parser, matcher (governance-blocks-first
  invariant), and command (subprocess-mocked dry-run + auto + blocked
  + disabled-policy + external-merge-failure paths).

Test totals: 46 unit + 15 integration + 6 chaos + 16 auto-merge = 83
passing in 4.0s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds an opt-in, policy-gated auto-merge feature to p9.py that automatically merges pull requests based on configurable rules. Introduces policy structures, rule matching logic, parsing with YAML sentinel resolution, and a new CLI subcommand that fetches PR metadata, evaluates rules, and executes merges via gh.

Changes

Auto-Merge Feature Implementation

Layer / File(s) Summary
Data Structures
scripts/p9.py (lines 52–54, 221–263, 251–255)
Adds EXIT_AUTO_MERGE_BLOCKED constant; introduces AutoMergeRule (branch/path criteria and action) and AutoMergePolicy (enabled flag, merge method, rules, defaults); extends PolicyConfig with auto_merge field.
YAML Parsing Infrastructure
scripts/p9.py (lines 394–507)
Extends _minimal_yaml_load fallback with _resolve_pending_lists, a second-pass sentinel resolver that converts pending mapping↔list markers into actual lists for correct block-style list parsing.
Policy Parsing & Validation
scripts/p9.py (lines 582–643)
Implements _parse_auto_merge with strict validation of rule shape (branch_pattern xor path_touched), allowed actions (auto, require_human, notify), merge method, and boolean defaults; integrates into _parse_policy.
Rule Matching Logic
scripts/p9.py (lines 652–686)
Adds match_auto_merge_action function implementing two-pass precedence: any require_human path rule acts as hard block; otherwise first matching rule wins; returns (action, reason) tuple.
CLI Handler & Helpers
scripts/p9.py (lines 1064–1168, 1427–1434)
Implements cmd_auto_merge that loads policy, checks PR MERGE_READY state, fetches branch and touched paths via gh pr view, matches rules, emits idempotent blocking event or executes merge via gh pr merge, and transitions state; adds _gh_pr_branch_and_paths and _gh_pr_merge helpers; wires auto-merge subcommand in build_parser().
Policy Fixture
tests/fixtures/policy-with-auto-merge.yaml
Defines test policy with auto_merge enabled, merge strategy (squash, delete branch), path-based human-approval rules (governance files), and auto-eligible branch patterns (docs/*, research/*, feat/p9-*) with notify default.
Tests
tests/test_p9_auto_merge.py
Validates policy parsing (enabled detection, invalid rules), rule matching (precedence, path/branch evaluation, governance blocking), and command integration (state transitions, dry-run, merge execution, failure handling, policy absence fallback).

Sequence Diagram

sequenceDiagram
    participant User as User / CLI
    participant P9 as cmd_auto_merge()
    participant Policy as Policy Loader
    participant GH as gh CLI
    participant PR as PR State

    User->>P9: p9 auto-merge --pr 42
    P9->>Policy: Load & parse auto_merge config
    Policy-->>P9: AutoMergePolicy
    P9->>PR: Check PR state (expect MERGE_READY)
    PR-->>P9: State confirmed
    P9->>GH: gh pr view 42 (fetch branch & files)
    GH-->>P9: branch=feat/p9-*, paths=[src/...]
    P9->>P9: match_auto_merge_action(policy, branch, paths)
    P9-->>P9: action=auto (matches feat/p9-* rule)
    P9->>GH: gh pr merge 42 --squash --delete-branch
    GH-->>P9: Merge successful
    P9->>PR: Append MERGE_READY → MERGED event
    PR-->>P9: State transitioned
    P9-->>User: Success (exit 0)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Hops with glee through auto-merge dreams,
Where rules guide PRs down swift streams,
Governance guards the sacred files,
While docs and features merge with smiles—
No human hands required to thrive,
Just blessed automation alive!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding an auto-merge actuator to close the gap between MERGE_READY state and actual merging.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/auto-merge

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.

@broomva broomva merged commit a7bc1da into main May 5, 2026
3 of 4 checks passed
@broomva broomva deleted the feat/auto-merge branch May 5, 2026 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant