Skip to content

feat(cli): add adjudicate --apply-same guarded batch merge (#137)#175

Merged
jasonssdev merged 1 commit into
mainfrom
feat/adjudicate-apply-same
Jul 25, 2026
Merged

feat(cli): add adjudicate --apply-same guarded batch merge (#137)#175
jasonssdev merged 1 commit into
mainfrom
feat/adjudicate-apply-same

Conversation

@jasonssdev

Copy link
Copy Markdown
Owner

Summary

Final slice of #137 — the guarded batch. adjudicate --apply-same fuses every eligible SAME 2-member duplicate group in one pass, closing the advisory→action loop that previously required one manual openkos merge per pair. Closes #137 (parts --json #161 and interactive --apply #165 already shipped).

The deferred guardrail (issue #138) was resolved by #147 — but that fix was prompt-only, so the only machine-checkable gate remains verdict == SAME + 2-member size. The batch's safety net is therefore an explicit aggregate preview + typed-count confirmation, not a confidence threshold.

Behavior

  • Eligibility: exactly verdict == SAME and a 2-member group; N>2 groups and DIFFERENT/UNCERTAIN are skipped (mirrors --apply).
  • Preview + typed-count gate: prints every merge it will perform, then requires --confirm-count <N> to equal the previewed count exactly (scriptable, mirrors the shipped purge --confirm-phrase); a TTY prompts for the count; a non-TTY run without the flag refuses with zero writes. Empty/wrong/non-numeric aborts byte-identically.
  • Empty report short-circuits to "nothing to apply" (exit 0) — a scripted run never fails spuriously.
  • Sequential + reversible: each merge lands in the merged_from ledger (unmerge round-trips). A mid-batch failure stops, keeps prior commits, and reports how many were applied. Apply-time re-resolution skips a member already absorbed earlier in the batch.
  • Refactor: per-pair prepare/preview/commit extracted into _prepare_one_merge/_format_merge_preview_line/_commit_one_merge, shared by both --apply and the batch so the destructive write ordering lives in one place.
  • Mutually exclusive with --apply and --json.

Verification

  • uv run pytest: 2130 passed, exit 0 · ruff clean · mypy clean
  • SDD verify: PASS — 7 requirements / 15 scenarios; the Phase 1 refactor kept the existing --apply suite green.
  • Full 4R review (this is a >400-line destructive path):
    • Risk: CLEAN — no data-loss or unauthorized-merge path (write ordering, count gate, ledger integrity, sensitivity high-water-mark all verified).
    • Resilience / Reliability: 3 real findings, all fixed in this PR with regression tests (zero-eligible spurious exit-1; mid-batch failure hid the applied count; preview/Total consistency + a misleading skip message).
    • Readability: minor SUGGESTIONs (helper dedup / placement) deferred.

Strict TDD throughout (RED confirmed before each GREEN, including the three review fixes).

Review budget

Exceeds the 800-line budget → size:exception. ~922 changed lines of code, 62% of it the 20-scenario test matrix for this destructive path; production surface is ~400 lines. Splitting off the Phase 1 refactor would not bring the feature PR under budget (the test matrix is inherent).

Closes #137.

Gives `adjudicate` a batch path to action: `--apply-same` fuses every
eligible SAME 2-member duplicate group in one pass, closing the advisory
-> action loop that previously required one manual `merge` per pair.

- Eligibility is exactly `verdict == SAME` and a 2-member group; N>2 groups
  and DIFFERENT/UNCERTAIN verdicts are skipped (mirroring `--apply`).
- The batch prints a full aggregate preview of every merge it will perform,
  then requires a typed-count confirmation: `--confirm-count <N>` must equal
  the previewed count exactly (scriptable, mirrors `purge --confirm-phrase`);
  on a TTY the count is prompted; a non-TTY run without the flag refuses with
  zero writes. Any empty/wrong/non-numeric value aborts byte-identically.
- An empty report (no eligible groups) short-circuits to "nothing to apply"
  (exit 0), so a scripted run never fails spuriously.
- Merges execute sequentially, each landing in the `merged_from` ledger so
  `unmerge` round-trips. A mid-batch failure stops but keeps prior commits
  and reports how many were applied before the failure. Apply-time
  re-resolution skips a member already absorbed earlier in the batch.
- Refactor: the per-pair prepare/preview/commit body is extracted into
  `_prepare_one_merge`/`_format_merge_preview_line`/`_commit_one_merge`,
  shared by both the interactive `--apply` walk and the batch, so the
  destructive write ordering is defined once.

`--apply-same` is mutually exclusive with `--apply` and `--json`.

Reviewed via the full 4R set (risk/resilience/reliability/readability); risk
review found no data-loss or unauthorized-merge path. Exceeds the 800-line
review budget (size:exception) — 62% of the diff is the 20-scenario test
matrix for this destructive path.

Closes #137.
@jasonssdev
jasonssdev merged commit a4f0427 into main Jul 25, 2026
6 checks passed
@jasonssdev
jasonssdev deleted the feat/adjudicate-apply-same branch July 25, 2026 06:15
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.

feat: give adjudicate a path to merge (--json / interactive apply / guarded batch)

1 participant