Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ Do not edit `CONTRIBUTING.md`, `PROMPTING.md`, `assets/AGENTS.md`, or

- Run `quest guide` and read it completely before starting work.
- Keep README examples consistent with the CLI's actual `--help` output.
In this checkout, `quest` means `bun run --silent quest`.
In this checkout, `quest` means `bun run --silent quest`; the dev shell's
`quest` is a pinned build for the merge driver, not this checkout.
- Use an isolated worktree under `.worktrees/`. Preserve existing changes.
- Use the Nix shell so local tooling matches CI: `nix develop`
- Run `just install` in a fresh checkout to install the locked dependencies.
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,10 @@ opening link. Prose, headings, an entry both sides rewrote, or one side
removed while the other edited, keep the usual conflict markers. Any `quest`
command registers the driver in a fresh clone or worktree whose
`.gitattributes` names it. Without `quest` on the merge's PATH, the driver
falls back to `git merge-file`, and GitHub's merges ignore drivers, so a PR
shows as conflicting until a local merge from its base is pushed.
falls back to `git merge-file` and, when conflicts remain, prints to stderr
that the file's link lists were not auto-resolved. GitHub's merges ignore
drivers, so a PR shows as conflicting until a local merge from its base is
pushed.

The registered driver passes Git's conflict marker length and revision labels
to `quest resolve`, including the ancestor label for `diff3` conflicts. A direct
Expand Down
19 changes: 18 additions & 1 deletion flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
# A reviewed quest for the dev shell's merge driver, so local merges resolve
# link lists without running the checkout's own, possibly unreviewed, code.
# Only its source is pinned, so the lock never chains older pins. Bump with
# `nix flake update quest-pinned`.
quest-pinned = {
url = "github:moq-dev/quest";
flake = false;
};
};

outputs =
{
self,
nixpkgs,
flake-utils,
quest-pinned,
...
}:
# The pinned nixpkgs no longer evaluates on x86_64-darwin; Intel Macs use the release binary.
Expand All @@ -29,6 +38,7 @@
config.allowUnfreePredicate = pkg: nixpkgs.lib.getName pkg == "claude-code";
};
quest = pkgs.callPackage ./nix/package.nix { };
questPinned = pkgs.callPackage "${quest-pinned}/nix/package.nix" { };
# https://downloads.claude.ai/claude-code-releases/2.1.305/manifest.zst.json
# Keep the tested runner version independent of nixpkgs.
claudeCode = pkgs.claude-code.override {
Expand Down Expand Up @@ -68,6 +78,8 @@
packages = with pkgs; [
# Installs dependencies, runs every script and tool, and compiles the release binaries.
bun
# `quest resolve` for the merge driver; `just` and `bun run quest` run the checkout.
questPinned
just
jq
git
Expand Down
1 change: 0 additions & 1 deletion quest/m0/helpers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ commands from Sep-Oct. Session counts in each child come from that window.
## Required

- [quest merge --land gaps](/quest/m0/helpers/merge-land.md) - squash on squash-only repositories, PR URLs outside a checkout, labels released after direct merges, and a warning when auto-merge outlives its head
- [Visible merge-driver fallback](/quest/m0/helpers/merge-driver.md) - a merge without `quest` on PATH says so, and this repository's dev shell provides one
- [Review rules](/quest/m0/helpers/review-rules.md) - a head whose diff matches the reviewed diff keeps its review
- [Plan delegated follow-ups](/quest/m0/helpers/follow-up-plan.md) - a caller plans a background merge's follow-ups without asking
- [Drop review-rounds](/quest/m0/helpers/review-rounds.md) - `quest status` stops counting GitHub reviews now that `quest review` numbers rounds
Expand Down
28 changes: 0 additions & 28 deletions quest/m0/helpers/merge-driver.md

This file was deleted.

4 changes: 0 additions & 4 deletions quest/m0/required-heading.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,3 @@ so it never sees the heading. After resolving, drop a `Required` heading
whose section is now empty. Leave other empty sections to `quest check`, which
should report them. Test it with both sides each removing one of the last two
entries.

## Related

- [Visible merge-driver fallback](/quest/m0/helpers/merge-driver.md) - the driver's other gap
3 changes: 2 additions & 1 deletion src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,8 @@ export function main(argv: string[], out: Output): number | Promise<number> {
"or zdiff3. Exits 1 when a conflict remains.\n\n" +
"quest init registers it for quest/** as the merge.quest driver. Any other command registers the driver " +
"in the clone's config when .gitattributes names merge=quest and no config sets it. Without quest on " +
"the merge's PATH, the driver falls back to git merge-file. GitHub's merges ignore merge drivers.",
"the merge's PATH, the driver falls back to git merge-file, and when conflicts remain it prints to stderr " +
"that the file's link lists were not auto-resolved. GitHub's merges ignore merge drivers.",
)
.argument("<ours>", "Our version, overwritten with the result (git's %A)")
.argument("<base>", "The common ancestor (git's %O)")
Expand Down
12 changes: 9 additions & 3 deletions src/cli/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ const MARKER_SIZE = 7;
* names for `%A`, `%O`, and `%B` and runs it with a shell. A `quest` missing
* from the merge's PATH, or one without `resolve`, falls back to git's own
* merge with conflict markers: `quest help <unknown>` exits non-zero, while
* `quest <unknown> --help` prints the top-level help and exits zero.
* `quest <unknown> --help` prints the top-level help and exits zero. A
* fallback that leaves conflicts says why on stderr, naming the file (`%P`),
* since those markers may be link-list edits `quest resolve` would have
* merged. Git keeps a placeholder it does not know, such as printf's `%s`.
*/
export const DRIVER =
"if quest help resolve >/dev/null 2>&1; " +
"then quest resolve %A %O %B --marker-size=%L --ours-label=%X --base-label=%S --theirs-label=%Y; " +
'quest_merge_status=$?; if [ "$quest_merge_status" -le 1 ]; then exit "$quest_merge_status"; fi; fi; ' +
"git merge-file --marker-size=%L -L %X -L %S -L %Y %A %O %B";
'quest_merge_status=$?; if [ "$quest_merge_status" -le 1 ]; then exit "$quest_merge_status"; fi; ' +
"quest_merge_reason='quest resolve failed'; else quest_merge_reason='no quest resolve on PATH'; fi; " +
"git merge-file --marker-size=%L -L %X -L %S -L %Y %A %O %B && exit 0; quest_merge_status=$?; " +
"printf 'quest: %s, so link lists in %s were not auto-resolved\\n' \"$quest_merge_reason\" %P >&2; " +
'exit "$quest_merge_status"';

export interface ResolveOptions {
markerSize?: number;
Expand Down
7 changes: 7 additions & 0 deletions test/resolve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ describe.skipIf(process.platform === "win32")("the merge driver", () => {
const merged = mergeMain(dir, tools);
expect(merged.status).toBe(1);
expect(readFileSync(join(dir, "quest/README.md"), "utf8")).toContain("<<<<<<< HEAD\n");
expect(merged.stderr).toContain(
"quest: no quest resolve on PATH, so link lists in quest/README.md were not auto-resolved\n",
);
});

for (const mode of ["quest", "missing", "older"] as const) {
Expand Down Expand Up @@ -320,6 +323,10 @@ describe.skipIf(process.platform === "win32")("the merge driver", () => {
const result = mergeMain(dir, mode === "quest" ? `${tools}:${process.env.PATH}` : tools, "their'branch");
expect(result.status, result.stderr).toBe(1);
expect(readFileSync(join(dir, "quest/README.md"), "utf8")).toBe(expected);
const reason = { quest: null, missing: "no quest resolve on PATH", older: "quest resolve failed" }[mode];
const warning = "so link lists in quest/README.md were not auto-resolved";
if (reason === null) expect(result.stderr).not.toContain(warning);
else expect(result.stderr).toContain(`quest: ${reason}, ${warning}\n`);
});
}

Expand Down
Loading