Skip to content

feat(resolve): merge concurrent quest link-list edits with a git merge driver - #219

Merged
kixelated merged 5 commits into
mainfrom
quest/m0/helpers/resolve
Oct 24, 2026
Merged

kixelated merged 5 commits into
mainfrom
quest/m0/helpers/resolve

Conversation

@kixelated

@kixelated kixelated commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m0/helpers/resolve.md and deletes it.

Problem

Concurrent PRs keep conflicting on quest link lists: main deletes finished quests' lines while a branch inserts a new quest beside them. Git's line merge reports these as conflicts even though the intent of both sides is clear.

Behavior

  • quest resolve <ours> <base> <theirs> is a git merge driver. It runs git merge-file --diff3, then resolves each conflict that consists only of whole entries of one Required, Related, or Closes list, each opening with a link (matched by that link's destination, using the real Markdown parser on each side's full document). Base entries neither side removed are kept, taking a one-sided edit; entries either side inserted are placed after the entry preceding them on that side, ours before theirs; an entry both sides inserted identically appears once. The result goes to <ours>; it exits 1 when conflicts remain.
  • Fail closed: prose, headings, an entry both sides rewrote, an entry one side removed and the other edited, duplicated keys, an entry without a link, or a reordering of shared entries all keep standard conflict markers. Other conflicts in the same file still resolve.
  • Registration: quest init appends quest/** merge=quest to .gitattributes and sets merge.quest.driver in the clone's local config. Any other command sets the config when .gitattributes names merge=quest and no config sets the driver, so fresh clones and worktrees pick it up. Config writes happen only inside a git work tree and never fail a command. quest uninstall removes the line and the merge.quest config section.
  • Fallback: the driver is
    if quest help resolve >/dev/null 2>&1; then quest resolve %A %O %B; else git merge-file -L ours -L base -L theirs %A %O %B; fi,
    so a missing quest, or an older one without resolve, gives today's conflict markers.
  • This repository's .gitattributes gets the line too, and the guide, README, and SETUP.md describe the driver and its GitHub limitation.

Choices made without asking

  1. Probe with quest help resolve, not quest resolve --help. The plan named the latter, but commander prints the top-level help and exits 0 for quest <unknown> --help, so an older binary would pass the probe and then fail the merge. quest help <unknown> exits 2. The lifecycle check now asserts both behaviors on the built binaries.
  2. No explicit sh -c. Git already runs drivers through a shell, so the registered command is the if/then/else itself, with %A %O %B substituted directly.
  3. Remove vs. edit fails closed. The plan says removed links stay removed, but when the other side edited that entry, the edit might matter, so it stays a conflict.
  4. Unresolved markers follow merge.conflictStyle: they include the base section for diff3/zdiff3. Markers are 7 characters and labeled ours/base/theirs, like the fallback. %L, %X, and %Y aren't passed. Analysis uses markers longer than any marker-like run in the inputs, so a setext ======= underline can't be mistaken for one.
  5. init writes .gitattributes even before git init. Only the config write needs a work tree. Self-healing later registers the driver.
  6. Self-healing only fills a missing driver. A custom or older value is left alone.
  7. No post-merge cleanup: a resolved list that ends up empty, or a link git moved out of the conflict that is now listed twice, is left for quest check to flag. It already reports both.

Validation

  • just check and just test pass locally: vitest, hook suites, and the lifecycle check on dist/quest.js and the compiled dist/quest.
  • New test/resolve.test.ts covers the common delete-beside-insert shape, insert ordering, duplicate inserts, one-sided edits, wrapped entries, CRLF, the fail-closed cases, a mixed prose+list file, heading-spanning conflicts, lists outside link sections, and marker-like content. It also runs a real git merge with the driver on PATH (resolves) and without it (falls back to markers), and checks registration, self-healing, and uninstall.
  • quest check --base origin/main passes.

Limitations

  • GitHub's merges ignore drivers, so a PR still shows as conflicting until a local merge from its base is pushed.
  • The driver only helps where quest is on the merge's PATH. In this repository's dev setup (bun run quest), it falls back.

Suggested follow-ups

  • Replay the 280 moq-dev/moq merges from the plan through the driver to measure how many of the 29 quest-only conflicts it now resolves.
  • Consider passing %L/%X/%Y so unresolved markers match git's own labels and size.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 2 commits October 23, 2026 23:04
…e driver

quest resolve merges by lines with git merge-file, then resolves each
conflict that only edits the entries of one Required, Related, or Closes
list: removals stay removed, inserts from either side are kept in place
(ours first), and a one-sided edit wins. Anything else keeps conflict
markers. quest init writes quest/** merge=quest to .gitattributes and sets
merge.quest.driver; any other command registers the driver when the
attributes name it, and uninstall removes both. Without a quest that has
resolve on PATH, the driver falls back to git merge-file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Resolve quest-list conflicts with a merge driver feat(resolve): merge concurrent quest link-list edits with a git merge driver Oct 24, 2026
@kixelated
kixelated marked this pull request as ready for review October 24, 2026 06:20
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T18:10:35.916424Z 43a3bd4 New commits
🔒 Security Review ✅ Completed 2026-10-24T18:13:52.649779Z 43a3bd4 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kixelated

Copy link
Copy Markdown
Collaborator Author

@codex security review

(Written by Claude Opus 5.5)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 5e90c4271c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# Conflicts:
#	quest/m0/helpers/README.md

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 53bfbadb14

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/merge.ts Resolved
A reference-style link's destination lives in a definition outside the
conflict, which the clean regions show only as merged. A side that
re-targeted the entry there looked unchanged, so a remove on the other
side silently dropped it. Resolve only entries whose own lines carry
their destination.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Landing summary

  • Merged main in twice, with no rebase. The first merge was clean. The second hand-resolved a quest/m0/helpers/README.md Required-list conflict by deleting both finished lines (Resolve and Claim ref conflicts). scripts/lifecycle-check.mjs auto-merged and keeps both changes (kixelated/quest#218 and this PR's driver asserts).
  • Fixed Codex's P2 finding in 43a3bd4: the driver now resolves only entries whose own lines carry their destination, so a reference-style entry whose definition changed outside the conflict keeps its markers. Added a regression test.
  • User decision: "Fix, then land on code review". The earlier security review run had stalled for over two hours, so the gate was the code review of the new head. Both Codex reviews then completed with no findings on 43a3bd4.
  • just check and just test pass locally, CI passes, and quest check --base origin/main passes with a build of main.

Landing 43a3bd4 with a squash merge.

(Written by Claude Opus 5.5)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant