npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gitkraken/conflict-tools

v0.2.1

Published

AI-powered git conflict resolution.

Downloads

1,679

Readme

@gitkraken/conflict-tools

AI-powered git conflict resolution. Parses conflict markers (including delete-modify conflicts), resolves them with an agentic AI loop that has access to git context (blame, grep, diff, log, show, file content), and writes results back to the working tree.

What it does

Three entry points, each serving a different consumer need:

  • extractConflict() — parses conflict markers from a single file and returns a structured representation. Handles both text conflicts (with markers) and delete-modify conflicts (one side deleted, the other modified). No AI, no resolution.

  • resolveConflict() — takes one Conflict and an optional ResolutionContext, runs the AI tool-use loop, returns a Resolution with resolved content, confidence, and metrics. The single-file workhorse.

  • resolveConflicts() + applyResolutions() — batch-resolve every currently unmerged file in the working tree, then write and stage results. Supports pattern-based routing: skip files, apply take-ours/take-theirs, or route to AI per glob pattern. The library does NOT orchestrate git rebase/merge/cherry-pick — the consumer drives the operation lifecycle.

The library never calls git or an AI provider directly. Consumers supply adapters via ports.

Install

pnpm add @gitkraken/conflict-tools

Ports

ConflictGitPort

A bag of optional high-level git ops, plus an optional raw exec fallback. Provide exec only (the library's dispatchers will build the git commands themselves), provide individual ops (your adapter's native API is used directly), or mix both.

import type { ConflictGitPort } from '@gitkraken/conflict-tools';

const git: ConflictGitPort = {
    exec: async (args, options) => simpleGit.raw(...args),
};

High-level ops the library dispatches through:

| Op | Purpose | |---|---| | readFile(path) | Read working-tree file content (used during conflict extraction) | | showFile(ref, path, opts?) | Read file content at a specific ref. Supports startLine/endLine | | unmergedEntries() | List files git considers unresolved | | mergeBase(a, b) | Common ancestor between two refs | | blame(path, opts?) | Line-level authorship. Supports startLine/endLine (maps to git blame -L) | | grep(pattern, opts?) | Content search across the tree. Supports maxResults | | diff(from, to, opts?) | Unified diff between refs, optionally scoped to a path | | log(opts?) | Commit history. Supports ref, path, maxCount | | show(sha, opts?) | Commit details (git show). Supports path to scope output | | writeFile(path, content) | Write resolved content to the working tree | | stageFiles(paths) | Stage resolved files (git add) | | checkoutFile(path, 'ours' \| 'theirs') | Apply a side directly via git | | removeFile(path) | Remove a file from the working tree (git rm) |

Outputs from showFile and blame are capped at 1000 lines; grep and log at 100 results; show at 500 lines. When the cap fires, the library prepends an actionable header (e.g. [Output capped at 1000 lines. Use startLine/endLine to read other regions of the file.]) so the AI can re-query with a narrower range.

If a flow needs an op that the consumer didn't supply and exec is unavailable, the library throws ConflictGitPortMissingOpError.

ConflictModelPort

A single-method port for AI inference. The library owns the tool-use loop — it calls generate, dispatches any toolCalls it sees against ConflictGitPort, feeds the results back, and repeats until the model returns a final structured answer.

import type { ConflictModelPort } from '@gitkraken/conflict-tools';

const model: ConflictModelPort = {
    generate: async ({ system, messages, tools, temperature, signal }) => {
        // Map to your AI SDK (Vercel AI SDK, Anthropic, OpenAI, VS Code LLM API, ...)
        // Return { text?, toolCalls?, usage? }
    },
};

The shape of tools, toolCalls, and toolResults follows abstract types (ToolDefinition, ToolCall, ToolResult) so consumers can adapt any SDK. The library does not require streaming or any provider-specific feature.

ResolutionVerifier (optional)

A pluggable post-resolution check. The library ships a defaultVerifier that scans the assembled content for residual conflict markers. During the AI loop, a failed verification triggers a retry with feedback — the model gets a chance to self-correct.

import { defaultVerifier } from '@gitkraken/conflict-tools';
import type { ResolutionVerifier } from '@gitkraken/conflict-tools';

// Use the built-in (checks for residual <<<<<<< / ======= / >>>>>>>)
const verifier = defaultVerifier;

// Or provide your own (e.g. run a linter, compile check)
const custom: ResolutionVerifier = {
    verify: async (filePath, content) => {
        const errors = await lint(content);
        return errors.length === 0
            ? { valid: true }
            : { valid: false, feedback: errors.join('\n') };
    },
};

Pass the verifier via deps.verifier to resolveConflict or resolveConflicts.

Entry points

extractConflict(filePath, deps) — parse only

import { extractConflict } from '@gitkraken/conflict-tools';

// Text conflict (has markers)
const conflict = await extractConflict('src/auth.ts', { git });

// conflict.filePath        — 'src/auth.ts'
// conflict.markers[]       — position, sides, and surrounding context per marker block
// conflict.type            — 'text'
// conflict.rawContent      — original file content with markers preserved

// Delete-modify conflict (pass reason from unmergedEntries)
const dm = await extractConflict('old-module.ts', { git }, 'deleted-by-them');

// dm.type                  — 'delete-modify'
// dm.deletedBy             — 'theirs'
// dm.markers               — [] (no conflict markers for delete-modify)

Returns null for files without conflict markers and no delete-modify reason.

resolveConflict(conflict, context, deps) — single file

import { resolveConflict } from '@gitkraken/conflict-tools';

const resolution = await resolveConflict(conflict, context, {
    model,
    git,
    config: { maxSteps: 15, temperature: 0 },
    onProgress: (event) => console.log(event),
});

// resolution.content       — resolved file content (markers replaced)
// resolution.strategy      — 'ai' | 'take-ours' | 'take-theirs' | 'deleted' | 'skipped'
// resolution.confidence    — 0..1, from the AI's own self-assessment
// resolution.description   — one-or-two sentence rationale, from the AI
// resolution.chunks?       — per-marker decisions (for observability and validation)
// resolution.metrics?      — { inputTokens, outputTokens, stepCount, toolCallCount, ... }

ResolutionContext carries optional hints the AI can use:

interface ResolutionContext {
    commitMessage?: string;
    prDescription?: string;
    previousResolutions?: Resolution[];
    fileNeighbors?: string[];
    refs?: { ours: string; theirs: string; base?: string };
    threeWayDiff?: { oursDiff: string; theirsDiff: string };
    metadata?: Record<string, unknown>;
}

When refs is provided, the library computes git diff base..ours -- file and git diff base..theirs -- file automatically and includes both in the prompt. This typically lets the AI resolve without any tool calls. Consumers that already have these diffs cached can pass threeWayDiff directly to skip the computation.

Phase 2 refine pass

After Phase 1 resolves conflict markers, the AI may return followUpInstructions: string[] on the resolution — a list of targeted edits it could not express as a whole-chunk decision (for example, re-applying a partial change from the incoming branch). When followUpInstructions is non-empty, resolveConflict automatically runs a second agentic loop (Phase 2) that applies those edits via the edit_file_lines tool.

ResolverConfig.refineMaxSteps?: number controls how many model turns Phase 2 is allowed. Defaults to 5.

Resolution.edits?: IntraFileEditOp[] carries the list of edit operations that were successfully applied during Phase 2. Absent when Phase 2 did not run or all edits were discarded.

IntraFileEditOp is a discriminated union on kind:

  • insert — inserts content after the line matched by anchor at startLine.
  • replace — replaces lines startLineendLine (matched by anchor) with content.
  • delete — removes lines startLineendLine matched by anchor. No content.
  • verify — confirms lines at startLine (optionally through endLine) match anchor without modifying content.

resolveConflicts(deps, context?) + applyResolutions(resolutions, deps) — batch

import { resolveConflicts, applyResolutions } from '@gitkraken/conflict-tools';

const step = await resolveConflicts(
    {
        model,
        git,
        config: {
            rules: [
                { match: ['*.lock', 'pnpm-lock.yaml'], strategy: 'take-theirs' },
                { match: '*.generated.ts', strategy: 'skip' },
                { match: 'dist/**', strategy: 'skip' },
            ],
            defaultStrategy: 'ai',
            fallbackStrategy: 'take-theirs',
            maxSteps: 15,
        },
        onProgress: (event) => updateUI(event),
    },
    { commitMessage, refs },
);

// step.resolutions[]       — successful resolutions
// step.errors[]            — { filePath, error } for files that failed (no fallback or AI exhausted)
// step.skipped?[]          — { filePath, reason } for files that were excluded or had no markers

await applyResolutions(step.resolutions, { git });
// Resolutions are written via writeFile, checkoutFile, or removeFile and staged.

StepConfig extends ResolverConfig with batch-only fields:

  • rules — array of FileRule objects. Each rule has a match (glob pattern or array of patterns) and a strategy. First matching rule wins. Patterns without / match on basename; patterns with / match on full path.
  • defaultStrategy — strategy for files not matching any rule. Defaults to 'ai'.
  • fallbackStrategy — when AI fails for a file, fall back to 'take-ours' or 'take-theirs' instead of recording an error.

Available strategies for rules and defaultStrategy:

| Strategy | Effect | |---|---| | 'ai' | Run AI resolver (default) | | 'take-ours' | Accept current branch version, no AI | | 'take-theirs' | Accept incoming branch version, no AI | | 'deleted' | Remove the file entirely | | 'skip' | Skip the file (no resolution produced) |

The function inspects the working tree once (via unmergedEntries), processes each conflict sequentially, and returns. It does not call git rebase --continue or any other operation command — that is the consumer's job.

Consumer examples

GitLens — single-file editor command

const git = createVSCodeConflictGit(vscodeGitApi);
const model = createVSCodeAiModel(vscodeLlmApi);

const conflict = await extractConflict(activeFilePath, { git });
if (conflict) {
    const resolution = await resolveConflict(conflict, { commitMessage }, { model, git });
    showInDiffEditor(resolution);
}

GitLens / GKD — batch "resolve all" (user-driven)

const step = await resolveConflicts({ git, model, onProgress: webview.post });
const approved = await reviewDialog(step.resolutions);
await applyResolutions(approved, { git });
// User clicks "continue rebase" themselves.

CLI / GitHub Action — automated rebase loop

// The rebase loop lives in the consumer (e.g. @merge-mate/core), not in conflict-tools.
for (const commit of commits) {
    const result = await git.exec(['rebase', '--onto', target, `${commit}^`, commit]);
    if (result.exitCode === 0) continue;

    const step = await resolveConflicts({
        git, model,
        config: {
            rules: [{ match: '*.lock', strategy: 'take-theirs' }],
            fallbackStrategy: 'take-ours',
        },
        onProgress: emit,
    });
    await applyResolutions(step.resolutions, { git });
    await git.exec(['rebase', '--continue']);
}

Eval harness

const conflict = await extractConflict(fixturePath, { git });
const resolution = await resolveConflict(conflict!, context, { model, git });
score(resolution, goldenFile);

Observability

resolveConflicts and resolveConflict accept an onProgress callback that receives a discriminated ConflictProgressEvent:

| Event | When | |---|---| | conflict:found | A conflict file has been parsed | | conflict:excluded | A file was skipped by a rule with strategy: 'skip' | | conflict:skipped | A file had no conflict markers and no delete-modify reason | | resolution:applied | A resolution was produced for a file | | resolution:fallback | AI failed; fallback strategy applied | | resolution:failed | AI failed and no fallback configured | | resolver:tool-call | The AI invoked a tool. Includes reason (why the AI called it) | | resolver:step-usage | Token usage for one model call | | resolver:completed | The resolver finished a single file | | resolver:response | Phase 1 produced its final response | | resolver:tool-result | A Phase 1 tool returned content | | refine:started | Phase 2 has begun | | refine:skipped | Phase 2 was not run (no follow-ups) | | refine:tool-call | Phase 2 model invoked edit_file_lines | | refine:tool-result | Phase 2 tool returned feedback | | refine:final-text | Phase 2 model emitted final description | | refine:completed | Phase 2 finished successfully (includes appliedCount) | | refine:failed | Phase 2 threw before completing | | edit:discarded | An edit batch was rejected (introduced markers) |

Note: refine:tool-result.appliedCount reports edits applied to an in-progress buffer per turn. The final disposition is signaled by refine:completed / edit:discarded / refine:failed — only edits that survive residual-marker validation appear in Resolution.edits.

Operation-level events (rebase started, step completed, etc.) are the consumer's responsibility — the library has no opinion about the surrounding workflow.

Error handling

| Error | Codes | When | |---|---|---| | ConflictError | PARSE_FAILED | Conflict extraction failed | | AIError | VALIDATION_EXHAUSTED, INCOMPLETE_RESOLUTION | Model could not produce a valid resolution after retries | | ConflictGitPortMissingOpError | — | A required git op is missing and no exec fallback was provided |

import { resolveConflict, AIError, ConflictError } from '@gitkraken/conflict-tools';

try {
    await resolveConflict(conflict, context, { model, git });
} catch (error) {
    if (error instanceof ConflictError) {
        // Conflict extraction failed (e.g. binary file or corrupt markers).
    } else if (error instanceof AIError) {
        // Model exhausted retries. Configure StepConfig.fallbackStrategy or handle here.
    }
}

In the batch entry point, errors that have a configured fallback do not throw — they are downgraded to resolution:fallback events, and the resulting resolutions appear in step.resolutions. Errors without a fallback land in step.errors and the loop continues with the next file.

Semver policy

| Change | Bump | |---|---| | New optional field on an exported type | patch | | New variant in ConflictProgressEvent | minor | | New optional method on ConflictGitOps | patch | | New required method on a port | major | | Remove or rename an exported field | major | | Change the signature of a port method | major | | Bundled prompt change (no API change) | patch | | New required field on a config | major |

Contributing

This package follows the monorepo's standard tooling — see the root CONTRIBUTING.md. Local commands:

pnpm --filter @gitkraken/conflict-tools build
pnpm --filter @gitkraken/conflict-tools typecheck
pnpm --filter @gitkraken/conflict-tools test