@gitkraken/conflict-tools 0.2.4-beta.6 → 0.2.4-beta.8

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
@@ -15,6 +15,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
15
15
  - Binding-aware broken-reference detection. `ParsedFile` gains an optional `bindings` (names bound at any scope, module-level and nested), and reference-integrity now treats a name as "resolvable before the merge" if a parent resolved it as a reference **or** defined/bound it. This catches a reference broken by removing a nested binding (e.g. a `const` used by a closure elsewhere in the same file): the dangling use was never a resolved reference — because it was locally bound — so the before/after reference diff alone could not see the regression. Backends that only surface top-level `definitions` can omit `bindings`; they simply do not gain the nested-binding case.
16
16
  - Operation-level assessment: `buildOperationDigest`/`summarizeOperation` now fold `StepResult.detection` into the rollup. A file with an error-severity finding (broken-reference, dropped-side, syntax-error, duplicate-definition) is added to `needsReview` (`reason: 'finding'`) regardless of confidence — even a file with no resolution of its own; `OperationDigest.detectionComplete` reports whether every detection check ran (so empty findings on a skipped check are not read as clean); and a derived `OperationDigest.verdict` (`'clean' | 'needs-review' | 'blocked'`) gives consumers an honest gate signal. `ReviewItem` gains `reason` and optional `findingCategories`, and its `confidence` is now optional.
17
17
 
18
+ ### Changed
19
+
20
+ - **Breaking**: consumer validation moved to a `Validator` port — `validate(sources: SourceOverlay): Promise<ValidatorFinding[]>` (replaces the per-file `ResolutionVerifier.verify`). It runs post-refine, non-blocking, over an overlay (one file for per-file validation, or the touched set for cross-file); its findings fold into the operation's detection findings and verdict. `Resolution.findings` replaces `Resolution.issues`. `Finding.category` is now an open `string` so a consumer validator can contribute its own categories — the package still emits the well-known `FindingCategory` values. `defaultVerifier` / `VerificationResult` are no longer exported (`checkResidualMarkers` stays internal, the built-in marker gate).
21
+ - **Breaking**: the resolver's in-loop blocking check is now **residual conflict markers only** (built-in, language-agnostic). The `ResolverDeps.verifier` port no longer gates the resolve loop or triggers re-resolution — it runs once **after** refine, **non-blocking**, and its issues are attached to `Resolution.issues` (advisory) and reported. This stops the resolver from burning its step budget on non-marker validation (which was exhausting into a whole-file `take-theirs` fallback), and puts consumer validation in its intended post-refine, report-only role. `ResolverLoopResult` no longer carries `issues`.
22
+
18
23
  ### Fixed
19
24
 
20
25
  - Reference-integrity detection no longer reads as "clean" when the parent snapshots could not be loaded. A git port that returns empty content for an unreadable parent (e.g. a `showFile` that swallows read errors into `""` instead of throwing) previously produced empty findings with `referenceIntegrity: 'ran'`, masking a total read failure as a clean result. `buildSnapshots` now ignores empty parent content; `DetectionReport.scope` reports `readable` (parents actually loaded) alongside `analyzed` (files in scope); and `referenceIntegrity` reports a new `'skipped-unreadable-source'` status when nothing in scope could be read — which flips `OperationDigest.detectionComplete` to `false` and the verdict to `needs-review` instead of `clean`.
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { AIError, AIErrorCode } from "./errors/ai.js";
2
2
  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
+ import { CheckStatus, DetectionReport, Finding, FindingCategory, FindingTier } from "./types/findings.js";
4
5
  import { IntraFileEditOp } from "./types/hints.js";
5
- import { Issue } from "./types/issue.js";
6
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";
@@ -10,8 +10,8 @@ import { checkoutFile, removeFile, show } from "./git/port-dispatch.js";
10
10
  import { ToolCall, ToolDefinition, ToolResult } from "./types/tool.js";
11
11
  import { ConflictModelMessage, ConflictModelParams, ConflictModelPort, ConflictModelResult, ResolverConfig } from "./ports/model.js";
12
12
  import { ParsedFile, ParserPort, SourceOverlay, SymbolDefinition, SymbolReference, SyntaxIssue } from "./ports/parser.js";
13
- import { ResolutionVerifier, VerificationResult, defaultVerifier } from "./ports/verify.js";
14
- import { CheckStatus, DetectionReport, Finding, FindingCategory, FindingTier } from "./types/findings.js";
13
+ import { Issue } from "./types/issue.js";
14
+ import { Validator, ValidatorFinding } from "./ports/verify.js";
15
15
  import { DetectDeps, detect } from "./reference-integrity/detect.js";
16
16
  import { createResolution } from "./resolution/create-resolution.js";
17
17
  import { takeOurs, takeTheirs } from "./resolution/take-strategy.js";
@@ -22,4 +22,4 @@ import { FileRule, StepConfig, StepResult } from "./types/step.js";
22
22
  import { resolveConflicts } from "./resolver/resolve-conflicts.js";
23
23
  import { OperationDigest, OperationSummary, OperationVerdict, ReviewItem } from "./types/summary.js";
24
24
  import { SummarizeOperationOptions, buildOperationDigest, summarizeOperation } from "./resolver/summarize-operation.js";
25
- export { AIError, type AIErrorCode, type BlameOptions, type CheckStatus, 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 DetectDeps, type DetectionReport, type DiffStats, type FileRule, type Finding, type FindingCategory, type FindingTier, type GrepOptions, type IntraFileEditOp, type Issue, type LogOptions, type OpOptions, type OperationDigest, type OperationSummary, type OperationVerdict, type ParsedFile, type ParserPort, type Resolution, type ResolutionContext, type ResolutionMetrics, type ResolutionRawSignals, type ResolutionRefs, type ResolutionStrategy, type ResolutionVerifier, type ResolvedChunk, type ResolverConfig, type ReviewItem, type ShowFileOptions, type ShowOptions, type SourceOverlay, type StepConfig, type StepResult, type SummarizeOperationOptions, type SymbolDefinition, type SymbolReference, type SyntaxIssue, type ThreeWayDiff, type ToolCall, type ToolDefinition, type ToolResult, type UnmergedEntry, type UnmergedReason, type VerificationResult, applyResolutions, buildOperationDigest, checkoutFile, createResolution, defaultVerifier, detect, extractConflict, removeFile, resolveConflict, resolveConflicts, show, summarizeOperation, takeOurs, takeTheirs };
25
+ export { AIError, type AIErrorCode, type BlameOptions, type CheckStatus, 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 DetectDeps, type DetectionReport, type DiffStats, type FileRule, type Finding, type FindingCategory, type FindingTier, type GrepOptions, type IntraFileEditOp, type Issue, type LogOptions, type OpOptions, type OperationDigest, type OperationSummary, type OperationVerdict, type ParsedFile, type ParserPort, type Resolution, type ResolutionContext, type ResolutionMetrics, type ResolutionRawSignals, type ResolutionRefs, type ResolutionStrategy, type ResolvedChunk, type ResolverConfig, type ReviewItem, type ShowFileOptions, type ShowOptions, type SourceOverlay, type StepConfig, type StepResult, type SummarizeOperationOptions, type SymbolDefinition, type SymbolReference, type SyntaxIssue, type ThreeWayDiff, type ToolCall, type ToolDefinition, type ToolResult, type UnmergedEntry, type UnmergedReason, type Validator, type ValidatorFinding, applyResolutions, buildOperationDigest, checkoutFile, createResolution, detect, extractConflict, removeFile, resolveConflict, resolveConflicts, show, summarizeOperation, takeOurs, takeTheirs };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{AIError as e}from"./errors/ai.js";import{ConflictError as t}from"./errors/conflict.js";import{ConflictGitPortMissingOpError as n}from"./ports/git.js";import{checkoutFile as r,removeFile as i,show as a}from"./git/port-dispatch.js";import{extractConflict as o}from"./extraction/extract.js";import{defaultVerifier as s}from"./ports/verify.js";import{detect as c}from"./reference-integrity/detect.js";import{createResolution as l}from"./resolution/create-resolution.js";import{takeOurs as u,takeTheirs as d}from"./resolution/take-strategy.js";import{applyResolutions as f}from"./resolver/apply-resolutions.js";import{resolveConflict as p}from"./resolver/resolve-conflict.js";import{resolveConflicts as m}from"./resolver/resolve-conflicts.js";import{buildOperationDigest as h,summarizeOperation as g}from"./resolver/summarize-operation.js";export{e as AIError,t as ConflictError,n as ConflictGitPortMissingOpError,f as applyResolutions,h as buildOperationDigest,r as checkoutFile,l as createResolution,s as defaultVerifier,c as detect,o as extractConflict,i as removeFile,p as resolveConflict,m as resolveConflicts,a as show,g as summarizeOperation,u as takeOurs,d as takeTheirs};
1
+ import{AIError as e}from"./errors/ai.js";import{ConflictError as t}from"./errors/conflict.js";import{ConflictGitPortMissingOpError as n}from"./ports/git.js";import{checkoutFile as r,removeFile as i,show as a}from"./git/port-dispatch.js";import{extractConflict as o}from"./extraction/extract.js";import{detect as s}from"./reference-integrity/detect.js";import{createResolution as c}from"./resolution/create-resolution.js";import{takeOurs as l,takeTheirs as u}from"./resolution/take-strategy.js";import{applyResolutions as d}from"./resolver/apply-resolutions.js";import{resolveConflict as f}from"./resolver/resolve-conflict.js";import{resolveConflicts as p}from"./resolver/resolve-conflicts.js";import{buildOperationDigest as m,summarizeOperation as h}from"./resolver/summarize-operation.js";export{e as AIError,t as ConflictError,n as ConflictGitPortMissingOpError,d as applyResolutions,m as buildOperationDigest,r as checkoutFile,c as createResolution,s as detect,o as extractConflict,i as removeFile,f as resolveConflict,p as resolveConflicts,a as show,h as summarizeOperation,l as takeOurs,u as takeTheirs};
@@ -1,3 +1,4 @@
1
+ import { SourceOverlay } from "./parser.js";
1
2
  import { Issue } from "../types/issue.js";
2
3
 
3
4
  //#region src/ports/verify.d.ts
@@ -5,10 +6,25 @@ interface VerificationResult {
5
6
  valid: boolean;
6
7
  issues?: Issue[];
7
8
  }
8
- interface ResolutionVerifier {
9
- verify(filePath: string, content: string): Promise<VerificationResult>;
9
+ interface ValidatorFinding {
10
+ /** Open category string set by the consumer (e.g. `'type-error'`, `'lint:no-console'`). */
11
+ category: string;
12
+ severity: 'error' | 'warning';
13
+ file: string;
14
+ /** 1-indexed. */
15
+ line?: number;
16
+ message: string;
17
+ }
18
+ /**
19
+ * The consumer's validation port (RFC stages 5/7). Plug in any checks — syntax, real import
20
+ * resolution, types, lint, project rules. **Non-blocking**: runs post-refine over an overlay (one file
21
+ * for per-file validation, or the whole touched set for cross-file); its findings are advisory (folded
22
+ * into the operation's findings and verdict), never a re-resolve. The only blocking gate is residual
23
+ * conflict markers, checked built-in by the resolver ({@link checkResidualMarkers}) — not this port.
24
+ */
25
+ interface Validator {
26
+ validate(sources: SourceOverlay): Promise<ValidatorFinding[]>;
10
27
  }
11
28
  declare function checkResidualMarkers(_filePath: string, content: string): VerificationResult;
12
- declare const defaultVerifier: ResolutionVerifier;
13
29
  //#endregion
14
- export { ResolutionVerifier, VerificationResult, checkResidualMarkers, defaultVerifier };
30
+ export { Validator, ValidatorFinding, VerificationResult, checkResidualMarkers };
@@ -1,2 +1,2 @@
1
1
  const e=[/^<{7}(\s|$)/,/^\|{7}(\s|$)/,/^={7}(\s|$)/,/^>{7}(\s|$)/];function t(t,n){let r=n.split(`
2
- `),i=[];for(let t=0;t<r.length;t++){let n=r[t]??``;for(let r of e)if(r.test(n)){i.push(t+1);break}}return i.length===0?{valid:!0}:{valid:!1,issues:[{severity:`error`,message:`Residual conflict markers found at line(s) ${i.join(`, `)}.`}]}}const n={async verify(e,n){return t(e,n)}};export{t as checkResidualMarkers,n as defaultVerifier};
2
+ `),i=[];for(let t=0;t<r.length;t++){let n=r[t]??``;for(let r of e)if(r.test(n)){i.push(t+1);break}}return i.length===0?{valid:!0}:{valid:!1,issues:[{severity:`error`,message:`Residual conflict markers found at line(s) ${i.join(`, `)}.`}]}}export{t as checkResidualMarkers};
@@ -1,7 +1,7 @@
1
1
  import { ConflictGitPort } from "../ports/git.js";
2
+ import { DetectionReport } from "../types/findings.js";
2
3
  import { Resolution, ResolutionContext } from "../types/resolution.js";
3
4
  import { ParserPort } from "../ports/parser.js";
4
- import { DetectionReport } from "../types/findings.js";
5
5
 
6
6
  //#region src/reference-integrity/detect.d.ts
7
7
  interface DetectDeps {
@@ -1,7 +1,7 @@
1
1
  import { ConflictGitPort } from "../ports/git.js";
2
2
  import { ConflictModelPort, ResolverConfig } from "../ports/model.js";
3
3
  import { ParserPort } from "../ports/parser.js";
4
- import { ResolutionVerifier } from "../ports/verify.js";
4
+ import { Validator } from "../ports/verify.js";
5
5
  import { ConflictProgressEvent } from "../types/events.js";
6
6
 
7
7
  //#region src/resolver/deps.d.ts
@@ -11,7 +11,7 @@ interface ResolverDeps<C extends ResolverConfig = ResolverConfig> {
11
11
  config?: C;
12
12
  onProgress?: (event: ConflictProgressEvent) => void;
13
13
  signal?: AbortSignal;
14
- verifier?: ResolutionVerifier;
14
+ validator?: Validator;
15
15
  parser?: ParserPort;
16
16
  }
17
17
  //#endregion
@@ -1,4 +1,3 @@
1
- import { Issue } from "../types/issue.js";
2
1
  import { ResolutionContext, ResolutionMetrics, ResolvedChunk } from "../types/resolution.js";
3
2
  import { Conflict } from "../types/conflict.js";
4
3
  import { ResolverDeps } from "./deps.js";
@@ -11,7 +10,6 @@ interface ResolverLoopResult {
11
10
  description: string;
12
11
  metrics: ResolutionMetrics;
13
12
  followUpInstructions?: string[];
14
- issues?: Issue[];
15
13
  }
16
14
  declare function runResolverLoop(conflict: Conflict, context: ResolutionContext, deps: ResolverDeps): Promise<ResolverLoopResult>;
17
15
  //#endregion
@@ -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,d){let f=d.config?.maxSteps??15,p=d.config?.temperature??0,{definitions:m,execute:h}=a(d.git),{system:g,userMessage:_}=i(n,s,{maxSteps:f}),v=[{role:`user`,content:_}],y=d.signal?{signal:d.signal}:void 0,b=0,x=0,S=0,C=[],w=n.markers.some(e=>e.userResolution!=null),T;for(;b<f;){b++;let e=await d.model.generate({system:g,messages:[...v],tools:m,temperature:p,signal:d.signal});if(e.usage&&(C.push(e.usage),d.onProgress?.({type:`resolver:step-usage`,filePath:n.filePath,stepNumber:b,usage:e.usage})),e.toolCalls&&e.toolCalls.length>0){v.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;d.onProgress?.({type:`resolver:tool-call`,filePath:n.filePath,tool:t.name,args:t.args,stepNumber:b,reason:e}),x++;let r=await h(t,y);d.onProgress?.({type:`resolver:tool-result`,filePath:n.filePath,tool:t.name,stepNumber:b,content:r.content}),v.push({role:`tool`,toolCallId:r.toolCallId,toolName:t.name,content:r.content})}continue}let i=e.text??``;if(!i.trim()){S++,v.push({role:`assistant`,content:``}),v.push({role:`user`,content:`[Step ${b}/${f}] 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){S++;let t=e instanceof Error?e.message:String(e);v.push({role:`assistant`,content:i}),v.push({role:`user`,content:`[Step ${b}/${f}] 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`){S++,v.push({role:`assistant`,content:i}),v.push({role:`user`,content:`[Step ${b}/${f}] 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`)&&w)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 d.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:u(C,b,x,S),...a.followUpInstructions?{followUpInstructions:a.followUpInstructions}:{}}}if(n.markers.length===0&&!a.strategy){S++,v.push({role:`assistant`,content:i}),v.push({role:`user`,content:`[Step ${b}/${f}] 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),_=o(s,n);if(!_.valid){S++;let e=(_.issues??[]).map(e=>e.message).join(` `);v.push({role:`assistant`,content:i}),v.push({role:`user`,content:`[Step ${b}/${f}] 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 E=l(s,n);if(d.verifier&&n.type===`text`){let e=t(n.rawContent,n.markers,E),r=await d.verifier.verify(n.filePath,e);if(!r.valid){S++;let e=(r.issues??[]).filter(e=>e.severity===`error`).map(e=>e.message).join(` `)||`Content did not pass verification.`;v.push({role:`assistant`,content:i}),v.push({role:`user`,content:`[Step ${b}/${f}] Verification failed: ${e}\nPlease fix and respond with corrected JSON.`});continue}let a=(r.issues??[]).filter(e=>e.severity===`warning`);a.length>0&&(T=a,d.onProgress?.({type:`verification:issues`,filePath:n.filePath,issues:a}))}let D=c(s,a.confidence);return d.onProgress?.({type:`resolver:response`,filePath:n.filePath,chunks:a.chunks,confidence:D,description:a.description,...a.followUpInstructions?{followUpInstructions:a.followUpInstructions}:{}}),{chunks:E,confidence:D,description:a.description,metrics:u(C,b,x,S),...a.followUpInstructions?{followUpInstructions:a.followUpInstructions}:{},...T&&T.length>0?{issues:T}:{}}}throw new e(`VALIDATION_EXHAUSTED`,`Resolver exhausted ${f} steps without producing a valid resolution for ${n.filePath}.`)}function c(e,t){let n=e.map(e=>e.confidence).filter(e=>e!==void 0);return n.length>0?Math.min(...n):e.length>0?t:1}function l(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 u(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{sumUsages as t}from"../resolution/metrics.js";import{parseModelResponse as n}from"./parse-response.js";import{buildPrompt as r}from"./prompt.js";import{createResolverTools as i}from"./tools.js";import{validateChunks as a}from"./validate.js";async function o(t,o,u){let d=u.config?.maxSteps??15,f=u.config?.temperature??0,{definitions:p,execute:m}=i(u.git),{system:h,userMessage:g}=r(t,o,{maxSteps:d}),_=[{role:`user`,content:g}],v=u.signal?{signal:u.signal}:void 0,y=0,b=0,x=0,S=[],C=t.markers.some(e=>e.userResolution!=null);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:t.filePath,stepNumber:y,usage:e.usage})),e.toolCalls&&e.toolCalls.length>0){_.push({role:`assistant`,content:e.text,toolCalls:e.toolCalls});for(let n of e.toolCalls){let e=typeof n.args.reason==`string`?n.args.reason:void 0;u.onProgress?.({type:`resolver:tool-call`,filePath:t.filePath,tool:n.name,args:n.args,stepNumber:y,reason:e}),b++;let r=await m(n,v);u.onProgress?.({type:`resolver:tool-result`,filePath:t.filePath,tool:n.name,stepNumber:y,content:r.content}),_.push({role:`tool`,toolCallId:r.toolCallId,toolName:n.name,content:r.content})}continue}let r=e.text??``;if(!r.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 i;try{i=n(r,t.markers.length)}catch(e){x++;let t=e instanceof Error?e.message:String(e);_.push({role:`assistant`,content:r}),_.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(i.strategy===`deleted`&&t.type!==`delete-modify`){x++,_.push({role:`assistant`,content:r}),_.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(i.strategy){let e=i.strategy;if((e===`ours`||e===`theirs`)&&C)i.chunks=t.markers.map((t,n)=>t.userResolution==null?{markerIndex:n,strategy:e}:null).filter(e=>e!==null),i.strategy=void 0;else return u.onProgress?.({type:`resolver:response`,filePath:t.filePath,chunks:i.chunks,strategy:e,confidence:i.confidence,description:i.description,...i.followUpInstructions?{followUpInstructions:i.followUpInstructions}:{}}),{chunks:i.chunks,strategy:e,confidence:i.confidence,description:i.description,metrics:l(S,y,b,x),...i.followUpInstructions?{followUpInstructions:i.followUpInstructions}:{}}}if(t.markers.length===0&&!i.strategy){x++,_.push({role:`assistant`,content:r}),_.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 o=i.chunks.filter(e=>t.markers[e.markerIndex]?.userResolution==null),g=a(o,t);if(!g.valid){x++;let e=(g.issues??[]).map(e=>e.message).join(` `);_.push({role:`assistant`,content:r}),_.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 w=c(o,t),T=s(o,i.confidence);return u.onProgress?.({type:`resolver:response`,filePath:t.filePath,chunks:i.chunks,confidence:T,description:i.description,...i.followUpInstructions?{followUpInstructions:i.followUpInstructions}:{}}),{chunks:w,confidence:T,description:i.description,metrics:l(S,y,b,x),...i.followUpInstructions?{followUpInstructions:i.followUpInstructions}:{}}}throw new e(`VALIDATION_EXHAUSTED`,`Resolver exhausted ${d} steps without producing a valid resolution for ${t.filePath}.`)}function s(e,t){let n=e.map(e=>e.confidence).filter(e=>e!==void 0);return n.length>0?Math.min(...n):e.length>0?t:1}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,n,r,i){return{...t(e)??{inputTokens:0,outputTokens:0},stepCount:n,toolCallCount:r,retryCount:i}}export{o as runResolverLoop};
@@ -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 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
+ import{resolveChunkContent as e}from"../resolution/chunk-apply.js";import{createResolution as t}from"../resolution/create-resolution.js";import{checkResidualMarkers as n}from"../ports/verify.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:_}=await i(c,d,{model:u.model,git:u.git,config:u.config,onProgress:u.onProgress,signal:u.signal}),v=t(c,f,{confidence:m,description:h,metrics:g,...p?{fileStrategy:p}:{},...d.threeWayDiff?{threeWayDiff:d.threeWayDiff}:{}}),y=_&&_.length>0;if(c.type===`text`&&y){u.onProgress?.({type:`refine:started`,filePath:c.filePath});let t=0;try{let i=await a(v.content,v.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 e=n(c.filePath,i.content);if(e.valid)v={...v,content:i.content,edits:i.edits,strategy:`ai`},t=i.edits.length;else{let t=(e.issues??[]).map(e=>e.message).join(` `);u.onProgress?.({type:`refine:discarded`,filePath:c.filePath,reason:`Edits introduced conflict markers: ${t}`})}}if(t>0){v={...v,confidence:Math.min(v.confidence,s.refineApplied)};let t=[];for(let n=0;n<c.markers.length;n++){let r=c.markers[n];if(!r||r.userResolution==null)continue;let a=e({markerIndex:n,...r.userResolution},r);a!==``&&(i.content.includes(a)||t.push(n))}t.length>0&&(u.onProgress?.({type:`refine:touched-user-region`,filePath:c.filePath,markerIndexes:t}),v={...v,note:`Refine modified user-resolved region(s): marker ${t.join(`, `)}.`})}else i.edits.length===0&&i.verifies>0||(v={...v,confidence:Math.min(v.confidence,s.refineNoAction)});if(i.usage){let e=r(v.metrics??{inputTokens:0,outputTokens:0},i.usage);v={...v,metrics:{...v.metrics,...e}}}u.onProgress?.({type:`refine:completed`,filePath:c.filePath,appliedCount:t})}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`});if(u.validator&&c.type===`text`){let e=await u.validator.validate(new Map([[c.filePath,v.content]]));if(e.length>0){let t=e.map(e=>({category:e.category,severity:e.severity,tier:`file`,location:{file:e.file,...e.line==null?{line:1}:{line:e.line}},message:e.message}));v={...v,findings:t}}}return u.onProgress?.({type:`resolver:completed`,filePath:c.filePath,stepCount:g.stepCount??0}),v}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{detect as r}from"../reference-integrity/detect.js";import{createResolution as i}from"../resolution/create-resolution.js";import{takeOurs as a,takeTheirs as o}from"../resolution/take-strategy.js";import{resolveConflict as s}from"./resolve-conflict.js";import c from"picomatch";function l(e){let t=e.map(e=>{let t=(Array.isArray(e.match)?e.match:[e.match]).map(e=>c(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 u(e,s){let c=e.config?.rules??[],u=e.config?.defaultStrategy??`ai`,p=l(c),m=await t(e.git,{signal:e.signal}),h={...s??{}},g=[],_=[],v=[];for(let t of m){if(e.signal?.aborted)break;let r=p(t.path)??u;if(r===`skip`){v.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){_.push({filePath:t.path,error:f(e)});continue}if(!s){v.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=r===`take-ours`?a(s):r===`take-theirs`?o(s):r===`deleted`?i(s,[],{confidence:1,fileStrategy:`deleted`}):await d(s,h,e,_),c&&(h.previousResolutions=[...h.previousResolutions??[],c],g.push(c),e.onProgress?.({type:`resolution:applied`,filePath:c.filePath,strategy:c.strategy,confidence:c.confidence,description:c.description}))}return{resolutions:g,errors:_,skipped:v,detection:await r(g,h,{git:e.git,parser:e.parser,signal:e.signal})}}async function d(t,n,r,i){try{return await s(t,n,{model:r.model,git:r.git,config:r.config,onProgress:r.onProgress,signal:r.signal,verifier:r.verifier})}catch(n){let s=f(n),c=r.config?.fallbackStrategy;return c?(r.onProgress?.({type:`resolution:fallback`,filePath:t.filePath,error:s,cause:s instanceof e?s.code:void 0}),{...c===`take-ours`?a(t):o(t),confidence:0}):(i.push({filePath:t.filePath,error:s}),r.onProgress?.({type:`resolution:failed`,filePath:t.filePath,error:s}),null)}}function f(e){return e instanceof Error?e:Error(String(e))}export{u 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{detect as r}from"../reference-integrity/detect.js";import{createResolution as i}from"../resolution/create-resolution.js";import{takeOurs as a,takeTheirs as o}from"../resolution/take-strategy.js";import{resolveConflict as s}from"./resolve-conflict.js";import c from"picomatch";function l(e){let t=e.map(e=>{let t=(Array.isArray(e.match)?e.match:[e.match]).map(e=>c(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 u(e,s){let c=e.config?.rules??[],u=e.config?.defaultStrategy??`ai`,p=l(c),m=await t(e.git,{signal:e.signal}),h={...s??{}},g=[],_=[],v=[];for(let t of m){if(e.signal?.aborted)break;let r=p(t.path)??u;if(r===`skip`){v.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){_.push({filePath:t.path,error:f(e)});continue}if(!s){v.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=r===`take-ours`?a(s):r===`take-theirs`?o(s):r===`deleted`?i(s,[],{confidence:1,fileStrategy:`deleted`}):await d(s,h,e,_),c&&(h.previousResolutions=[...h.previousResolutions??[],c],g.push(c),e.onProgress?.({type:`resolution:applied`,filePath:c.filePath,strategy:c.strategy,confidence:c.confidence,description:c.description}))}let y=await r(g,h,{git:e.git,parser:e.parser,signal:e.signal}),b=g.flatMap(e=>e.findings??[]);return{resolutions:g,errors:_,skipped:v,detection:b.length>0?{...y,findings:[...y.findings,...b]}:y}}async function d(t,n,r,i){try{return await s(t,n,{model:r.model,git:r.git,config:r.config,onProgress:r.onProgress,signal:r.signal,validator:r.validator})}catch(n){let s=f(n),c=r.config?.fallbackStrategy;return c?(r.onProgress?.({type:`resolution:fallback`,filePath:t.filePath,error:s,cause:s instanceof e?s.code:void 0}),{...c===`take-ours`?a(t):o(t),confidence:0}):(i.push({filePath:t.filePath,error:s}),r.onProgress?.({type:`resolution:failed`,filePath:t.filePath,error:s}),null)}}function f(e){return e instanceof Error?e:Error(String(e))}export{u as resolveConflicts};
@@ -5,7 +5,7 @@ import { OperationDigest, OperationSummary } from "../types/summary.js";
5
5
  //#region src/resolver/summarize-operation.d.ts
6
6
  /**
7
7
  * Options for {@link summarizeOperation}. Intentionally a flat bag rather than the resolver's
8
- * `ResolverDeps` shape: summarization is a leaf operation that needs no git, verifier, or progress
8
+ * `ResolverDeps` shape: summarization is a leaf operation that needs no git, validator, or progress
9
9
  * sink, and its `model` is optional — omit it for a digest-only result.
10
10
  */
11
11
  interface SummarizeOperationOptions {
@@ -1,6 +1,6 @@
1
- import { Issue } from "../types/issue.js";
2
1
  import { ResolvedChunk } from "../types/resolution.js";
3
2
  import { Conflict } from "../types/conflict.js";
3
+ import { Issue } from "../types/issue.js";
4
4
 
5
5
  //#region src/resolver/validate.d.ts
6
6
  interface ValidationResult {
@@ -1,6 +1,6 @@
1
- import { Issue } from "./issue.js";
2
1
  import { ResolutionStrategy, ResolvedChunk } from "./resolution.js";
3
2
  import { ConflictType } from "./conflict.js";
3
+ import { Issue } from "./issue.js";
4
4
  import { AiTokenUsage } from "@gitkraken/shared-tools";
5
5
 
6
6
  //#region src/types/events.d.ts
@@ -1,8 +1,13 @@
1
1
  //#region src/types/findings.d.ts
2
+ /** The package's own detection categories. `Finding.category` is an open `string` so consumer
3
+ * validators can contribute their own kinds (e.g. `'type-error'`, `'lint:no-console'`); these are the
4
+ * well-known values the package itself emits. */
2
5
  type FindingCategory = 'broken-reference' | 'duplicate-definition' | 'syntax-error' | 'dropped-side';
3
6
  type FindingTier = 'file' | 'project';
4
7
  interface Finding {
5
- category: FindingCategory;
8
+ /** Finding kind. Open string — the package emits {@link FindingCategory} values; a consumer
9
+ * validator may use its own category strings. */
10
+ category: string;
6
11
  severity: 'error' | 'warning';
7
12
  tier: FindingTier;
8
13
  location: {
@@ -1,5 +1,5 @@
1
+ import { Finding } from "./findings.js";
1
2
  import { IntraFileEditOp } from "./hints.js";
2
- import { Issue } from "./issue.js";
3
3
  import { ConflictType } from "./conflict.js";
4
4
  import { AiTokenUsage } from "@gitkraken/shared-tools";
5
5
 
@@ -26,7 +26,9 @@ interface Resolution {
26
26
  confidence: number;
27
27
  description: string;
28
28
  note?: string;
29
- issues?: Issue[];
29
+ /** Non-blocking per-file findings from the consumer's validator port (post-refine). Folded into
30
+ * the operation's detection findings, hence into the verdict. */
31
+ findings?: Finding[];
30
32
  chunks?: ResolvedChunk[];
31
33
  edits?: IntraFileEditOp[];
32
34
  metrics?: ResolutionMetrics;
@@ -1,6 +1,6 @@
1
+ import { DetectionReport } from "./findings.js";
1
2
  import { Resolution } from "./resolution.js";
2
3
  import { ResolverConfig } from "../ports/model.js";
3
- import { DetectionReport } from "./findings.js";
4
4
 
5
5
  //#region src/types/step.d.ts
6
6
  interface StepResult {
@@ -1,5 +1,4 @@
1
1
  import { ResolutionStrategy } from "./resolution.js";
2
- import { FindingCategory } from "./findings.js";
3
2
  import { AiTokenUsage } from "@gitkraken/shared-tools";
4
3
 
5
4
  //#region src/types/summary.d.ts
@@ -11,7 +10,7 @@ interface ReviewItem {
11
10
  confidence?: number;
12
11
  reason: 'low-confidence' | 'finding';
13
12
  /** Categories of the error-severity findings on this file; present when `reason` is `'finding'`. */
14
- findingCategories?: FindingCategory[];
13
+ findingCategories?: string[];
15
14
  }
16
15
  /** Suggested honest verdict for the operation. `blocked` when any error-severity finding exists;
17
16
  * `needs-review` when confidence is low, detection did not fully run, resolution errored, or a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitkraken/conflict-tools",
3
- "version": "0.2.4-beta.6",
3
+ "version": "0.2.4-beta.8",
4
4
  "description": "AI-powered git conflict resolution.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Axosoft, LLC dba GitKraken",