Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

conflict-check

I run multiple AI coding agents (Claude Code, Codex, Cursor Agent, whatever) in parallel git worktrees on the same repo, and eventually they collide in a way git merge can't see. This is a small CLI I built to catch that.

See CHANGELOG.md for what's new (v0.2 adds cross-file conflict detection and N-way branch support).

Why

Say Agent A renames a function, changes what it returns, or changes some assumption about shared state. Meanwhile Agent B, working in a different worktree with zero visibility into what A is doing, writes new code that depends on the old behavior. If those two changes touch different lines — or even different files — git merge doesn't see a conflict. No markers, no warning, exit code 0. The build's green. It just doesn't work anymore, and you find out later, the hard way.

conflict-check tries to catch that before you merge. You tell it what each agent was supposed to be doing, it grabs the diff each agent actually produced, and it asks an LLM whether the two look like they're making conflicting assumptions about the same code.

It actually works, here's proof

This isn't a made-up example — it's a real run of npm run demo against the Gemini provider:

  • Branch agent-a-rename renames add() to sum() in math.js.
  • Branch agent-b-add-helper adds a new sumAll() in the same file, in a different part of it, that calls add() — no idea the rename happened.

Merge them and see what git thinks:

$ git checkout agent-a-rename && git merge agent-b-add-helper
Auto-merging math.js
# clean merge. no conflict markers. exit code 0.

Totally clean, as far as git's concerned. Except sumAll() now calls a function that doesn't exist anymore. Here's what conflict-check check says about the same two branches, before you merge:

$ conflict-check check --branch-a agent-a-rename --branch-b agent-b-add-helper

Checking "agent-a-rename" (1 file) against "agent-b-add-helper" (1 file)...
Using gemini (gemini-3.6-flash)

Overlapping files: math.js

Checking math.js... CONFLICT

✗ math.js
Agent A renames the `add()` function to `sum()`, while Agent B adds a new
`sumAll()` function that explicitly calls `add()`. If both changes are
merged, `sumAll()` will attempt to call `add()`, which no longer exists,
resulting in a runtime ReferenceError.

Summary:
✗ 1 of 1 overlapping file(s) have a likely semantic conflict.

That's the actual, unedited output. I didn't feed it the word "ReferenceError" anywhere — it figured that out from the two diffs on its own.

It catches cross-file conflicts too

The same-file example above is the easy case — both agents touched math.js, so there was an overlapping file to even look at. The harder, more common case is when they don't share a file: Agent A renames a function in one file, and Agent B calls the old name from a completely different file it added or already had. No overlapping filename, no diff collision — git merge sees nothing, and neither did earlier versions of this tool.

conflict-check now parses each branch's diff with a real AST (via ts-morph) to find functions/methods that were added, removed, or renamed, then searches the whole repo — not just the diff — for anything that still calls the old name. Here's that exact scenario, still from npm run demo:

  • Branch agent-a-rename renames add() to sum() in math.js.
  • Branch agent-b-add-helper adds a brand-new file, batch.js, that imports add from math.js and calls it — a file agent-a-rename never touched, and that didn't exist before either branch started.
$ conflict-check check --branch-a agent-a-rename --branch-b agent-b-add-helper

Checking 2 branches (1 pair): agent-a-rename, agent-b-add-helper
Using gemini (gemini-3.6-flash)

=== "agent-a-rename" ⇄ "agent-b-add-helper" ===

[same-file] 0 file(s) touched by both branches
  ✓ No files touched by both branches.

[cross-file] renamed/removed symbols still referenced elsewhere
  Checking math.js: `add` renamed to `sum`... CONFLICT
 CROSS-FILE  math.js — `add` renamed to `sum` — from branch "agent-a-rename"
   CONFIRMED REFERENCE 
    [confirmed] batch.js:1 (branch "agent-b-add-helper") — import { add } from "./math.js";
    [confirmed] batch.js:4 (branch "agent-b-add-helper") — return numbers.reduce((total, n) => add(total, n), 0);
  Branch agent-a-rename renamed `add` to `sum` in math.js, but branch
  agent-b-add-helper's new batch.js still imports and calls `add`, which no
  longer exists after the rename. Merging both branches will throw at
  import/call time.

=== Summary ===
✓ No same-file conflicts across 0 checked file(s).
✗ 1 cross-file conflict(s) with a CONFIRMED reference (real import/scope resolution).

Caught with zero overlapping files. Note the CONFIRMED REFERENCE tag — that's not decoration. It means the reference was resolved through real import/scope binding (ts-morph's language service), not just a name that happens to match. When that can't be resolved (say, both branches touched the same symbol, or the calling code has no module system to trace through), check falls back to a plain AST name match instead and tags it POSSIBLE — NAME MATCH ONLY — still worth a look, but a coincidentally reused name could slip through there, so treat it as a lead to verify, not a confirmed diagnosis. Every reported reference — same-file or cross-file, confirmed or possible — carries its label front and center in the terminal output, never just tucked away in some internal field.

Installing it

Not on npm yet, so you'll have to build it yourself:

git clone https://github.com/Cipher-08/conflict-check.git
cd conflict-check
npm install
npm run build
npm link

npm link is what makes the conflict-check command available globally. It symlinks the built output, so after you pull changes you just need npm run build again — no need to re-link.

If npm link blows up with EACCES, that's your global npm prefix (often /usr/local/lib on a Mac) not being writable by your user — happens a lot with a system-installed Node. Don't sudo npm link your way through it; either fix the prefix permissions or just switch to nvm, which sidesteps the whole problem. Or skip linking entirely and run it as node dist/index.js <command> from inside the repo.

Once it's linked, go into whatever repo you actually want to check and run:

conflict-check init

Commands

  • conflict-check init — sets up .conflict-check/ in the current repo. Prompts for an Anthropic key if it doesn't find one in your env.
  • conflict-check log-task --branch <name> --description "<text>" — jot down what an agent's about to work on. Skip either flag and it'll just ask you interactively.
  • conflict-check snapshot --branch <name> --base <base> — runs git diff <base>...<branch> and saves it to .conflict-check/snapshots/<branch>.diff, plus a small <branch>.meta.json recording the branch's commit sha (used later for cross-file checking, even if the branch/worktree is gone by the time you run check).
  • conflict-check check — the actual point of the tool. Runs the same-file and cross-file checks over every pair of branches you give it, prints a report per pair, then a summary. Exits non-zero on any conflict (confirmed or possible) or if a check couldn't complete, so you can stick it in a pre-merge script. Give it branches any of these ways:
    • --branch-a <name> --branch-b <name> — the original two-branch form, still works exactly as before.
    • --branch <name> — repeat it for as many branches as you're comparing.
    • --branches a,b,c — same thing, comma-separated.

Normal flow looks like this:

conflict-check init

# before each agent starts
conflict-check log-task --branch agent-a-branch --description "Rename add() to sum()"
conflict-check log-task --branch agent-b-branch --description "Add a helper that calls add()"

# once each agent's done
conflict-check snapshot --branch agent-a-branch --base main
conflict-check snapshot --branch agent-b-branch --base main

# before you actually merge
conflict-check check --branch-a agent-a-branch --branch-b agent-b-branch

Running more than two agents at once? Snapshot each one, then check them all together — every pairwise combination gets checked, and conflicts are reported grouped by which pair they're between:

conflict-check check --branches agent-a-branch,agent-b-branch,agent-c-branch

All of this is just JSON and diff files sitting in .conflict-check/ at the repo root — no database, no server, no account, nothing to sign up for.

Which LLM it uses

Two options, and it picks whichever key you've got set (GEMINI_API_KEY wins if both happen to be present):

Gemini — free, no credit card needed. Go to aistudio.google.com, sign in, click "Get API key" → "Create API key", copy it:

export GEMINI_API_KEY=AIza...

Defaults to gemini-3.6-flash, override with GEMINI_MODEL if you want. Free tier has rate limits but it's plenty for trying this out.

Anthropic — needs a billed account:

export ANTHROPIC_API_KEY=sk-ant-...

Or just let conflict-check init prompt you for it — it'll stash it in .conflict-check/config.json, which is gitignored. Defaults to claude-sonnet-5, override with CONFLICT_CHECK_MODEL or a "model" key in that same config file.

Either way, your key only ever goes to that provider's own API, and only when you actually run check.

What this doesn't do (yet)

Being upfront about this stuff up front, most important first:

  • Cross-file detection only understands JS/TS/JSX/TSX, and only functions, arrow-function/function-expression consts, and class methods. Other languages, or symbols like exported constants/types/React components declared some other way, aren't tracked — a removed/renamed one of those won't be caught by the cross-file check (same-file checking is unaffected; it works on raw diffs regardless of language).
  • The "possible (name match)" fallback can still be fooled by a coincidentally reused name. It only kicks in when the symbol's old declaration can't be semantically resolved in the corpus (e.g. both branches touched the same file), and at that point it's back to matching by name with a same-file-declaration shadow check — better than a raw grep, but not a guarantee. That's exactly why it's tagged differently in the output from a CONFIRMED REFERENCE — treat it as a lead, not a verdict.
  • Cross-file checking needs conflict-check snapshot to have run with this version of the tool (it records the branch's commit sha). A snapshot taken before this feature existed just gets skipped for cross-file purposes, with a warning — same-file checking on it still works. Re-run snapshot to pick it up.
  • Cross-file checking also needs the branches' commits to still be resolvable in git — reachable from some ref or otherwise not garbage-collected. Deleting a branch outright (not just the worktree) not long after snapshotting it risks losing that.
  • It only warns, it never touches your code or does the merge for you.
  • No dashboard, no accounts, nothing hosted. Just you, your terminal, one repo at a time.
  • Filename matching (same-file check) is dumb string comparison on diff headers, not quote-aware — files with spaces or weird characters in the name (which git escapes in diff output) probably won't match up correctly.
  • On a very large repo, the cross-file check parses every tracked JS/TS file to build its search corpus — there's no size cap yet, so expect it to take longer the bigger the repo.

Ideas for later

  • Resolve bare/aliased imports (node_modules, tsconfig path aliases, monorepo package references) for cross-file reference finding — right now only relative imports resolve, since the corpus is built purely from git blobs with no node_modules around to resolve into.
  • Track exported constants, types, and other non-function/method symbols in the cross-file check, not just functions/arrow-functions/methods.
  • Maybe a hosted dashboard for teams, eventually, if the local version turns out to actually be useful to people. Not promising anything here.

Contributing

It's rough, it's a v1, and I built it fast — issues and PRs are welcome. If you find a case where it misses something obvious or flags something that isn't actually a problem, open an issue with the two diffs involved. That's the feedback that'll actually shape what comes next.

npm test runs the test suite (vitest) — mostly the AST symbol-diffing and cross-file reference resolution logic, including one end-to-end test that spins up a real temp git repo.

License

MIT

About

Detect semantic conflicts between AI coding agents working in parallel git worktrees

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages