@gitkraken/conflict-tools 0.2.1 → 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 CHANGED
@@ -7,6 +7,10 @@ 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
+
10
14
  ## [0.2.1] - 2026-06-09
11
15
 
12
16
  ### Added
@@ -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=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 "confidence" (0-1), "description" (1-2 sentences), and either "strategy" ("ours", "theirs", or "deleted") for a file-level decision, or "chunks" (array of { markerIndex, strategy, content? }).`});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 "confidence" (0-1), "description" (1-2 sentences), and either "strategy" ("ours", "theirs", or "deleted") for a file-level decision, or "chunks" (array of { markerIndex, strategy, content? }).`});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
+ 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,n){let r=t(e);if(r===null)throw Error(`No valid JSON object found in model response.`);let i;try{i=JSON.parse(r)}catch{throw Error(`Failed to parse JSON from model response.`)}if(typeof i!=`object`||!i||Array.isArray(i))throw Error(`Model response must be a JSON object.`);let a=i,o=a.confidence;if(typeof o!=`number`||!Number.isFinite(o)||o<0||o>1)throw Error(`Field "confidence" must be a number in [0, 1].`);let s=a.description;if(typeof s!=`string`||s.trim().length===0)throw Error(`Field "description" must be a non-empty string.`);let c;if(Array.isArray(a.followUpInstructions)){let e=a.followUpInstructions.filter(e=>typeof e==`string`).map(e=>e.trim()).filter(e=>e.length>0);e.length>0&&(c=e)}if(a.strategy!==void 0){let e=a.strategy;if(e!==`ours`&&e!==`theirs`&&e!==`deleted`)throw Error(`Field "strategy" must be "ours", "theirs", or "deleted".`);return{confidence:o,description:s.trim(),chunks:[],strategy:e,...c?{followUpInstructions:c}:{}}}let l=a.chunks;if(!Array.isArray(l))throw Error(`Field "chunks" must be an array. Each chunk needs { markerIndex, strategy, content? }.`);let u=[];for(let e of l){if(typeof e!=`object`||!e)throw Error(`Each chunk must be an object.`);let t=e,r=t.markerIndex,i=t.strategy;if(typeof r!=`number`||!Number.isInteger(r))throw Error(`Each chunk must have an integer markerIndex.`);if(n===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(r<0||r>=n)throw Error(`markerIndex ${r} is out of range. Valid range: 0..${n-1}.`);if(i===`ours`||i===`theirs`)u.push({markerIndex:r,strategy:i});else if(i===`merged`){let e=t.content;if(typeof e!=`string`)throw Error(`Chunk at markerIndex ${r} with strategy "merged" must have a string "content".`);u.push({markerIndex:r,strategy:i,content:e})}else throw Error(`Unknown strategy "${String(i)}" at markerIndex ${r}. Valid strategies: "ours", "theirs", "merged".`)}return{confidence:o,description:s.trim(),chunks:u,...c?{followUpInstructions:c}:{}}}function t(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};
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};
@@ -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
- "confidence": 0.85,
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 both sides' intent. 0.5 = significant uncertainty. Lower if the conflict is ambiguous, semantics are unclear, or you had to guess.
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{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};
@@ -11,8 +11,13 @@ type ChunkResolution = {
11
11
  strategy: 'merged';
12
12
  content: string;
13
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). */
14
17
  type ResolvedChunk = ChunkResolution & {
15
18
  markerIndex: number;
19
+ confidence?: number;
20
+ reason?: string;
16
21
  };
17
22
  interface Resolution {
18
23
  filePath: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitkraken/conflict-tools",
3
- "version": "0.2.1",
3
+ "version": "0.2.2-beta.1",
4
4
  "description": "AI-powered git conflict resolution.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Axosoft, LLC dba GitKraken",