# Merge Strategies *Four strategies, clear tradeoffs, and when each is the right tool.* ## Overview The merge strategy is the top-level knob that tells Aura how aggressive to be when combining two sides. Most teams run with the default — `semantic` — and only reach for another strategy in specific situations: release branches that must take one side wholesale, monorepo imports from a vendor branch, or multi-author rewrites where every non-conflicting statement should merge. Aura ships four strategies: `semantic` (default), `prefer-ours`, `prefer-theirs`, and `semantic-union`. Each is a deterministic function of `(ancestor, ours, theirs)` with clearly-specified conflict behavior. Each can be set globally, per-merge, per-file-glob, or per-conflict. And each honors the underlying file-type merger — strategies are not a replacement for [AST merge](/how-ast-merge-works) or [JSON deep merge](/json-deep-merge), they are a policy on top of them. > A strategy is a promise about how conflicts are *decided*, not a license to ignore them. Even `prefer-ours` preserves full audit trail and calls attention to what was dropped. ## How It Works ### The strategy controls three things 1. **Classification of ties.** When both sides modified the same node and the payloads are structurally similar but not identical, how strict is the equivalence test? 2. **Default resolution on conflict.** When Aura raises a conflict, does it stop for human input (the default), or does it auto-resolve using the strategy's policy? 3. **Statement-level recursion.** For `semantic-union`, Aura recurses one level deeper than usual to merge disjoint statements inside the same function. ### Strategy table | Strategy | On modify/modify ties | On true conflict | Recurses into bodies | Typical use | |-------------------|------------------------|----------------------|----------------------|-------------| | `semantic` | Raise if payloads differ | Stop, ask human | Yes (one level) | Default for every merge | | `prefer-ours` | Keep ours silently | Keep ours silently | Yes | Cherry-pick into a locked release branch | | `prefer-theirs` | Take theirs silently | Take theirs silently | Yes | Re-import from an authoritative upstream | | `semantic-union` | Attempt statement-level merge first | Raise only if sub-nodes truly conflict | Yes (deep) | Long-lived feature branches with many disjoint edits | ### Strategy is a stack Strategy selection walks a stack of sources, first-match wins: 1. `--strategy` flag on `aura merge` (highest precedence). 2. Per-file rule in `.aura/merge.json` (`{ "strategies": { "src/legacy/**": "prefer-ours" } }`). 3. Per-conflict override inside the [interactive picker](/interactive-conflict-picker). 4. Repo-wide default in `.aura/config.json`. 5. Built-in default: `semantic`. Strategy decisions are recorded in the merge audit log with the source that provided them, so it is always clear *why* a given conflict auto-resolved. ### Guardrails that apply regardless of strategy Even under `prefer-ours` or `prefer-theirs`, Aura refuses to: - Discard a function that `aura_prove` had connected to a protected goal. - Delete a file flagged as load-bearing by the session's impact analysis without an explicit `--force`. - Override an intent log that is pinned (`aura log-intent --pin`) by a teammate on the opposite side. These guardrails exist because silent-preference strategies are precisely where accidents are expensive. The strategy decides the normal cases; the guardrails catch the extraordinary ones. ## Examples ### A: `semantic` — the default Ancestor, ours, theirs as in [the AST merge walkthrough](/how-ast-merge-works). `semantic` merges anything disjoint, raises conflicts on real disagreements, and leaves the decision to a human. ```bash $ aura merge feature/payments 3 conflicts, 11 auto-merged ``` This is what you want 95% of the time. ### B: `prefer-ours` for a hotfix branch You are maintaining a release branch. A teammate's feature branch has drifted in directions you cannot accept for the release. You want to cherry-pick only the non-conflicting bits; every true conflict should keep the release branch's version. ```bash $ aura merge feature/payments --strategy prefer-ours 0 conflicts raised. auto-resolved: src/billing.ts :: calculateFee -> kept ours (theirs changed formula) src/billing.ts :: postInvoice -> kept ours (theirs added new arg) package.json :: /version -> kept ours 14 auto-merged. ``` The audit log records exactly what was dropped from theirs: ```bash $ aura merge audit session: merge/feature-payments -> release/1.4 (2m ago) strategy: prefer-ours 3 resolutions auto-applied: c_8f21 keep-ours "calculateFee body from theirs discarded" c_8f22 keep-ours "postInvoice signature change from theirs discarded" c_9a03 keep-ours "version bump from theirs discarded" ``` You can later review the dropped changes with `aura conflicts show --reveal-dropped` if you want to rescue any of them in a follow-up PR. ### C: `prefer-theirs` for a vendor import A monorepo imports a vendored library by merging its upstream branch. You never want local edits to override upstream; any conflict means the local change is obsolete. ```bash $ aura merge vendor/upstream --strategy prefer-theirs --scope "vendor/**" 8 auto-resolved (local edits dropped) 22 files auto-merged ``` The `--scope` flag confines the strategy to the vendored directory. Conflicts outside that path still fall under the repo's default `semantic` strategy. ### D: `semantic-union` for a long-lived feature branch Two developers have been working on the same authentication module for two weeks. Almost every function has edits on both sides, but the edits are mostly disjoint statements — one added logging, the other added validation. Default `semantic` would flag every function as `modify/modify` and require resolution. ```bash $ aura merge feature/auth-refactor --strategy semantic-union 2 conflicts raised, 47 auto-merged (of which 31 statement-level unions) ``` Aura recursed into each function, diffed statement-by-statement, and produced a union of both side's additions where they did not overlap. The two remaining conflicts are genuine: both sides rewrote the same if-branch. Statement union example: ```typescript // ancestor function charge(amount: number) { log("charging"); stripe.create({ amount }); } // ours function charge(amount: number) { if (amount <= 0) throw new Error("invalid"); // +1 log("charging"); stripe.create({ amount }); } // theirs function charge(amount: number) { log("charging"); stripe.create({ amount }); metrics.increment("charge"); // +1 } // semantic-union merged function charge(amount: number) { if (amount <= 0) throw new Error("invalid"); log("charging"); stripe.create({ amount }); metrics.increment("charge"); } ``` ### E: Per-file strategy configuration ```json { "strategies": { "default": "semantic", "src/generated/**": "prefer-theirs", "docs/**": "semantic-union", "package-lock.json": "prefer-theirs", "CHANGELOG.md": "semantic-union", "config/production/**": "prefer-ours" } } ``` This encodes three policies: - Generated code always takes upstream. - Documentation and changelogs union-merge so every team member's edits survive. - Production configuration never silently accepts an incoming edit. ## Edge Cases **`semantic-union` on scalar conflicts.** Unions apply only to container nodes (blocks, sequences, object maps). A conflict on a scalar (a version number, a string literal) falls through to the underlying `modify/modify` conflict regardless of strategy — there is no meaningful union of `5` and `10`. **Strategy and file-type mergers.** The strategy is orthogonal to the file-type merger. `prefer-ours` over a YAML file uses [YAML merge](/yaml-merge) and applies `prefer-ours` only at conflict points; non-conflicting YAML changes from theirs still apply. You are not discarding the entire file, just the disagreements. **Strategy changes mid-session.** You cannot change the top-level strategy once a merge session has started. You can, however, override individual conflict resolutions from the [picker](/interactive-conflict-picker), which is strictly safer: it targets only the conflicts you see. **`prefer-*` on insert/insert collisions.** Both sides inserted a function with the same name but different bodies. `prefer-ours` keeps ours's version and discards theirs's — potentially losing an unrelated implementation. Aura flags insert/insert collisions as `high-severity` in the audit log under any silent-preference strategy. **Interaction with `--dry-run`.** Every strategy supports `--dry-run` which prints the resolution plan without writing. Useful for inspecting what `prefer-theirs` will silently drop before committing. **Interaction with protected branches.** Many teams restrict silent-preference strategies on protected branches. Aura supports this via `.aura/config.json`: ```json { "protected_branches": { "main": { "allowed_strategies": ["semantic"] }, "release/*": { "allowed_strategies": ["semantic", "prefer-ours"] } } } ``` Attempts to merge into a protected branch with a disallowed strategy fail before the session starts, with a diagnostic that names the constraint. **Strategy default and onboarding.** New repos start with `semantic`. Switching a large repo from Git's three-way text merge to Aura's `semantic` strategy typically eliminates 70-95% of the conflicts the team previously hit, depending on language mix and team size. The remaining conflicts are nearly always real disagreements that Git was also surfacing, just with more noise. **Strategy visibility in diffs.** Commits produced by silent-preference strategies carry a trailer indicating the strategy used and how many conflicts were auto-resolved: ```text Aura-Strategy: prefer-ours Aura-Auto-Resolved: 3 Aura-Merge-Audit: .aura/merge/session-abc.audit.jsonl ``` Code hosts that parse trailers (GitHub, GitLab) can surface these on PRs so reviewers know a non-default strategy was used. **Audit expectations.** Silent-preference strategies shift risk from "conflict not resolved" to "change silently dropped." Aura mitigates this with mandatory audit entries, but teams using `prefer-ours` or `prefer-theirs` should set up review workflows that inspect the strategy audit summary on every merge. The CLI emits a machine-readable summary (`aura merge audit --json`) suitable for PR bots. **Strategy composition.** Strategies do not nest. A file matched by `prefer-ours` does not get `semantic-union` inside it; the whole file is under `prefer-ours`. This keeps the mental model simple. If you need semantic-union on a subset of a file, use the [interactive picker](/interactive-conflict-picker) and apply `u` per conflict. **Scripted overrides.** Teams with custom resolution policies can supply a `strategy-plugin` binary that Aura consults per conflict. The plugin receives the conflict JSON on stdin and returns a verb on stdout. Plugins are recorded in the audit log by path and hash. ## See Also - [How AST merge works](/how-ast-merge-works) - [Conflict resolution](/conflict-resolution) - [Interactive conflict picker](/interactive-conflict-picker) - [JSON deep merge](/json-deep-merge) - [YAML merge](/yaml-merge) - [.env merge](/env-merge)