Skip to content

fix(conflict): deterministic JSON merge + --theirs fallback (no more story thrash)#106

Merged
tzone85 merged 1 commit into
mainfrom
fix/conflict-resolver-deterministic
Jun 25, 2026
Merged

fix(conflict): deterministic JSON merge + --theirs fallback (no more story thrash)#106
tzone85 merged 1 commit into
mainfrom
fix/conflict-resolver-deterministic

Conversation

@tzone85

@tzone85 tzone85 commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Problem

clipforge and pulsereview hung for hours during a 6-app build: when the LLM conflict resolver returned commentary instead of merged content for package.json / vitest.config.ts, the resolver aborted the entire rebase and the story thrashed through every escalation tier indefinitely (tech lead returned commentary, not file content). Prior fixes (#91/#93/#99) made commentary fail cleanly but never resolved it — so it still deadlocked.

Fix (vxd itself — prevents recurrence on any project)

  1. Structural JSON merge for package.json, tsconfig*.json, jsconfig.json, composer.json, .eslintrc.json, … (lock files excluded). Pulls both index sides (git.ConflictSides :2:/:3:) and deep-unions the objects so both sides' dependencies/scripts/compilerOptions survive. Fully deterministic — no LLM. This is exactly the file class that deadlocked.
  2. Deterministic --theirs fallback (git.CheckoutTheirs) when the LLM genuinely can't merge any file — surfaced via the new errUnmergeable sentinel (commentary or leftover markers). Keeps the story-branch version and continues; the pre-merge QA gate + integration build validate it. API/transport errors (fatal, capacity, transient) are NOT errUnmergeable → they still abort/pause for a clean retry/vxd resume, never silently taking a side under an outage.
  3. Restores the review/qa orphan-recovery regression test that fix: complete transient-failure recovery (tier-column reset + review/qa-stranded recovery) #103 shipped without.

Rebase ours/theirs (pinned by test)

git rebase <upstream> runs from the story worktree → during a conflict ours = base, theirs = story. Keeping the story's work = --theirs.

Test plan

  • conflict_json_merge_test.go — deep-union of deps/scripts, theirs-wins on scalar/type-mismatch, invalid-JSON falls through
  • conflict_sides_test.go (git pkg) — ours=base/theirs=story semantics, CheckoutTheirs resolves+stages
  • conflict_fallback_test.go — commentary → fallback succeeds (rebase completes, story version kept); generic LLM error still aborts
  • resume_orphan_recovery_test.go — guards fix: complete transient-failure recovery (tier-column reset + review/qa-stranded recovery) #103
  • go build, go vet, golangci-lint run (0 issues), full go test ./... green

🤖 Generated with Claude Code

…ergeable file never thrashes a story

Root cause of clipforge/pulsereview hanging for hours: when the LLM returned
commentary instead of merged content for package.json/vitest.config.ts, the
resolver aborted the whole rebase, and the story thrashed through every
escalation tier indefinitely ('tech lead returned commentary, not file content').

Two fixes in internal/engine/conflict_resolver.go (+ internal/git/conflict.go):
- Structural JSON merge for package.json/tsconfig*.json/jsconfig.json/etc:
  pulls both index sides (git.ConflictSides :2:/:3:) and deep-unions the objects
  (deepMergeJSON) so BOTH sides' deps/scripts/compilerOptions survive — fully
  deterministic, no LLM. Lock files excluded; invalid JSON falls through to LLM.
- Deterministic --theirs fallback (git.CheckoutTheirs) when the LLM genuinely
  cannot merge any file — surfaced as the new errUnmergeable sentinel (commentary
  or leftover markers). The story-branch version is kept and the rebase continues;
  the pre-merge QA gate + integration build validate it. API/transport errors
  (fatal, capacity, transient) are NOT errUnmergeable, so they still abort/pause
  for a clean retry/resume — never silently take a side under an outage.

Also restores the review/qa orphan-recovery regression test (#103 shipped without
one): resume_orphan_recovery_test.go.

Tests: conflict_json_merge_test.go (deep-union, theirs-wins, invalid-JSON),
conflict_sides_test.go (rebase ours=base/theirs=story semantics, CheckoutTheirs),
conflict_fallback_test.go (commentary→fallback succeeds; generic LLM error still
aborts), resume_orphan_recovery_test.go. go build/vet/lint(0)/test all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tzone85 tzone85 merged commit 37a76ce into main Jun 25, 2026
5 checks passed
@tzone85 tzone85 deleted the fix/conflict-resolver-deterministic branch June 25, 2026 08:19
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