@gitkraken/conflict-tools 0.2.4-beta.18 → 0.2.4-beta.19
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 +5 -8
- package/README.md +4 -5
- package/dist/index.d.ts +1 -2
- package/dist/ports/model.d.ts +0 -9
- package/dist/ports/parser.d.ts +2 -2
- package/dist/ports/verify.d.ts +1 -1
- package/dist/reference-integrity/detect.js +1 -1
- package/dist/resolver/fix-loop.d.ts +4 -3
- package/dist/resolver/fix-loop.js +0 -0
- package/dist/resolver/resolve-conflict.js +1 -1
- package/dist/resolver/resolve-conflicts.js +0 -0
- package/dist/resolver/summarize-operation.js +1 -1
- package/dist/types/events.d.ts +0 -5
- package/dist/types/step.d.ts +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,25 +14,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
14
14
|
- Cross-file fixes in the fix phase (opt-in `ResolverConfig.fix`). After per-file convergence, a project-tier broken reference (an import/cross-file use whose target stopped resolving) is routed to the refine of its **use-site** file: the finding becomes a fix instruction, and the whole set is re-detected, up to `crossFileMaxIterations` rounds (default 2). Each cross-file fix is a single refine pass plus a mandatory per-file re-validation; the final whole-set detection also re-checks per-file for files a cross-file fix touched. **Limitation:** only use-site files that have an AI resolution are routed — a cross-file break landing on an auto-merged file (no resolution to carry the edit) is reported but not auto-fixed yet (needs a patches channel). The definition side, if broken, arrives as its own finding on its own file. Report-only (fix off) is unchanged.
|
|
15
15
|
- Per-file fix loop (opt-in via `ResolverConfig.fix`, default `false`). When enabled, an AI-resolved (`strategy: 'ai'`) text file that carries error-severity per-file findings is routed back through refine: the findings become fix instructions, the file is re-analyzed after each edit, and the loop repeats until the file is clean, a round makes no progress, or `fixLoopMaxIterations` (default 3) is reached. Whatever it cannot fix stays in the findings, so the verdict reflects the post-fix state. The no-markers invariant holds (a fix that reintroduces markers is discarded). New progress events `fix:started` / `fix:completed`. Report-only (fix off) is unchanged. Deferred for the opt-in fix path: rebase per-step-ref/transient discipline, parser/validator call caching, cross-file fixes, and a full finding-lifecycle model (double-counting is handled here by fingerprint dedup when folding findings into the report).
|
|
16
16
|
- `collectFileFindings` — a per-file detection unit (structural + intra-file broken references + consumer validator issues, all `tier: 'file'`) over already-loaded parent snapshots; the fix loop re-runs it after each edit. `detect()` remains the authoritative whole-set report.
|
|
17
|
-
- 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.
|
|
18
|
-
- 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
|
|
17
|
+
- 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. Detection itself is report-only; fixing is opt-in via the fix phase (above) and off by default.
|
|
18
|
+
- 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 the resolver's emergency fallback took one side wholesale and discarded a substantial diff from the other; a deliberate rule/default take is not flagged.
|
|
19
19
|
- `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.
|
|
20
20
|
- 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.
|
|
21
|
-
- Operation-level assessment: `buildOperationDigest`/`summarizeOperation` now fold `StepResult.detection` into the rollup. A file with
|
|
21
|
+
- Operation-level assessment: `buildOperationDigest`/`summarizeOperation` now fold `StepResult.detection` into the rollup. A file with any finding is added to `needsReview` (`reason: 'finding'`) regardless of confidence — even a file with no resolution of its own; an error finding (broken-reference, syntax-error, dropped-side) drives `verdict: 'blocked'`, while a warning finding (duplicate-definition, consumer-validator warnings) drives `needs-review`; `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.
|
|
22
22
|
|
|
23
23
|
### Changed
|
|
24
24
|
|
|
25
25
|
- Reference-integrity findings are now tiered by locality: an intra-file break — a local definition/binding the merge dropped while a use of it remains in the same file (e.g. a `const` used by a closure below) — is a `tier: 'file'` finding; a broken cross-file reference (an import whose target stopped resolving) stays `tier: 'project'`. Previously every `broken-reference` was `'project'`. Classification uses the reference's `target` file when the parser supplies it, else whether the name was a local definition/binding in the parent. This sets up the per-file vs cross-file fix loops.
|
|
26
26
|
- **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).
|
|
27
|
-
- **Breaking**: the resolver's in-loop blocking check is now **residual conflict markers only** (built-in, language-agnostic).
|
|
28
|
-
|
|
29
|
-
### Changed
|
|
30
|
-
|
|
27
|
+
- **Breaking**: the resolver's in-loop blocking check is now **residual conflict markers only** (built-in, language-agnostic). Consumer validation (the `Validator` port, above) no longer gates the resolve loop or triggers re-resolution — it runs once **after** refine, **non-blocking**, and its findings are folded into the report (advisory). 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`.
|
|
31
28
|
- The per-file fix loop now emits `fix:completed` (iterations 0, remaining 0) even when a file has no findings to fix, so a consumer can distinguish "the fix loop evaluated this file and it was clean" from "the fix loop never reached this file". Previously a clean file returned silently, leaving the two cases indistinguishable in the event stream.
|
|
32
29
|
|
|
33
30
|
### Fixed
|
|
34
31
|
|
|
35
|
-
- The opt-in fix phase is now fault-isolated. A parser, validator, or model throw inside the fix phase no longer rejects the whole operation: the
|
|
32
|
+
- The opt-in fix phase is now fault-isolated. A parser, validator, or model throw inside the fix phase no longer rejects the whole operation: the resolutions (possibly partially fixed) are kept, both detection checks are reported as `errored` (so the verdict is `needs-review`, never a false `clean`), and the throw is surfaced via `fix:failed`. A user abort still propagates and cancels the operation.
|
|
36
33
|
- `OperationDigest.verdict` can now reach `clean` on a real repository. `detectionComplete` treated `partial-unsupported-language` as "did not run", so any operation whose scope contained a non-code file (package.json, README, lockfiles — always present in a real merge) forced `verdict: 'needs-review'` even with zero findings and full confidence. Since `--fix` gates auto-apply on `verdict === 'clean'`, this made auto-apply effectively unreachable. A check now counts as complete when it ran, including when some scope files were an unsupported language (they carry no references to break); only a check that could not run at all — no parser, no refs, an unreadable parent snapshot, or a thrown parser — leaves detection incomplete.
|
|
37
34
|
- A non-abort error in the refine pass no longer destroys the pre-refine resolution. Previously the refine `catch` rethrew, bubbling to `resolveOne` and degrading a good resolve-stage result into a take-side fallback (confidence 0) — the same class of failure as a throwing validator, one stage later. The pre-refine resolution is now kept, its confidence capped (the enhancement did not complete), and the error reported via `refine:failed`. A user abort still cancels (rethrown past the fallback).
|
|
38
35
|
- A throwing consumer validator no longer destroys a good resolution. The post-refine `validator.validate()` call is now guarded: a validator error is reported via a new `validation:failed` progress event and the resolution is kept intact, instead of bubbling to `resolveOne` and degrading a successful AI resolve into a whole-file take-side fallback (confidence 0) — the exact dropped-side loss the tool guards against. The validator stays advisory.
|
package/README.md
CHANGED
|
@@ -302,7 +302,7 @@ const report = await detect(step.resolutions, { refs }, { git, parser });
|
|
|
302
302
|
|
|
303
303
|
- references that resolved in a pre-merge parent (ours/theirs) but no longer resolve in the merged result (`broken-reference`, with `provenance`),
|
|
304
304
|
- duplicate definitions and syntax errors in the resolved files (`duplicate-definition`, `syntax-error`),
|
|
305
|
-
-
|
|
305
|
+
- the resolver's emergency fallback taking one side wholesale and discarding a substantial diff (`dropped-side`).
|
|
306
306
|
|
|
307
307
|
The scope includes the resolved files *and* the merge's auto-merged/incoming set (discovered from `refs` via `changedFiles`/`mergeBase`, or overridden by `context.touchedFiles`), so a dangling reference in a cleanly-merged file is still caught.
|
|
308
308
|
|
|
@@ -333,9 +333,9 @@ interface DetectionReport {
|
|
|
333
333
|
| Category | Meaning |
|
|
334
334
|
|---|---|
|
|
335
335
|
| `broken-reference` | A symbol resolved in a pre-merge parent but no longer resolves in the merged result |
|
|
336
|
-
| `duplicate-definition` | The same symbol is defined twice in a resolved file |
|
|
336
|
+
| `duplicate-definition` | The same symbol is defined twice in a resolved file (advisory `warning` — the detector cannot tell a genuine redefinition from legal overloads / declaration merging) |
|
|
337
337
|
| `syntax-error` | The parser reported a syntax error in a resolved file |
|
|
338
|
-
| `dropped-side` |
|
|
338
|
+
| `dropped-side` | The resolver's emergency fallback took one side wholesale, discarding a substantial diff (a deliberate rule/default take is not flagged) |
|
|
339
339
|
|
|
340
340
|
`CheckStatus` tells "ran, clean" apart from "did not run" — so an empty `findings` array is never mistaken for a clean result:
|
|
341
341
|
|
|
@@ -484,10 +484,9 @@ score(resolution, goldenFile);
|
|
|
484
484
|
| `refine:touched-user-region` | Refine modified a user-resolved marker region (`markerIndexes`) |
|
|
485
485
|
| `fix:started` | The fix loop began fixing a file (`findingCount`) |
|
|
486
486
|
| `fix:completed` | The fix loop finished evaluating a file (`iterations`, `remaining`) — emitted even for a clean file (iterations 0, remaining 0) |
|
|
487
|
-
| `fix:failed` | The opt-in fix phase threw a non-abort error (`filePath?`, `reason`); the operation degrades gracefully —
|
|
487
|
+
| `fix:failed` | The opt-in fix phase threw a non-abort error (`filePath?`, `reason`); the operation degrades gracefully — the resolutions (possibly partially fixed) are kept and both checks are reported as `errored` |
|
|
488
488
|
| `fix:budget-exhausted` | The global `maxModelCalls` budget was spent (`spent`, `limit`); the fix phase stopped and left the remainder in findings |
|
|
489
489
|
| `validation:failed` | The consumer validator threw; reported and swallowed, resolution kept (`reason`) |
|
|
490
|
-
| `verification:issues` | Advisory verification issues attached to a resolution (`issues`) |
|
|
491
490
|
|
|
492
491
|
Note: `refine:tool-result.appliedCount` reports edits applied to an in-progress buffer per turn. The final disposition is signaled by `refine:completed` / `refine:discarded` / `refine:failed` — only edits that survive residual-marker validation appear in `Resolution.edits`.
|
|
493
492
|
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,6 @@ 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 { Issue } from "./types/issue.js";
|
|
14
13
|
import { Validator, ValidatorFinding } from "./ports/verify.js";
|
|
15
14
|
import { DetectDeps, detect } from "./reference-integrity/detect.js";
|
|
16
15
|
import { createResolution } from "./resolution/create-resolution.js";
|
|
@@ -22,4 +21,4 @@ import { FileRule, StepConfig, StepResult } from "./types/step.js";
|
|
|
22
21
|
import { resolveConflicts } from "./resolver/resolve-conflicts.js";
|
|
23
22
|
import { OperationDigest, OperationSummary, OperationVerdict, ReviewItem } from "./types/summary.js";
|
|
24
23
|
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
|
|
24
|
+
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 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/ports/model.d.ts
CHANGED
|
@@ -35,15 +35,6 @@ interface ResolverConfig {
|
|
|
35
35
|
maxSteps?: number;
|
|
36
36
|
temperature?: number;
|
|
37
37
|
refineMaxSteps?: number;
|
|
38
|
-
/** Opt in to the per-file fix loop (default `false` → report-only). When enabled, an AI-resolved file
|
|
39
|
-
* carrying error-severity per-file findings is routed back through refine to attempt a fix; what it
|
|
40
|
-
* cannot fix remains in the findings. Off by default so the report-only path is unchanged. */
|
|
41
|
-
fix?: boolean;
|
|
42
|
-
/** Max per-file fix iterations (refine → re-detect the file) when `fix` is on. Default 3. */
|
|
43
|
-
fixLoopMaxIterations?: number;
|
|
44
|
-
/** Max cross-file fix rounds (route project-level breaks to their use-site file → re-detect the whole
|
|
45
|
-
* set) when `fix` is on. Default 2. */
|
|
46
|
-
crossFileMaxIterations?: number;
|
|
47
38
|
/** Global cap on `model.generate` calls for the whole operation (resolve + refine + fix). Default
|
|
48
39
|
* `undefined` = unlimited. Primarily a backstop for the fix phase's multiplicative worst case
|
|
49
40
|
* (files × fixLoopMaxIterations + crossFileMaxIterations × routable calls). When spent, the fix
|
package/dist/ports/parser.d.ts
CHANGED
|
@@ -49,9 +49,9 @@ interface ParserPort {
|
|
|
49
49
|
/**
|
|
50
50
|
* Parse and resolve every file in `sources`, treating those contents as authoritative for their
|
|
51
51
|
* paths. Returns one {@link ParsedFile} per input path. Works only from the provided content — it
|
|
52
|
-
* never reads the filesystem itself.
|
|
52
|
+
* never reads the filesystem itself. `signal`, when provided, should abort an in-flight analysis.
|
|
53
53
|
*/
|
|
54
|
-
analyze(sources: SourceOverlay): Promise<ReadonlyMap<string, ParsedFile>>;
|
|
54
|
+
analyze(sources: SourceOverlay, signal?: AbortSignal): Promise<ReadonlyMap<string, ParsedFile>>;
|
|
55
55
|
}
|
|
56
56
|
//#endregion
|
|
57
57
|
export { ParsedFile, ParserPort, SourceOverlay, SymbolDefinition, SymbolReference, SyntaxIssue };
|
package/dist/ports/verify.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ interface ValidatorFinding {
|
|
|
23
23
|
* conflict markers, checked built-in by the resolver ({@link checkResidualMarkers}) — not this port.
|
|
24
24
|
*/
|
|
25
25
|
interface Validator {
|
|
26
|
-
validate(sources: SourceOverlay): Promise<ValidatorFinding[]>;
|
|
26
|
+
validate(sources: SourceOverlay, signal?: AbortSignal): Promise<ValidatorFinding[]>;
|
|
27
27
|
}
|
|
28
28
|
declare function checkResidualMarkers(_filePath: string, content: string): VerificationResult;
|
|
29
29
|
//#endregion
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{buildSnapshots as e}from"./snapshot.js";async function t(t,n,i){let s=i.parser;if(!s)return{findings:[],checks:{referenceIntegrity:`skipped-no-parser`,structural:`skipped-no-parser`}};try{let l=n.detectionRefs??n.refs?await e(t,n,i):null;if(l){let[e,n,
|
|
1
|
+
import{buildSnapshots as e}from"./snapshot.js";async function t(t,n,i){let s=i.parser;if(!s)return{findings:[],checks:{referenceIntegrity:`skipped-no-parser`,structural:`skipped-no-parser`}};try{let l=n.detectionRefs??n.refs?await e(t,n,i):null;if(l){let[e,n,u]=await Promise.all([s.analyze(l.ours,i.signal),s.analyze(l.theirs,i.signal),s.analyze(l.result,i.signal)]),d=l.analyzed>0&&l.readable===0?`skipped-unreadable-source`:c([e,n,u])?`partial-unsupported-language`:`ran`;return{findings:[...r(e,n,u),...a(u),...o(t,l)],checks:{referenceIntegrity:d,structural:c([u])?`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 s.analyze(u,i.signal);return{findings:a(d),checks:{referenceIntegrity:`skipped-no-refs`,structural:c([d])?`partial-unsupported-language`:`ran`}}}catch{return{findings:[],checks:{referenceIntegrity:`errored`,structural:`errored`}}}}async function n(e,t,n,i,o){let s=async t=>t==null?new Map:o.parser.analyze(new Map([[e,t]]),o.signal),c=await s(t),l=await s(n),u=await o.parser.analyze(new Map([[e,i]]),o.signal),d=[...r(c,l,u).filter(e=>e.tier===`file`),...a(u)];if(o.validator){let t=await o.validator.validate(new Map([[e,i]]),o.signal);for(let e of t)d.push({category:e.category,severity:e.severity,tier:`file`,location:{file:e.file,line:e.line??1},message:e.message})}return d}function r(e,t,n){let r=new Map;i(e,`ours`,r),i(t,`theirs`,r);let a=[];for(let[e,t]of n)for(let n of t.references){if(n.resolved)continue;let t=r.get(l(e,n.name));t&&a.push({category:`broken-reference`,severity:`error`,tier:t.crossFile?`project`:`file`,location:{file:e,line:n.line,symbol:n.name},provenance:{side:t.side,line:t.line},message:`Reference "${n.name}" resolved before the merge (on ${t.side}) but no longer resolves.`})}return a}function i(e,t,n){let r=(e,r,i,a)=>{let o=l(e,r);n.has(o)||n.set(o,{side:t,line:i,crossFile:a})};for(let[t,n]of e){for(let e of n.definitions)r(t,e.name,e.line,!1);for(let e of n.bindings??[])r(t,e.name,e.line,!1)}for(let[t,n]of e)for(let e of n.references)e.resolved&&r(t,e.name,e.line,e.target?e.target.file!==t:!0)}function a(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:`warning`,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 o(e,t){let n=[];for(let r of e){let e=r.strategy===`take-ours`?`theirs`:r.strategy===`take-theirs`?`ours`:null;if(!e||r.confidence!==0)continue;let i=(e===`ours`?t.ours:t.theirs).get(r.filePath);if(i==null)continue;let a=s(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 s(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 c(e){return e.some(e=>{for(let t of e.values())if(t.language===`unsupported`)return!0;return!1})}function l(e,t){return JSON.stringify([e,t])}export{n as collectFileFindings,t as detect};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ConflictGitPort } from "../ports/git.js";
|
|
2
2
|
import { DetectionReport } from "../types/findings.js";
|
|
3
3
|
import { Resolution, ResolutionContext } from "../types/resolution.js";
|
|
4
|
-
import { ConflictModelPort
|
|
4
|
+
import { ConflictModelPort } from "../ports/model.js";
|
|
5
5
|
import { ParserPort } from "../ports/parser.js";
|
|
6
6
|
import { Validator } from "../ports/verify.js";
|
|
7
7
|
import { ConflictProgressEvent } from "../types/events.js";
|
|
8
|
+
import { StepConfig } from "../types/step.js";
|
|
8
9
|
import { ModelBudget } from "./model-budget.js";
|
|
9
10
|
|
|
10
11
|
//#region src/resolver/fix-loop.d.ts
|
|
@@ -12,7 +13,7 @@ interface FixLoopDeps {
|
|
|
12
13
|
model: ConflictModelPort;
|
|
13
14
|
parser: ParserPort;
|
|
14
15
|
validator?: Validator;
|
|
15
|
-
config?:
|
|
16
|
+
config?: StepConfig;
|
|
16
17
|
onProgress?: (event: ConflictProgressEvent) => void;
|
|
17
18
|
signal?: AbortSignal;
|
|
18
19
|
/** Global model-call budget for the operation. When exhausted, the loop stops before any further
|
|
@@ -38,7 +39,7 @@ interface FixPhaseDeps {
|
|
|
38
39
|
git: ConflictGitPort;
|
|
39
40
|
parser: ParserPort;
|
|
40
41
|
validator?: Validator;
|
|
41
|
-
config?:
|
|
42
|
+
config?: StepConfig;
|
|
42
43
|
onProgress?: (event: ConflictProgressEvent) => void;
|
|
43
44
|
signal?: AbortSignal;
|
|
44
45
|
/** Global model-call budget for the operation. When exhausted, both fix stages stop before any
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
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){if(u.signal?.aborted)throw e;let t=e instanceof Error?e.message:String(e);u.onProgress?.({type:`refine:failed`,filePath:c.filePath,reason:t}),v={...v,confidence:Math.min(v.confidence,s.refineNoAction)}}}else u.onProgress?.({type:`refine:skipped`,filePath:c.filePath,reason:`no-follow-ups`});if(u.validator&&c.type===`text`)try{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
|
|
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){if(u.signal?.aborted)throw e;let t=e instanceof Error?e.message:String(e);u.onProgress?.({type:`refine:failed`,filePath:c.filePath,reason:t}),v={...v,confidence:Math.min(v.confidence,s.refineNoAction)}}}else u.onProgress?.({type:`refine:skipped`,filePath:c.filePath,reason:`no-follow-ups`});if(u.validator&&c.type===`text`)try{let e=await u.validator.validate(new Map([[c.filePath,v.content]]),u.signal);if(e.length>0){let t=e.map(e=>({category:e.category,severity:e.severity,tier:`file`,location:{file:e.file,line:e.line??1},message:e.message}));v={...v,findings:t}}}catch(e){if(u.signal?.aborted)throw e;let t=e instanceof Error?e.message:String(e);u.onProgress?.({type:`validation:failed`,filePath:c.filePath,reason:t})}return u.onProgress?.({type:`resolver:completed`,filePath:c.filePath,stepCount:g.stepCount??0}),v}export{c as resolveConflict};
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildSummaryPrompt as e}from"./prompt.js";const t=.8;function n(e,n=t){let o={ai:0,"take-ours":0,"take-theirs":0,deleted:0,skipped:0},s=0,c=null,l=new Map;for(let t of e.resolutions)o[t.strategy]++,s+=t.chunks?.length??0,c=c===null?t.confidence:Math.min(c,t.confidence),t.confidence<n&&l.set(t.filePath,{filePath:t.filePath,confidence:t.confidence,reason:`low-confidence`});let u=e.detection,d=u?.findings??[],f=new Map;for(let e of d){
|
|
1
|
+
import{buildSummaryPrompt as e}from"./prompt.js";const t=.8;function n(e,n=t){let o={ai:0,"take-ours":0,"take-theirs":0,deleted:0,skipped:0},s=0,c=null,l=new Map;for(let t of e.resolutions)o[t.strategy]++,s+=t.chunks?.length??0,c=c===null?t.confidence:Math.min(c,t.confidence),t.confidence<n&&l.set(t.filePath,{filePath:t.filePath,confidence:t.confidence,reason:`low-confidence`});let u=e.detection,d=u?.findings??[],f=new Map,p=!1;for(let e of d){e.severity===`error`&&(p=!0);let t=f.get(e.location.file)??new Set;t.add(e.category),f.set(e.location.file,t)}for(let[e,t]of f){let n=l.get(e);l.set(e,{filePath:e,...n?.confidence==null?{}:{confidence:n.confidence},reason:`finding`,findingCategories:[...t].sort()})}let m=[...l.values()].sort(i),h=u!=null&&r(u.checks.referenceIntegrity)&&r(u.checks.structural),g=a({hasErrorFinding:p,hasWarningFinding:d.some(e=>e.severity===`warning`),needsReview:m.length>0,detectionComplete:h,hasErrors:e.errors.length>0});return{files:e.resolutions.length,conflicts:s,byStrategy:o,minConfidence:c,needsReview:m,skipped:e.skipped?.length??0,errors:e.errors.length,detectionComplete:h,findingCount:d.length,verdict:g}}function r(e){return e===`ran`||e===`partial-unsupported-language`}function i(e,t){return e.reason===t.reason?(e.confidence??1)-(t.confidence??1):e.reason===`finding`?-1:1}function a(e){return e.hasErrorFinding?`blocked`:e.needsReview||!e.detectionComplete||e.hasErrors||e.hasWarningFinding?`needs-review`:`clean`}async function o(r,i={}){let a=n(r,i.reviewThreshold??t);if(!i.model)return{digest:a};let{system:o,userMessage:s}=e(r,a),c=await i.model.generate({system:o,messages:[{role:`user`,content:s}],temperature:i.temperature??0,signal:i.signal}),l=c.text?.trim();return{digest:a,...l?{prose:l}:{},...c.usage?{usage:c.usage}:{}}}export{n as buildOperationDigest,o as summarizeOperation};
|
package/dist/types/events.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ResolutionStrategy, ResolvedChunk } from "./resolution.js";
|
|
2
2
|
import { ConflictType } from "./conflict.js";
|
|
3
|
-
import { Issue } from "./issue.js";
|
|
4
3
|
import { AiTokenUsage } from "@gitkraken/shared-tools";
|
|
5
4
|
|
|
6
5
|
//#region src/types/events.d.ts
|
|
@@ -116,10 +115,6 @@ type ConflictProgressEvent = {
|
|
|
116
115
|
type: 'fix:budget-exhausted';
|
|
117
116
|
spent: number;
|
|
118
117
|
limit: number;
|
|
119
|
-
} | {
|
|
120
|
-
type: 'verification:issues';
|
|
121
|
-
filePath: string;
|
|
122
|
-
issues: Issue[];
|
|
123
118
|
} | {
|
|
124
119
|
type: 'validation:failed';
|
|
125
120
|
filePath: string;
|
package/dist/types/step.d.ts
CHANGED
|
@@ -23,6 +23,16 @@ interface StepConfig extends ResolverConfig {
|
|
|
23
23
|
defaultStrategy?: 'ai' | 'take-ours' | 'take-theirs' | 'skip' | 'deleted';
|
|
24
24
|
rules?: FileRule[];
|
|
25
25
|
fallbackStrategy?: 'take-ours' | 'take-theirs';
|
|
26
|
+
/** Opt in to the fix phase (default `false` → report-only). When enabled, an AI-resolved file carrying
|
|
27
|
+
* error-severity per-file findings is routed back through refine to attempt a fix; what it cannot fix
|
|
28
|
+
* remains in the findings. Requires `ResolverDeps.parser` — without a parser the fix phase is a no-op
|
|
29
|
+
* and the report-only detection path runs instead. Off by default so the report-only path is unchanged. */
|
|
30
|
+
fix?: boolean;
|
|
31
|
+
/** Max per-file fix iterations (refine → re-detect the file) when `fix` is on. Default 3. */
|
|
32
|
+
fixLoopMaxIterations?: number;
|
|
33
|
+
/** Max cross-file fix rounds (route project-level breaks to their use-site file → re-detect the whole
|
|
34
|
+
* set) when `fix` is on. Default 2. */
|
|
35
|
+
crossFileMaxIterations?: number;
|
|
26
36
|
}
|
|
27
37
|
//#endregion
|
|
28
38
|
export { FileRule, StepConfig, StepResult };
|