From 815e44a042c8194aa2d4f44f34dd8700eaaaca73 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:09:09 +0000 Subject: [PATCH] chore(upstream): run mergiraf on conflicted files during upstream merge Invokes `mergiraf solve` (syntax-aware git merge tool) on each conflicted file before the kilocode-specific transform cascade, re-materializing the file with diff3 markers first so mergiraf can reconstruct the base revision. This auto-resolves the common pattern of neighbouring import additions around kilocode_change markers plus most JSON/YAML/TOML and other structural conflicts, leaving the existing transforms to handle only what's genuinely ambiguous. mergiraf is required at startup; if missing the script aborts early with install instructions (brew / cargo / nix) rather than failing mid-merge. --- script/upstream/merge.ts | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/script/upstream/merge.ts b/script/upstream/merge.ts index 160fdba53b..4783dd1c65 100644 --- a/script/upstream/merge.ts +++ b/script/upstream/merge.ts @@ -61,6 +61,45 @@ interface MergeOptions { author?: string } +async function hasMergiraf(): Promise { + const result = await $`mergiraf --version`.quiet().nothrow() + return result.exitCode === 0 +} + +function abortMissingMergiraf(): never { + logger.error("mergiraf is required but not installed.") + logger.info(" It provides syntax-aware resolution for imports, JSON/YAML/TOML,") + logger.info(" and other structural conflicts during upstream merges.") + logger.info(" Install via one of:") + logger.info(" brew install mergiraf # macOS / Linuxbrew") + logger.info(" cargo install mergiraf # any platform with rustup") + logger.info(" nix profile install nixpkgs#mergiraf # nix") + logger.info(" See https://mergiraf.org/installation.html for more options.") + process.exit(1) +} + +/** + * Attempt syntax-aware resolution of conflicted files via mergiraf. + * Re-materializes each file with diff3 markers first so mergiraf can + * reconstruct the base revision (needed for its structural heuristics). + * Returns the number of files fully resolved and staged. + */ +async function runMergiraf(files: string[]): Promise { + let solved = 0 + for (const file of files) { + const co = await $`git checkout --conflict=diff3 -- ${file}`.quiet().nothrow() + if (co.exitCode !== 0) continue + await $`mergiraf solve ${file}`.quiet().nothrow() + const content = await Bun.file(file) + .text() + .catch(() => "") + if (content.includes("<<<<<<< ")) continue + await $`git add ${file}`.quiet() + solved++ + } + return solved +} + function parseArgs(): MergeOptions { const args = process.argv.slice(2) @@ -139,6 +178,10 @@ async function main() { process.exit(1) } + if (!(await hasMergiraf())) { + abortMissingMergiraf() + } + if (await git.hasUncommittedChanges()) { logger.error("Working directory has uncommitted changes. Please commit or stash them first.") process.exit(1) @@ -452,6 +495,19 @@ async function main() { if (conflictedFiles.length > 0) { logger.info("Attempting to auto-resolve remaining conflicts...") + // Step 7c-pre: syntax-aware resolution via mergiraf. + // Handles the common pattern of neighbouring import additions around + // kilocode_change markers, plus JSON/YAML/TOML key merges and other + // structural conflicts. Presence is enforced at startup. + logger.info("Running mergiraf on remaining conflicts...") + const solved = await runMergiraf(conflictedFiles) + if (solved > 0) { + logger.success(`mergiraf auto-resolved ${solved} conflict(s)`) + conflictedFiles = await git.getConflictedFiles() + } else { + logger.info("mergiraf did not resolve any conflicts") + } + // Transform i18n files const i18nResults = await transformConflictedI18n(conflictedFiles, { dryRun: false, verbose: options.verbose }) const i18nTransformed = i18nResults.filter((r) => r.replacements > 0).length