@gitkraken/conflict-tools 0.2.0 → 0.2.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/index.d.ts +2 -2
- package/dist/resolution/create-resolution.d.ts +3 -1
- package/dist/resolution/create-resolution.js +1 -1
- package/dist/resolution/raw-signals.d.ts +12 -0
- package/dist/resolution/raw-signals.js +3 -0
- package/dist/resolver/loop.js +1 -1
- package/dist/resolver/parse-response.js +1 -1
- package/dist/resolver/prompt.js +7 -8
- package/dist/resolver/resolve-conflict.js +1 -1
- package/dist/resolver/resolve-conflicts.js +1 -1
- package/dist/resolver/three-way-diff.d.ts +4 -2
- package/dist/resolver/three-way-diff.js +2 -1
- package/dist/types/resolution.d.ts +33 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- The emergency fallback (taken when the resolver exhausts its steps and a `fallbackStrategy` is configured) now reports `confidence: 0` instead of `1`, so a blind take-one-side result no longer masquerades as a fully-confident resolution. The taken side's `strategy` and `content` are unchanged.
|
|
13
|
+
|
|
14
|
+
## [0.2.1] - 2026-06-09
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `Resolution.rawSignals?: ResolutionRawSignals` exposes policy-free measurements of a conflict and its resolution (conflict type, line counts per side, marker statistics, step/retry counts) for consumers to feed into their own assessment logic.
|
|
19
|
+
- Exported `ResolutionRawSignals` and `DiffStats` types from the package root.
|
|
20
|
+
- `ThreeWayDiff` gained optional `oursStats?: DiffStats` and `theirsStats?: DiffStats`, carrying parsed insertion/deletion counts per side.
|
|
21
|
+
- `ResolutionMetrics.retryCount?: number` tracks re-prompts after parse/validation/verification failures, excluding tool-call steps.
|
|
22
|
+
|
|
10
23
|
## [0.2.0] - 2026-06-02
|
|
11
24
|
|
|
12
25
|
### Added
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ConflictError, ConflictErrorCode } from "./errors/conflict.js";
|
|
|
3
3
|
import { BlameOptions, ConflictDiffOptions, ConflictGitOps, ConflictGitPort, ConflictGitPortMissingOpError, GrepOptions, LogOptions, OpOptions, ShowFileOptions, ShowOptions, UnmergedEntry, UnmergedReason } from "./ports/git.js";
|
|
4
4
|
import { IntraFileEditOp } from "./types/hints.js";
|
|
5
5
|
import { Issue } from "./types/issue.js";
|
|
6
|
-
import { Resolution, ResolutionContext, ResolutionMetrics, ResolutionRefs, ResolutionStrategy, ResolvedChunk, ThreeWayDiff } from "./types/resolution.js";
|
|
6
|
+
import { DiffStats, Resolution, ResolutionContext, ResolutionMetrics, ResolutionRawSignals, ResolutionRefs, ResolutionStrategy, ResolvedChunk, ThreeWayDiff } from "./types/resolution.js";
|
|
7
7
|
import { Conflict, ConflictMarker, ConflictMarkerUserInput, ConflictType } from "./types/conflict.js";
|
|
8
8
|
import { extractConflict } from "./extraction/extract.js";
|
|
9
9
|
import { checkoutFile, removeFile, show } from "./git/port-dispatch.js";
|
|
@@ -17,4 +17,4 @@ import { ConflictProgressEvent } from "./types/events.js";
|
|
|
17
17
|
import { resolveConflict } from "./resolver/resolve-conflict.js";
|
|
18
18
|
import { FileRule, StepConfig, StepResult } from "./types/step.js";
|
|
19
19
|
import { resolveConflicts } from "./resolver/resolve-conflicts.js";
|
|
20
|
-
export { AIError, type AIErrorCode, type BlameOptions, type Conflict, type ConflictDiffOptions, ConflictError, type ConflictErrorCode, type ConflictGitOps, type ConflictGitPort, ConflictGitPortMissingOpError, type ConflictMarker, type ConflictMarkerUserInput, type ConflictModelMessage, type ConflictModelParams, type ConflictModelPort, type ConflictModelResult, type ConflictProgressEvent, type ConflictType, type FileRule, type GrepOptions, type IntraFileEditOp, type Issue, type LogOptions, type OpOptions, type Resolution, type ResolutionContext, type ResolutionMetrics, type ResolutionRefs, type ResolutionStrategy, type ResolutionVerifier, type ResolvedChunk, type ResolverConfig, type ShowFileOptions, type ShowOptions, type StepConfig, type StepResult, type ThreeWayDiff, type ToolCall, type ToolDefinition, type ToolResult, type UnmergedEntry, type UnmergedReason, type VerificationResult, applyResolutions, checkoutFile, createResolution, defaultVerifier, extractConflict, removeFile, resolveConflict, resolveConflicts, show, takeOurs, takeTheirs };
|
|
20
|
+
export { AIError, type AIErrorCode, type BlameOptions, type Conflict, type ConflictDiffOptions, ConflictError, type ConflictErrorCode, type ConflictGitOps, type ConflictGitPort, ConflictGitPortMissingOpError, type ConflictMarker, type ConflictMarkerUserInput, type ConflictModelMessage, type ConflictModelParams, type ConflictModelPort, type ConflictModelResult, type ConflictProgressEvent, type ConflictType, type DiffStats, type FileRule, type GrepOptions, type IntraFileEditOp, type Issue, type LogOptions, type OpOptions, type Resolution, type ResolutionContext, type ResolutionMetrics, type ResolutionRawSignals, type ResolutionRefs, type ResolutionStrategy, type ResolutionVerifier, type ResolvedChunk, type ResolverConfig, type ShowFileOptions, type ShowOptions, type StepConfig, type StepResult, type ThreeWayDiff, type ToolCall, type ToolDefinition, type ToolResult, type UnmergedEntry, type UnmergedReason, type VerificationResult, applyResolutions, checkoutFile, createResolution, defaultVerifier, extractConflict, removeFile, resolveConflict, resolveConflicts, show, takeOurs, takeTheirs };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Resolution, ResolutionMetrics, ResolvedChunk } from "../types/resolution.js";
|
|
1
|
+
import { Resolution, ResolutionMetrics, ResolvedChunk, ThreeWayDiff } from "../types/resolution.js";
|
|
2
2
|
import { Conflict } from "../types/conflict.js";
|
|
3
3
|
|
|
4
4
|
//#region src/resolution/create-resolution.d.ts
|
|
@@ -7,6 +7,8 @@ interface CreateResolutionOptions {
|
|
|
7
7
|
description?: string;
|
|
8
8
|
metrics?: ResolutionMetrics;
|
|
9
9
|
fileStrategy?: 'ours' | 'theirs' | 'deleted';
|
|
10
|
+
/** Source for `rawSignals.combinedDiffLines`; absent for non-AI strategies (reports 0). */
|
|
11
|
+
threeWayDiff?: ThreeWayDiff;
|
|
10
12
|
}
|
|
11
13
|
declare function createResolution(conflict: Conflict, chunks: ResolvedChunk[], options: CreateResolutionOptions): Resolution;
|
|
12
14
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{applyChunks as e}from"./chunk-apply.js";function
|
|
1
|
+
import{applyChunks as e}from"./chunk-apply.js";import{computeRawSignals as t}from"./raw-signals.js";function n(n,a,o){let s=a;o.fileStrategy&&o.fileStrategy!==`deleted`&&(s=n.markers.map((e,t)=>({markerIndex:t,strategy:o.fileStrategy})));let c=o.description!==void 0&&o.description.trim().length>0?o.description:i(s),l;l=o.fileStrategy===`deleted`?`deleted`:o.fileStrategy?o.fileStrategy===`ours`?`take-ours`:`take-theirs`:r(s);let u=o.fileStrategy===`deleted`||s.length===0?``:e(n.rawContent,n.markers,s);return{filePath:n.filePath,content:u,strategy:l,confidence:o.confidence,description:c,chunks:s,...o.metrics?{metrics:o.metrics}:{},rawSignals:t(n,{threeWayDiff:o.threeWayDiff,stepCount:o.metrics?.stepCount,retryCount:o.metrics?.retryCount})}}function r(e){if(e.length===0)return`skipped`;let t=new Set(e.map(e=>e.strategy));if(t.size===1){if(t.has(`ours`))return`take-ours`;if(t.has(`theirs`))return`take-theirs`}return`ai`}function i(e){if(e.length===0)return`No markers resolved.`;let t=new Set(e.map(e=>e.strategy));if(t.size===1){if(t.has(`ours`))return`All markers resolved by taking ours.`;if(t.has(`theirs`))return`All markers resolved by taking theirs.`}return`Resolved ${e.length} marker(s) with mixed strategies.`}export{n as createResolution};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ResolutionRawSignals, ThreeWayDiff } from "../types/resolution.js";
|
|
2
|
+
import { Conflict } from "../types/conflict.js";
|
|
3
|
+
|
|
4
|
+
//#region src/resolution/raw-signals.d.ts
|
|
5
|
+
interface RawSignalsInput {
|
|
6
|
+
threeWayDiff?: ThreeWayDiff;
|
|
7
|
+
stepCount?: number;
|
|
8
|
+
retryCount?: number;
|
|
9
|
+
}
|
|
10
|
+
declare function computeRawSignals(conflict: Conflict, input?: RawSignalsInput): ResolutionRawSignals;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { RawSignalsInput, computeRawSignals };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
function e(e,i={}){let a=n(i.threeWayDiff?.oursStats),o=n(i.threeWayDiff?.theirsStats);return{conflictType:e.type,maxSideLines:t(e),oursDiffLines:a,theirsDiffLines:o,combinedDiffLines:a+o,markersCount:e.markers.length,markersWithMissingBase:r(e),emptySideMarkersCount:e.markers.filter(e=>e.ours===``||e.theirs===``).length,stepCount:i.stepCount??0,retryCount:i.retryCount??0}}function t(e){let t=0;for(let n of e.markers){let e=i(n.ours),r=i(n.theirs);e>t&&(t=e),r>t&&(t=r)}return t}function n(e){return e?e.insertions+e.deletions:0}function r(e){return e.markers.some(e=>e.base!==null)?e.markers.filter(e=>e.base===null).length:0}function i(e){return e===``?0:(e.endsWith(`
|
|
2
|
+
`)?e.slice(0,-1):e).split(`
|
|
3
|
+
`).length}export{e as computeRawSignals};
|
package/dist/resolver/loop.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AIError as e}from"../errors/ai.js";import{applyChunks as t}from"../resolution/chunk-apply.js";import{sumUsages as n}from"../resolution/metrics.js";import{parseModelResponse as r}from"./parse-response.js";import{buildPrompt as i}from"./prompt.js";import{createResolverTools as a}from"./tools.js";import{validateChunks as o}from"./validate.js";async function s(n,s,u){let d=u.config?.maxSteps??15,f=u.config?.temperature??0,{definitions:p,execute:m}=a(u.git),{system:h,userMessage:g}=i(n,s,{maxSteps:d}),_=[{role:`user`,content:g}],v=u.signal?{signal:u.signal}:void 0,y=0,b=0,x=[],
|
|
1
|
+
import{AIError as e}from"../errors/ai.js";import{applyChunks as t}from"../resolution/chunk-apply.js";import{sumUsages as n}from"../resolution/metrics.js";import{parseModelResponse as r}from"./parse-response.js";import{buildPrompt as i}from"./prompt.js";import{createResolverTools as a}from"./tools.js";import{validateChunks as o}from"./validate.js";async function s(n,s,u){let d=u.config?.maxSteps??15,f=u.config?.temperature??0,{definitions:p,execute:m}=a(u.git),{system:h,userMessage:g}=i(n,s,{maxSteps:d}),_=[{role:`user`,content:g}],v=u.signal?{signal:u.signal}:void 0,y=0,b=0,x=0,S=[],C=n.markers.some(e=>e.userResolution!=null),w;for(;y<d;){y++;let e=await u.model.generate({system:h,messages:[..._],tools:p,temperature:f,signal:u.signal});if(e.usage&&(S.push(e.usage),u.onProgress?.({type:`resolver:step-usage`,filePath:n.filePath,stepNumber:y,usage:e.usage})),e.toolCalls&&e.toolCalls.length>0){_.push({role:`assistant`,content:e.text,toolCalls:e.toolCalls});for(let t of e.toolCalls){let e=typeof t.args.reason==`string`?t.args.reason:void 0;u.onProgress?.({type:`resolver:tool-call`,filePath:n.filePath,tool:t.name,args:t.args,stepNumber:y,reason:e}),b++;let r=await m(t,v);u.onProgress?.({type:`resolver:tool-result`,filePath:n.filePath,tool:t.name,stepNumber:y,content:r.content}),_.push({role:`tool`,toolCallId:r.toolCallId,toolName:t.name,content:r.content})}continue}let i=e.text??``;if(!i.trim()){x++,_.push({role:`assistant`,content:``}),_.push({role:`user`,content:`[Step ${y}/${d}] Your response was empty. Please respond with a valid JSON object containing "description" (1-2 sentences) and either "strategy" ("ours", "theirs", or "deleted") with a top-level "confidence" (0-1) for a file-level decision, or "chunks" (array of { markerIndex, strategy, confidence, content?, reason? }).`});continue}let a;try{a=r(i,n.markers.length)}catch(e){x++;let t=e instanceof Error?e.message:String(e);_.push({role:`assistant`,content:i}),_.push({role:`user`,content:`[Step ${y}/${d}] Failed to parse your response: ${t}. Please respond with a valid JSON object containing "description" (1-2 sentences) and either "strategy" ("ours", "theirs", or "deleted") with a top-level "confidence" (0-1) for a file-level decision, or "chunks" (array of { markerIndex, strategy, confidence, content?, reason? }).`});continue}if(a.strategy===`deleted`&&n.type!==`delete-modify`){x++,_.push({role:`assistant`,content:i}),_.push({role:`user`,content:`[Step ${y}/${d}] Strategy "deleted" is only valid for delete-modify conflicts. This is a text conflict — respond with "chunks" or a file-level "ours"/"theirs" strategy.`});continue}if(a.strategy){let e=a.strategy;if((e===`ours`||e===`theirs`)&&C)a.chunks=n.markers.map((t,n)=>t.userResolution==null?{markerIndex:n,strategy:e}:null).filter(e=>e!==null),a.strategy=void 0;else return u.onProgress?.({type:`resolver:response`,filePath:n.filePath,chunks:a.chunks,strategy:e,confidence:a.confidence,description:a.description,...a.followUpInstructions?{followUpInstructions:a.followUpInstructions}:{}}),{chunks:a.chunks,strategy:e,confidence:a.confidence,description:a.description,metrics:l(S,y,b,x),...a.followUpInstructions?{followUpInstructions:a.followUpInstructions}:{}}}if(n.markers.length===0&&!a.strategy){x++,_.push({role:`assistant`,content:i}),_.push({role:`user`,content:`[Step ${y}/${d}] Delete-modify conflicts require a file-level strategy field ("ours", "theirs", or "deleted"). Please respond with a JSON object containing "strategy".`});continue}let s=a.chunks.filter(e=>n.markers[e.markerIndex]?.userResolution==null),g=o(s,n);if(!g.valid){x++;let e=(g.issues??[]).map(e=>e.message).join(` `);_.push({role:`assistant`,content:i}),_.push({role:`user`,content:`[Step ${y}/${d}] Validation failed: ${e}\nPlease fix the issues and respond with a corrected JSON object. Make sure every marker index appears exactly once, or use a top-level "strategy" field.`});continue}let T=c(s,n);if(u.verifier&&n.type===`text`){let e=t(n.rawContent,n.markers,T),r=await u.verifier.verify(n.filePath,e);if(!r.valid){x++;let e=(r.issues??[]).filter(e=>e.severity===`error`).map(e=>e.message).join(` `)||`Content did not pass verification.`;_.push({role:`assistant`,content:i}),_.push({role:`user`,content:`[Step ${y}/${d}] Verification failed: ${e}\nPlease fix and respond with corrected JSON.`});continue}let a=(r.issues??[]).filter(e=>e.severity===`warning`);a.length>0&&(w=a,u.onProgress?.({type:`verification:issues`,filePath:n.filePath,issues:a}))}return u.onProgress?.({type:`resolver:response`,filePath:n.filePath,chunks:a.chunks,confidence:a.confidence,description:a.description,...a.followUpInstructions?{followUpInstructions:a.followUpInstructions}:{}}),{chunks:T,confidence:a.confidence,description:a.description,metrics:l(S,y,b,x),...a.followUpInstructions?{followUpInstructions:a.followUpInstructions}:{},...w&&w.length>0?{issues:w}:{}}}throw new e(`VALIDATION_EXHAUSTED`,`Resolver exhausted ${d} steps without producing a valid resolution for ${n.filePath}.`)}function c(e,t){let n=[...e];for(let e=0;e<t.markers.length;e++){let r=t.markers[e]?.userResolution;r!=null&&n.push({markerIndex:e,...r})}return n.sort((e,t)=>e.markerIndex-t.markerIndex),n}function l(e,t,r,i){return{...n(e)??{inputTokens:0,outputTokens:0},stepCount:t,toolCallCount:r,retryCount:i}}export{s as runResolverLoop};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function e(e,
|
|
1
|
+
function e(e,r){let i=n(e);if(i===null)throw Error(`No valid JSON object found in model response.`);let a;try{a=JSON.parse(i)}catch{throw Error(`Failed to parse JSON from model response.`)}if(typeof a!=`object`||!a||Array.isArray(a))throw Error(`Model response must be a JSON object.`);let o=a,s=o.description;if(typeof s!=`string`||s.trim().length===0)throw Error(`Field "description" must be a non-empty string.`);let c;if(Array.isArray(o.followUpInstructions)){let e=o.followUpInstructions.filter(e=>typeof e==`string`).map(e=>e.trim()).filter(e=>e.length>0);e.length>0&&(c=e)}if(o.strategy!==void 0){let e=o.strategy;if(e!==`ours`&&e!==`theirs`&&e!==`deleted`)throw Error(`Field "strategy" must be "ours", "theirs", or "deleted".`);return{confidence:t(o.confidence,`Field "confidence"`),description:s.trim(),chunks:[],strategy:e,...c?{followUpInstructions:c}:{}}}let l=o.chunks;if(!Array.isArray(l))throw Error(`Field "chunks" must be an array. Each chunk needs { markerIndex, strategy, confidence, content? }.`);let u=[],d=[];for(let e of l){if(typeof e!=`object`||!e)throw Error(`Each chunk must be an object.`);let n=e,i=n.markerIndex,a=n.strategy;if(typeof i!=`number`||!Number.isInteger(i))throw Error(`Each chunk must have an integer markerIndex.`);if(r===0)throw Error(`This conflict has no markers (delete-modify). Use a file-level "strategy" field ("ours", "theirs", or "deleted") instead of "chunks" with markerIndex.`);if(i<0||i>=r)throw Error(`markerIndex ${i} is out of range. Valid range: 0..${r-1}.`);let o=t(n.confidence,`Chunk at markerIndex ${i}: "confidence"`);d.push(o);let s=typeof n.reason==`string`&&n.reason.trim().length>0?n.reason.trim():void 0;if(a===`ours`||a===`theirs`)u.push({markerIndex:i,strategy:a,confidence:o,...s?{reason:s}:{}});else if(a===`merged`){let e=n.content;if(typeof e!=`string`)throw Error(`Chunk at markerIndex ${i} with strategy "merged" must have a string "content".`);u.push({markerIndex:i,strategy:a,content:e,confidence:o,...s?{reason:s}:{}})}else throw Error(`Unknown strategy "${String(a)}" at markerIndex ${i}. Valid strategies: "ours", "theirs", "merged".`)}return{confidence:d.length>0?Math.min(...d):t(o.confidence,`Field "confidence"`),description:s.trim(),chunks:u,...c?{followUpInstructions:c}:{}}}function t(e,t){if(typeof e!=`number`||!Number.isFinite(e)||e<0||e>1)throw Error(`${t} must be a number in [0, 1].`);return e}function n(e){let t=e.trim(),n=/```(?:json)?\s*\n([\s\S]*?)\n\s*```/.exec(t);if(n){let e=n[1];if(e!==void 0)return e.trim()}let r=/\{[\s\S]*\}/.exec(t);return r?r[0]:null}export{e as parseModelResponse};
|
package/dist/resolver/prompt.js
CHANGED
|
@@ -18,12 +18,11 @@ When you are ready to resolve, respond with a JSON object in this format:
|
|
|
18
18
|
|
|
19
19
|
\`\`\`json
|
|
20
20
|
{
|
|
21
|
-
"
|
|
22
|
-
"description": "Per-chunk: took ours on marker 0, theirs on marker 1, merged markers 2.",
|
|
21
|
+
"description": "Per-chunk: took ours on marker 0, theirs on marker 1, merged marker 2.",
|
|
23
22
|
"chunks": [
|
|
24
|
-
{ "markerIndex": 0, "strategy": "ours" },
|
|
25
|
-
{ "markerIndex": 1, "strategy": "theirs" },
|
|
26
|
-
{ "markerIndex": 2, "strategy": "merged", "content": "resolved code here" }
|
|
23
|
+
{ "markerIndex": 0, "strategy": "ours", "confidence": 0.9, "reason": "Theirs only reformatted; ours holds the real change." },
|
|
24
|
+
{ "markerIndex": 1, "strategy": "theirs", "confidence": 0.8, "reason": "Theirs adds the new validation; ours was untouched here." },
|
|
25
|
+
{ "markerIndex": 2, "strategy": "merged", "confidence": 0.65, "content": "resolved code here", "reason": "Both sides edited this block; combined ours' logging with theirs' guard." }
|
|
27
26
|
],
|
|
28
27
|
"followUpInstructions": [
|
|
29
28
|
"Insert the analytics import from theirs (line 12) below the existing logger import (line 7).",
|
|
@@ -59,12 +58,12 @@ Use file-level "deleted" to accept a file deletion in a delete-modify conflict:
|
|
|
59
58
|
\`\`\`
|
|
60
59
|
|
|
61
60
|
Top-level fields:
|
|
62
|
-
- "confidence": a number in [0, 1]. 1 = absolutely sure the resolution preserves
|
|
61
|
+
- "confidence": a number in [0, 1]. For "chunks" responses, put confidence on EACH chunk (see below) — do NOT include a top-level "confidence". For a file-level "strategy" response, give one top-level "confidence" for the whole-file decision. 1 = absolutely sure the resolution preserves intent. 0.5 = significant uncertainty. Lower if the conflict is ambiguous, semantics are unclear, or you had to guess.
|
|
63
62
|
- "followUpInstructions": optional array of strings. Each string is a concrete TODO for Phase 2 — specify the location (line number or context) and the action. Use when the chosen base strategy needs targeted adjustments that Phase 2 should apply. Omit when the chunks or file-level strategy fully express the resolution without further changes needed.
|
|
64
63
|
- Good: "Insert the analytics import from theirs (line 12) below the existing logger import (line 7)."
|
|
65
64
|
- Bad: "Make sure both features are preserved somewhere appropriate."
|
|
66
65
|
|
|
67
|
-
Confidence scoring guide:
|
|
66
|
+
Confidence scoring guide (applies to each chunk's "confidence", and to a file-level "strategy" confidence):
|
|
68
67
|
- 1.0: Trivial — only one side changed, or changes are in completely independent regions
|
|
69
68
|
- 0.8–0.9: Clear intent — both sides changed but intent is unambiguous from context
|
|
70
69
|
- 0.6–0.7: Reasonable guess — requires interpretation of intent, some risk
|
|
@@ -72,7 +71,7 @@ Confidence scoring guide:
|
|
|
72
71
|
- Cap whole-file strategies at 0.8 when both sides have changes. If one side is identical to base, 1.0 is appropriate.
|
|
73
72
|
|
|
74
73
|
- "description": one or two sentences summarising what the chunks/strategy decided and why. Do NOT describe future edits here — those belong in followUpInstructions.
|
|
75
|
-
- "chunks": array of resolved chunks, one per conflict marker.
|
|
74
|
+
- "chunks": array of resolved chunks, one per conflict marker. Each chunk MUST include "confidence" (a number in [0, 1] for THAT marker's decision) and MAY include "reason" (one short clause explaining the choice; omit it for trivial picks).
|
|
76
75
|
|
|
77
76
|
Resolution guidelines:
|
|
78
77
|
- Choose the strategy that produces the cleanest result. Combining sides is NOT inherently better — if combining requires copying large blocks of code that already exist before <<<<<<< or after >>>>>>>, prefer file-level + followUpInstructions instead.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{checkResidualMarkers as e}from"../ports/verify.js";import{resolveChunkContent as t}from"../resolution/chunk-apply.js";import{createResolution as n}from"../resolution/create-resolution.js";import{mergeUsages as r}from"../resolution/metrics.js";import{runResolverLoop as i}from"./loop.js";import{runRefinePass as a}from"./refine-pass.js";import{buildThreeWayDiff as o}from"./three-way-diff.js";const s={refineNoAction:.6,refineApplied:.9};async function c(c,l,u){let
|
|
1
|
+
import{checkResidualMarkers as e}from"../ports/verify.js";import{resolveChunkContent as t}from"../resolution/chunk-apply.js";import{createResolution as n}from"../resolution/create-resolution.js";import{mergeUsages as r}from"../resolution/metrics.js";import{runResolverLoop as i}from"./loop.js";import{runRefinePass as a}from"./refine-pass.js";import{buildThreeWayDiff as o}from"./three-way-diff.js";const s={refineNoAction:.6,refineApplied:.9};async function c(c,l,u){let d=l.threeWayDiff||!l.refs?l:{...l,threeWayDiff:await o(c.filePath,l.refs,{git:u.git,signal:u.signal})},{chunks:f,strategy:p,confidence:m,description:h,metrics:g,followUpInstructions:_,issues:v}=await i(c,d,{model:u.model,git:u.git,config:u.config,onProgress:u.onProgress,signal:u.signal,verifier:u.verifier}),y=n(c,f,{confidence:m,description:h,metrics:g,...p?{fileStrategy:p}:{},...d.threeWayDiff?{threeWayDiff:d.threeWayDiff}:{}});v&&v.length>0&&(y={...y,issues:v});let b=_&&_.length>0;if(c.type===`text`&&b){u.onProgress?.({type:`refine:started`,filePath:c.filePath});let n=0;try{let i=await a(y.content,y.description,_,{model:u.model,signal:u.signal,temperature:u.config?.temperature,refineMaxSteps:u.config?.refineMaxSteps,onProgress:u.onProgress,filePath:c.filePath});if(i.edits.length>0){let t=e(c.filePath,i.content);if(t.valid)y={...y,content:i.content,edits:i.edits,strategy:`ai`},n=i.edits.length;else{let e=(t.issues??[]).map(e=>e.message).join(` `);u.onProgress?.({type:`refine:discarded`,filePath:c.filePath,reason:`Edits introduced conflict markers: ${e}`})}}if(n>0){y={...y,confidence:Math.min(y.confidence,s.refineApplied)};let e=[];for(let n=0;n<c.markers.length;n++){let r=c.markers[n];if(!r||r.userResolution==null)continue;let a=t({markerIndex:n,...r.userResolution},r);a!==``&&(i.content.includes(a)||e.push(n))}e.length>0&&(u.onProgress?.({type:`refine:touched-user-region`,filePath:c.filePath,markerIndexes:e}),y={...y,note:`Refine modified user-resolved region(s): marker ${e.join(`, `)}.`})}else i.edits.length===0&&i.verifies>0||(y={...y,confidence:Math.min(y.confidence,s.refineNoAction)});if(i.usage){let e=r(y.metrics??{inputTokens:0,outputTokens:0},i.usage);y={...y,metrics:{...y.metrics,...e}}}u.onProgress?.({type:`refine:completed`,filePath:c.filePath,appliedCount:n})}catch(e){let t=e instanceof Error?e.message:String(e);throw u.onProgress?.({type:`refine:failed`,filePath:c.filePath,reason:t}),e}}else u.onProgress?.({type:`refine:skipped`,filePath:c.filePath,reason:`no-follow-ups`});return u.onProgress?.({type:`resolver:completed`,filePath:c.filePath,stepCount:g.stepCount??0}),y}export{c as resolveConflict};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AIError as e}from"../errors/ai.js";import{unmergedEntries as t}from"../git/port-dispatch.js";import{extractConflict as n}from"../extraction/extract.js";import{createResolution as r}from"../resolution/create-resolution.js";import{takeOurs as i,takeTheirs as a}from"../resolution/take-strategy.js";import{resolveConflict as o}from"./resolve-conflict.js";import s from"picomatch";function c(e){let t=e.map(e=>{let t=(Array.isArray(e.match)?e.match:[e.match]).map(e=>s(e,{basename:!e.includes(`/`)}));return{isMatch:e=>t.some(t=>t(e)),strategy:e.strategy}});return e=>{for(let{isMatch:n,strategy:r}of t)if(n(e))return r}}async function l(e,o){let s=e.config?.rules??[],l=e.config?.defaultStrategy??`ai`,f=c(s),p=await t(e.git,{signal:e.signal}),m={...o??{}},h=[],g=[],_=[];for(let t of p){if(e.signal?.aborted)break;let o=f(t.path)??l;if(o===`skip`){_.push({filePath:t.path,reason:`excluded`}),e.onProgress?.({type:`conflict:excluded`,filePath:t.path});continue}let s;try{s=await n(t.path,{git:e.git,signal:e.signal},t.reason)}catch(e){g.push({filePath:t.path,error:d(e)});continue}if(!s){_.push({filePath:t.path,reason:`no-markers`}),e.onProgress?.({type:`conflict:skipped`,filePath:t.path,reason:`no-markers`,entryReason:t.reason});continue}e.onProgress?.({type:`conflict:found`,filePath:s.filePath,conflictType:s.type,markerCount:s.markers.length});let c=null;c=o===`take-ours`?i(s):o===`take-theirs`?a(s):o===`deleted`?r(s,[],{confidence:1,fileStrategy:`deleted`}):await u(s,m,e,g),c&&(m.previousResolutions=[...m.previousResolutions??[],c],h.push(c),e.onProgress?.({type:`resolution:applied`,filePath:c.filePath,strategy:c.strategy,confidence:c.confidence,description:c.description}))}return{resolutions:h,errors:g,skipped:_}}async function u(t,n,r,s){try{return await o(t,n,{model:r.model,git:r.git,config:r.config,onProgress:r.onProgress,signal:r.signal,verifier:r.verifier})}catch(n){let o=d(n),c=r.config?.fallbackStrategy;return c?(r.onProgress?.({type:`resolution:fallback`,filePath:t.filePath,error:o,cause:o instanceof e?o.code:void 0}),c===`take-ours`?i(t):a(t)):(s.push({filePath:t.filePath,error:o}),r.onProgress?.({type:`resolution:failed`,filePath:t.filePath,error:o}),null)}}function d(e){return e instanceof Error?e:Error(String(e))}export{l as resolveConflicts};
|
|
1
|
+
import{AIError as e}from"../errors/ai.js";import{unmergedEntries as t}from"../git/port-dispatch.js";import{extractConflict as n}from"../extraction/extract.js";import{createResolution as r}from"../resolution/create-resolution.js";import{takeOurs as i,takeTheirs as a}from"../resolution/take-strategy.js";import{resolveConflict as o}from"./resolve-conflict.js";import s from"picomatch";function c(e){let t=e.map(e=>{let t=(Array.isArray(e.match)?e.match:[e.match]).map(e=>s(e,{basename:!e.includes(`/`)}));return{isMatch:e=>t.some(t=>t(e)),strategy:e.strategy}});return e=>{for(let{isMatch:n,strategy:r}of t)if(n(e))return r}}async function l(e,o){let s=e.config?.rules??[],l=e.config?.defaultStrategy??`ai`,f=c(s),p=await t(e.git,{signal:e.signal}),m={...o??{}},h=[],g=[],_=[];for(let t of p){if(e.signal?.aborted)break;let o=f(t.path)??l;if(o===`skip`){_.push({filePath:t.path,reason:`excluded`}),e.onProgress?.({type:`conflict:excluded`,filePath:t.path});continue}let s;try{s=await n(t.path,{git:e.git,signal:e.signal},t.reason)}catch(e){g.push({filePath:t.path,error:d(e)});continue}if(!s){_.push({filePath:t.path,reason:`no-markers`}),e.onProgress?.({type:`conflict:skipped`,filePath:t.path,reason:`no-markers`,entryReason:t.reason});continue}e.onProgress?.({type:`conflict:found`,filePath:s.filePath,conflictType:s.type,markerCount:s.markers.length});let c=null;c=o===`take-ours`?i(s):o===`take-theirs`?a(s):o===`deleted`?r(s,[],{confidence:1,fileStrategy:`deleted`}):await u(s,m,e,g),c&&(m.previousResolutions=[...m.previousResolutions??[],c],h.push(c),e.onProgress?.({type:`resolution:applied`,filePath:c.filePath,strategy:c.strategy,confidence:c.confidence,description:c.description}))}return{resolutions:h,errors:g,skipped:_}}async function u(t,n,r,s){try{return await o(t,n,{model:r.model,git:r.git,config:r.config,onProgress:r.onProgress,signal:r.signal,verifier:r.verifier})}catch(n){let o=d(n),c=r.config?.fallbackStrategy;return c?(r.onProgress?.({type:`resolution:fallback`,filePath:t.filePath,error:o,cause:o instanceof e?o.code:void 0}),{...c===`take-ours`?i(t):a(t),confidence:0}):(s.push({filePath:t.filePath,error:o}),r.onProgress?.({type:`resolution:failed`,filePath:t.filePath,error:o}),null)}}function d(e){return e instanceof Error?e:Error(String(e))}export{l as resolveConflicts};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ConflictGitPort } from "../ports/git.js";
|
|
2
|
-
import { ResolutionRefs, ThreeWayDiff } from "../types/resolution.js";
|
|
2
|
+
import { DiffStats, ResolutionRefs, ThreeWayDiff } from "../types/resolution.js";
|
|
3
3
|
|
|
4
4
|
//#region src/resolver/three-way-diff.d.ts
|
|
5
5
|
declare function buildThreeWayDiff(filePath: string, refs: ResolutionRefs, deps: {
|
|
6
6
|
git: ConflictGitPort;
|
|
7
7
|
signal?: AbortSignal;
|
|
8
8
|
}): Promise<ThreeWayDiff>;
|
|
9
|
+
/** Counts +/- content lines, ignoring the `+++`/`---` file headers. */
|
|
10
|
+
declare function parseDiffStats(unifiedDiff: string): DiffStats;
|
|
9
11
|
//#endregion
|
|
10
|
-
export { buildThreeWayDiff };
|
|
12
|
+
export { buildThreeWayDiff, parseDiffStats };
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import{diff as e,mergeBase as t}from"../git/port-dispatch.js";async function n(n,
|
|
1
|
+
import{diff as e,mergeBase as t}from"../git/port-dispatch.js";async function n(n,i,a){let o=i.base??await t(a.git,i.ours,i.theirs,{signal:a.signal}),[s,c]=await Promise.all([e(a.git,o,i.ours,{path:n,signal:a.signal}),e(a.git,o,i.theirs,{path:n,signal:a.signal})]);return{oursDiff:s,theirsDiff:c,oursStats:r(s),theirsStats:r(c)}}function r(e){let t=0,n=0;for(let r of e.split(`
|
|
2
|
+
`))r.startsWith(`+++ `)||r.startsWith(`--- `)||(r.startsWith(`+`)?t++:r.startsWith(`-`)&&n++);return{insertions:t,deletions:n}}export{n as buildThreeWayDiff,r as parseDiffStats};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IntraFileEditOp } from "./hints.js";
|
|
2
2
|
import { Issue } from "./issue.js";
|
|
3
|
+
import { ConflictType } from "./conflict.js";
|
|
3
4
|
import { AiTokenUsage } from "@gitkraken/shared-tools";
|
|
4
5
|
|
|
5
6
|
//#region src/types/resolution.d.ts
|
|
@@ -10,8 +11,13 @@ type ChunkResolution = {
|
|
|
10
11
|
strategy: 'merged';
|
|
11
12
|
content: string;
|
|
12
13
|
};
|
|
14
|
+
/** A resolved conflict marker. `confidence`/`reason` are present for AI-decided chunks — the model
|
|
15
|
+
* reports them per marker — and absent for synthetic chunks (file-level strategy expansion,
|
|
16
|
+
* user-finalized markers, whole-file take-ours/take-theirs). */
|
|
13
17
|
type ResolvedChunk = ChunkResolution & {
|
|
14
18
|
markerIndex: number;
|
|
19
|
+
confidence?: number;
|
|
20
|
+
reason?: string;
|
|
15
21
|
};
|
|
16
22
|
interface Resolution {
|
|
17
23
|
filePath: string;
|
|
@@ -24,15 +30,39 @@ interface Resolution {
|
|
|
24
30
|
chunks?: ResolvedChunk[];
|
|
25
31
|
edits?: IntraFileEditOp[];
|
|
26
32
|
metrics?: ResolutionMetrics;
|
|
33
|
+
rawSignals?: ResolutionRawSignals;
|
|
34
|
+
}
|
|
35
|
+
/** Policy-free measurements of a conflict and its resolution; consumers feed these into their own assessment. */
|
|
36
|
+
interface ResolutionRawSignals {
|
|
37
|
+
conflictType: ConflictType;
|
|
38
|
+
maxSideLines: number;
|
|
39
|
+
/** Changed lines per side and combined; 0 when no three-way diff was available. */
|
|
40
|
+
oursDiffLines: number;
|
|
41
|
+
theirsDiffLines: number;
|
|
42
|
+
combinedDiffLines: number;
|
|
43
|
+
markersCount: number;
|
|
44
|
+
/** Only counted when the style provides base on some marker (diff3); 0 otherwise. */
|
|
45
|
+
markersWithMissingBase: number;
|
|
46
|
+
/** Markers with an empty ours or theirs side. */
|
|
47
|
+
emptySideMarkersCount: number;
|
|
48
|
+
stepCount: number;
|
|
49
|
+
/** Re-prompts after parse/validation/verification failures, excluding tool-call steps. */
|
|
50
|
+
retryCount: number;
|
|
27
51
|
}
|
|
28
52
|
interface ResolutionRefs {
|
|
29
53
|
ours: string;
|
|
30
54
|
theirs: string;
|
|
31
55
|
base?: string;
|
|
32
56
|
}
|
|
57
|
+
interface DiffStats {
|
|
58
|
+
insertions: number;
|
|
59
|
+
deletions: number;
|
|
60
|
+
}
|
|
33
61
|
interface ThreeWayDiff {
|
|
34
62
|
oursDiff: string;
|
|
35
63
|
theirsDiff: string;
|
|
64
|
+
oursStats?: DiffStats;
|
|
65
|
+
theirsStats?: DiffStats;
|
|
36
66
|
}
|
|
37
67
|
interface ResolutionContext {
|
|
38
68
|
commitMessage?: string;
|
|
@@ -50,8 +80,10 @@ interface ResolutionContext {
|
|
|
50
80
|
interface ResolutionMetrics extends AiTokenUsage {
|
|
51
81
|
stepCount?: number;
|
|
52
82
|
toolCallCount?: number;
|
|
83
|
+
/** Re-prompts after parse/validation/verification failures, excluding tool-call steps. */
|
|
84
|
+
retryCount?: number;
|
|
53
85
|
durationMs?: number;
|
|
54
86
|
metadata?: Record<string, unknown>;
|
|
55
87
|
}
|
|
56
88
|
//#endregion
|
|
57
|
-
export { ChunkResolution, Resolution, ResolutionContext, ResolutionMetrics, ResolutionRefs, ResolutionStrategy, ResolvedChunk, ThreeWayDiff };
|
|
89
|
+
export { ChunkResolution, DiffStats, Resolution, ResolutionContext, ResolutionMetrics, ResolutionRawSignals, ResolutionRefs, ResolutionStrategy, ResolvedChunk, ThreeWayDiff };
|