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.
This commit is contained in:
kiloconnect[bot]
2026-04-28 11:09:09 +00:00
committed by Mark IJbema
parent f2b73dbfac
commit 815e44a042
+56
View File
@@ -61,6 +61,45 @@ interface MergeOptions {
author?: string
}
async function hasMergiraf(): Promise<boolean> {
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<number> {
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