@gitkraken/conflict-tools 0.2.4-beta.3 → 0.2.4-beta.4

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
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
11
11
 
12
12
  - Reference-integrity detection. `resolveConflicts` now attaches a `StepResult.detection` report listing broken references (symbols that resolved in a pre-merge parent but no longer resolve in the merged result, with provenance), duplicate definitions, and syntax errors in the resolved files. Detection is opt-in via a new `ParserPort` (`ResolverDeps.parser`) that supplies parsing/symbol resolution per surface (tree-sitter, language server); when it is absent, unavailable, or a file's language is unsupported, the report says so rather than implying a clean result. Report-only in this release — no automatic fixing.
13
13
  - Auto-merged scope for reference-integrity detection: the detector discovers the merge's incoming/auto-merged set itself (new optional `changedFiles` git-port op; `git diff --name-only base..theirs`, base from `refs.base` or `mergeBase`) and includes it in scope, so dangling references in cleanly-merged files are caught. Scope is capped and reported via `DetectionReport.scope`; an optional `ResolutionContext.touchedFiles` overrides discovery. Adds a `dropped-side` finding when a whole-side take (take-ours/take-theirs/fallback) discards a substantial diff from the other side.
14
+ - `ResolutionContext.detectionRefs` — optional parent tips for reference-integrity detection's before/after comparison, separate from `refs` (which drives the resolver's three-way diff). Detection uses `detectionRefs ?? refs`. Needed when the resolver's refs are the in-flight per-step state (e.g. a rebase) while detection needs the two stable pre-merge branch tips; in a merge they coincide and it can be left unset.
14
15
  - 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.
15
16
 
16
17
  ## [0.2.3] - 2026-07-06
@@ -1,3 +1,3 @@
1
- import{buildSnapshots as e}from"./snapshot.js";async function t(t,r,o){let c=o.parser;if(!c)return{findings:[],checks:{referenceIntegrity:`skipped-no-parser`,structural:`skipped-no-parser`}};try{let l=r.refs?await e(t,r,o):null;if(l){let[e,r,o]=await Promise.all([c.analyze(l.ours),c.analyze(l.theirs),c.analyze(l.result)]);return{findings:[...n(e,r,o),...i(o),...a(t,l)],checks:{referenceIntegrity:s([e,r,o])?`partial-unsupported-language`:`ran`,structural:s([o])?`partial-unsupported-language`:`ran`},scope:{analyzed:l.analyzed,truncated:l.truncated}}}let u=new Map(t.map(e=>[e.filePath,e.content])),d=await c.analyze(u);return{findings:i(d),checks:{referenceIntegrity:`skipped-no-refs`,structural:s([d])?`partial-unsupported-language`:`ran`}}}catch{return{findings:[],checks:{referenceIntegrity:`errored`,structural:`errored`}}}}function n(e,t,n){let i=new Map;r(e,`ours`,i),r(t,`theirs`,i);let a=[];for(let[e,t]of n)for(let n of t.references){if(n.resolved)continue;let t=i.get(c(e,n.name));t&&a.push({category:`broken-reference`,severity:`error`,tier:`project`,location:{file:e,line:n.line,symbol:n.name},provenance:t,message:`Reference "${n.name}" resolved before the merge (on ${t.side}) but no longer resolves.`})}return a}function r(e,t,n){for(let[r,i]of e)for(let e of i.references){if(!e.resolved)continue;let i=c(r,e.name);n.has(i)||n.set(i,{side:t,line:e.line})}}function i(e){let t=[];for(let[n,r]of e){let e=new Map;for(let i of r.definitions){let r=e.get(i.name);r==null?e.set(i.name,i.line):t.push({category:`duplicate-definition`,severity:`error`,tier:`file`,location:{file:n,line:i.line,symbol:i.name},message:`Duplicate definition of "${i.name}" (also defined at line ${r}).`})}for(let e of r.syntaxErrors)t.push({category:`syntax-error`,severity:`error`,tier:`file`,location:{file:n,line:e.line},message:e.message})}return t}function a(e,t){let n=[];for(let r of e){let e=r.strategy===`take-ours`?`theirs`:r.strategy===`take-theirs`?`ours`:null;if(!e)continue;let i=(e===`ours`?t.ours:t.theirs).get(r.filePath);if(i==null)continue;let a=o(i,r.content);a<20||n.push({category:`dropped-side`,severity:`error`,tier:`file`,location:{file:r.filePath,line:1},message:`Resolution took "${r.strategy}" wholesale, discarding ~${a} line(s) of the ${e} side.`})}return n}function o(e,t){let n=new Set(t.split(`
1
+ import{buildSnapshots as e}from"./snapshot.js";async function t(t,r,o){let c=o.parser;if(!c)return{findings:[],checks:{referenceIntegrity:`skipped-no-parser`,structural:`skipped-no-parser`}};try{let l=r.detectionRefs??r.refs?await e(t,r,o):null;if(l){let[e,r,o]=await Promise.all([c.analyze(l.ours),c.analyze(l.theirs),c.analyze(l.result)]);return{findings:[...n(e,r,o),...i(o),...a(t,l)],checks:{referenceIntegrity:s([e,r,o])?`partial-unsupported-language`:`ran`,structural:s([o])?`partial-unsupported-language`:`ran`},scope:{analyzed:l.analyzed,truncated:l.truncated}}}let u=new Map(t.map(e=>[e.filePath,e.content])),d=await c.analyze(u);return{findings:i(d),checks:{referenceIntegrity:`skipped-no-refs`,structural:s([d])?`partial-unsupported-language`:`ran`}}}catch{return{findings:[],checks:{referenceIntegrity:`errored`,structural:`errored`}}}}function n(e,t,n){let i=new Map;r(e,`ours`,i),r(t,`theirs`,i);let a=[];for(let[e,t]of n)for(let n of t.references){if(n.resolved)continue;let t=i.get(c(e,n.name));t&&a.push({category:`broken-reference`,severity:`error`,tier:`project`,location:{file:e,line:n.line,symbol:n.name},provenance:t,message:`Reference "${n.name}" resolved before the merge (on ${t.side}) but no longer resolves.`})}return a}function r(e,t,n){for(let[r,i]of e)for(let e of i.references){if(!e.resolved)continue;let i=c(r,e.name);n.has(i)||n.set(i,{side:t,line:e.line})}}function i(e){let t=[];for(let[n,r]of e){let e=new Map;for(let i of r.definitions){let r=e.get(i.name);r==null?e.set(i.name,i.line):t.push({category:`duplicate-definition`,severity:`error`,tier:`file`,location:{file:n,line:i.line,symbol:i.name},message:`Duplicate definition of "${i.name}" (also defined at line ${r}).`})}for(let e of r.syntaxErrors)t.push({category:`syntax-error`,severity:`error`,tier:`file`,location:{file:n,line:e.line},message:e.message})}return t}function a(e,t){let n=[];for(let r of e){let e=r.strategy===`take-ours`?`theirs`:r.strategy===`take-theirs`?`ours`:null;if(!e)continue;let i=(e===`ours`?t.ours:t.theirs).get(r.filePath);if(i==null)continue;let a=o(i,r.content);a<20||n.push({category:`dropped-side`,severity:`error`,tier:`file`,location:{file:r.filePath,line:1},message:`Resolution took "${r.strategy}" wholesale, discarding ~${a} line(s) of the ${e} side.`})}return n}function o(e,t){let n=new Set(t.split(`
2
2
  `)),r=0;for(let t of e.split(`
3
3
  `))t.trim()!==``&&!n.has(t)&&r++;return r}function s(e){return e.some(e=>{for(let t of e.values())if(t.language===`unsupported`)return!0;return!1})}function c(e,t){return JSON.stringify([e,t])}export{t as detect};
@@ -1 +1 @@
1
- import{changedFiles as e,mergeBase as t,readFile as n,showFile as r}from"../git/port-dispatch.js";async function i(e,t,n){let r=t.refs;if(!r)return null;let i=n.signal?{signal:n.signal}:void 0,c=new Map(e.map(e=>[e.filePath,e.content])),l=t.touchedFiles??await a(n.git,r,i),u=[],d=new Set;for(let e of[...c.keys(),...l,...t.fileNeighbors??[]])d.has(e)||(d.add(e),u.push(e));let f=u.length>400,p=f?u.slice(0,400):u,m=new Map,h=new Map,g=new Map;for(let e of p){let t=await o(n.git,r.ours,e,i);t!=null&&m.set(e,t);let a=await o(n.git,r.theirs,e,i);a!=null&&h.set(e,a);let l=c.get(e);if(l!=null)g.set(e,l);else{let t=await s(n.git,e,i);t!=null&&g.set(e,t)}}return{ours:m,theirs:h,result:g,analyzed:p.length,truncated:f}}async function a(n,r,i){try{return await e(n,r.base??await t(n,r.ours,r.theirs,i),r.theirs,i)}catch{return[]}}async function o(e,t,n,i){try{return await r(e,t,n,i)}catch{return null}}async function s(e,t,r){try{return await n(e,t,r)}catch{return null}}export{i as buildSnapshots};
1
+ import{changedFiles as e,mergeBase as t,readFile as n,showFile as r}from"../git/port-dispatch.js";async function i(e,t,n){let r=t.detectionRefs??t.refs;if(!r)return null;let i=n.signal?{signal:n.signal}:void 0,c=new Map(e.map(e=>[e.filePath,e.content])),l=t.touchedFiles??await a(n.git,r,i),u=[],d=new Set;for(let e of[...c.keys(),...l,...t.fileNeighbors??[]])d.has(e)||(d.add(e),u.push(e));let f=u.length>400,p=f?u.slice(0,400):u,m=new Map,h=new Map,g=new Map;for(let e of p){let t=await o(n.git,r.ours,e,i);t!=null&&m.set(e,t);let a=await o(n.git,r.theirs,e,i);a!=null&&h.set(e,a);let l=c.get(e);if(l!=null)g.set(e,l);else{let t=await s(n.git,e,i);t!=null&&g.set(e,t)}}return{ours:m,theirs:h,result:g,analyzed:p.length,truncated:f}}async function a(n,r,i){try{return await e(n,r.base??await t(n,r.ours,r.theirs,i),r.theirs,i)}catch{return[]}}async function o(e,t,n,i){try{return await r(e,t,n,i)}catch{return null}}async function s(e,t,r){try{return await n(e,t,r)}catch{return null}}export{i as buildSnapshots};
@@ -74,6 +74,11 @@ interface ResolutionContext {
74
74
  * When provided, it overrides auto-discovery and defines the incoming detection scope. */
75
75
  touchedFiles?: string[];
76
76
  refs?: ResolutionRefs;
77
+ /** Parent tips for reference-integrity detection's before/after comparison, when they differ from
78
+ * `refs`. `refs` is the resolver's three-way source (in a rebase, the in-flight per-step state);
79
+ * detection needs the two stable pre-merge branch tips. In a merge they coincide, so this is
80
+ * usually left unset and detection falls back to `refs`. */
81
+ detectionRefs?: ResolutionRefs;
77
82
  threeWayDiff?: ThreeWayDiff;
78
83
  /** Free-text guidance from the user that applies to the whole file ("prefer logging across the
79
84
  * board; throw on invalid input"). Rendered into the prompt as a top-level `<user-guidance>`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitkraken/conflict-tools",
3
- "version": "0.2.4-beta.3",
3
+ "version": "0.2.4-beta.4",
4
4
  "description": "AI-powered git conflict resolution.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Axosoft, LLC dba GitKraken",