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

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
@@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
14
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.
15
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.
16
16
 
17
+ ### Fixed
18
+
19
+ - 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`.
20
+
17
21
  ## [0.2.3] - 2026-07-06
18
22
 
19
23
  ### Added
@@ -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.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(`
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)]),u=l.analyzed>0&&l.readable===0?`skipped-unreadable-source`:s([e,r,o])?`partial-unsupported-language`:`ran`;return{findings:[...n(e,r,o),...i(o),...a(t,l)],checks:{referenceIntegrity:u,structural:s([o])?`partial-unsupported-language`:`ran`},scope:{analyzed:l.analyzed,readable:l.readable,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};
@@ -7,8 +7,11 @@ interface DetectionSnapshots {
7
7
  ours: SourceOverlay;
8
8
  theirs: SourceOverlay;
9
9
  result: SourceOverlay;
10
- /** Number of files actually included in the snapshots (after the cap). */
10
+ /** Number of files in scope (after the cap). */
11
11
  analyzed: number;
12
+ /** How many scope files had loadable parent content on `ours` or `theirs`. When this is 0 the
13
+ * before/after comparison had nothing to read — a total read failure, not a clean result. */
14
+ readable: number;
12
15
  /** Whether the scope exceeded the cap and was truncated. */
13
16
  truncated: boolean;
14
17
  }
@@ -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.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};
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,_=0;for(let e of p){let t=await o(n.git,r.ours,e,i);t&&m.set(e,t);let a=await o(n.git,r.theirs,e,i);a&&h.set(e,a),(t||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,readable:_,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};
@@ -19,19 +19,22 @@ interface Finding {
19
19
  }
20
20
  /** Why a check produced its result — lets a consumer tell "ran, clean" apart from "did not run".
21
21
  * `errored` means the parser was present but threw; `partial-unsupported-language` means some files
22
- * in scope could not be parsed. */
23
- type CheckStatus = 'ran' | 'skipped-no-parser' | 'skipped-no-refs' | 'partial-unsupported-language' | 'errored';
22
+ * in scope could not be parsed; `skipped-unreadable-source` means the parent snapshots came back
23
+ * empty (unreadable source), so the before/after comparison had nothing to compare against. */
24
+ type CheckStatus = 'ran' | 'skipped-no-parser' | 'skipped-no-refs' | 'skipped-unreadable-source' | 'partial-unsupported-language' | 'errored';
24
25
  interface DetectionReport {
25
26
  findings: Finding[];
26
27
  checks: {
27
28
  referenceIntegrity: CheckStatus;
28
29
  structural: CheckStatus;
29
30
  };
30
- /** How many files were analyzed and whether the scope was capped — an honest coverage signal so
31
- * empty findings on a truncated scope are not read as "clean". Present only when the reference
32
- * check ran (refs available). */
31
+ /** Honest coverage signal so empty findings are not read as "clean": `analyzed` is the number of
32
+ * files in scope, `readable` how many had loadable parent content (0 means the before/after
33
+ * comparison had nothing to compare), and `truncated` whether the scope was capped. Present only
34
+ * when the reference check ran (refs available). */
33
35
  scope?: {
34
36
  analyzed: number;
37
+ readable: number;
35
38
  truncated: boolean;
36
39
  };
37
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitkraken/conflict-tools",
3
- "version": "0.2.4-beta.4",
3
+ "version": "0.2.4-beta.5",
4
4
  "description": "AI-powered git conflict resolution.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Axosoft, LLC dba GitKraken",