@gitkraken/conflict-tools 0.7.1-beta.10 → 0.7.1-beta.12
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/README.md +20 -11
- package/dist/index.d.ts +1 -1
- package/dist/ports/validator.d.ts +22 -6
- package/dist/reference-integrity/detect.d.ts +2 -10
- package/dist/reference-integrity/detect.js +1 -1
- package/dist/reference-integrity/validate-set.d.ts +24 -0
- package/dist/reference-integrity/validate-set.js +1 -0
- package/dist/resolution/edit-apply.js +4 -5
- package/dist/resolver/fix-loop.d.ts +1 -1
- package/dist/resolver/fix-loop.js +2 -2
- package/dist/resolver/prompt.js +2 -5
- package/dist/resolver/refine-pass.d.ts +0 -1
- package/dist/resolver/refine-pass.js +1 -1
- package/dist/resolver/refine-tools.d.ts +0 -1
- package/dist/resolver/refine-tools.js +4 -4
- package/dist/resolver/resolve-conflict.js +1 -1
- package/dist/resolver/resolve-conflicts.js +1 -1
- package/dist/resolver/summarize-operation.js +1 -1
- package/dist/types/events.d.ts +7 -1
- package/dist/types/findings.d.ts +7 -1
- package/dist/types/hints.d.ts +0 -8
- package/dist/types/summary.d.ts +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -131,15 +131,21 @@ Pass via `deps.parser`. When absent, detection is skipped and the report says so
|
|
|
131
131
|
|
|
132
132
|
### `Validator` (optional)
|
|
133
133
|
|
|
134
|
-
The consumer's validation port. Plug in any checks — syntax, real import resolution, types, lint, project rules. It
|
|
134
|
+
The consumer's validation port. Plug in any checks — syntax, real import resolution, types, lint, project rules. It is called with an overlay and a `call` context, and returns `ValidatorFinding[]` or `{ status: 'not-run', reason }`.
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
`call` says what the overlay holds:
|
|
137
|
+
|
|
138
|
+
- **Per-file calls** (`scope: 'file'`) hold one file, right after that file's refine. `role` is `result` for the resolved content, and `ours` / `theirs` when the package builds a baseline from the pre-merge parents (findings the parents already had are subtracted). A validator that cannot check one file in isolation (a compile check that needs its siblings) returns `{ status: 'not-run', reason }` for these. A per-file `not-run` adds no findings and is reported as a `validation:not-run` event.
|
|
139
|
+
- **The whole-set call** (`scope: 'set'`, `role: 'result'`) runs **once per operation**, after every file of the step is resolved, and after the fix phase when it runs. The overlay holds every resolved file's final content: a resolution's own content, or for a whole-side take the taken side's content when the detection snapshots hold it (otherwise the file is left out). `call.deleted` lists the paths the step deletes, including retired rename paths; they are absent from the overlay, so treat them as removed. There is no parent baseline: errors the merged set has are the merge's to answer for. The call is skipped when there is nothing to validate.
|
|
140
|
+
|
|
141
|
+
The whole-set outcome is reported on `DetectionReport.checks.validator` (`ran`, `skipped-no-validator`, `not-run`, `errored`) with `validatorReason`. A whole-set `not-run` or throw never fails the operation, but it makes `detectionComplete` false, so the verdict cannot read `clean`.
|
|
137
142
|
|
|
138
143
|
```typescript
|
|
139
144
|
import type { Validator, ValidatorFinding } from '@gitkraken/conflict-tools';
|
|
140
145
|
|
|
141
146
|
const validator: Validator = {
|
|
142
|
-
async validate(sources) {
|
|
147
|
+
async validate(sources, signal, call) {
|
|
148
|
+
if (call.scope === 'file') return { status: 'not-run', reason: 'needs the whole set' };
|
|
143
149
|
const findings: ValidatorFinding[] = [];
|
|
144
150
|
for (const [file, content] of sources) {
|
|
145
151
|
for (const err of await typecheck(file, content)) {
|
|
@@ -151,7 +157,7 @@ const validator: Validator = {
|
|
|
151
157
|
};
|
|
152
158
|
```
|
|
153
159
|
|
|
154
|
-
**Advisory, never blocking.** Its findings fold into the operation's detection findings and the verdict; a validator finding never triggers a re-resolve, and a *throwing* validator never destroys the resolution — it is kept intact and the failure is reported. On the resolve/report path that report is a `validation:failed` event; inside the opt-in fix phase a validator throw folds into the phase's per-file isolation and surfaces as `fix:failed` (like any per-file throw there), so telemetry that must catch every validator failure should watch both. `ValidatorFinding.category` is an open string (e.g. `'type-error'`, `'lint:no-console'`) so consumer categories flow through alongside the package's own `FindingCategory` values.
|
|
160
|
+
**Advisory, never blocking.** Its findings fold into the operation's detection findings and the verdict; a validator finding never triggers a re-resolve, and a *throwing* validator never destroys the resolution — it is kept intact and the failure is reported. On the resolve/report path that report is a `validation:failed` event (`scope` says whether it was a per-file or the whole-set call); inside the opt-in fix phase a validator throw folds into the phase's per-file isolation and surfaces as `fix:failed` (like any per-file throw there), so telemetry that must catch every validator failure should watch both. `ValidatorFinding.category` is an open string (e.g. `'type-error'`, `'lint:no-console'`) so consumer categories flow through alongside the package's own `FindingCategory` values.
|
|
155
161
|
|
|
156
162
|
The one blocking gate — **residual conflict markers** — is *not* this port's job. It is checked built-in and language-agnostic inside the resolve loop. (This was the old `defaultVerifier`; it is now internal. `defaultVerifier`, `ResolutionVerifier`, and `VerificationResult` are no longer exported.)
|
|
157
163
|
|
|
@@ -190,7 +196,7 @@ import { resolveConflict } from '@gitkraken/conflict-tools';
|
|
|
190
196
|
const resolution = await resolveConflict(conflict, context, {
|
|
191
197
|
model,
|
|
192
198
|
git,
|
|
193
|
-
validator, // optional — advisory
|
|
199
|
+
validator, // optional — advisory checks
|
|
194
200
|
config: { maxSteps: 15, temperature: 0, refineMaxSteps: 5, modelCallTimeoutMs: 300_000 },
|
|
195
201
|
onProgress: (event) => console.log(event),
|
|
196
202
|
});
|
|
@@ -260,7 +266,6 @@ A `merged` chunk is also checked for brackets: the `{}`, `()` and `[]` balance (
|
|
|
260
266
|
- `insert` — inserts `content` after the line matched by `anchor` at `startLine`.
|
|
261
267
|
- `replace` — replaces lines `startLine`–`endLine` (matched by `anchor`) with `content`.
|
|
262
268
|
- `delete` — removes lines `startLine`–`endLine` matched by `anchor`. No `content`.
|
|
263
|
-
- `verify` — confirms lines at `startLine` (optionally through `endLine`) match `anchor` without modifying content.
|
|
264
269
|
|
|
265
270
|
### `resolveConflicts(deps, context?) + applyResolutions(resolutions, deps)` — batch
|
|
266
271
|
|
|
@@ -272,7 +277,7 @@ const step = await resolveConflicts(
|
|
|
272
277
|
model,
|
|
273
278
|
git,
|
|
274
279
|
parser, // optional — supply to enable set-level detection (stage 3)
|
|
275
|
-
validator, // optional — advisory
|
|
280
|
+
validator, // optional — advisory checks
|
|
276
281
|
config: {
|
|
277
282
|
rules: [
|
|
278
283
|
{ match: ['*.lock', 'pnpm-lock.yaml'], strategy: 'take-theirs' },
|
|
@@ -430,7 +435,9 @@ The scope includes the resolved files *and* the merge's auto-merged/incoming set
|
|
|
430
435
|
```typescript
|
|
431
436
|
interface DetectionReport {
|
|
432
437
|
findings: Finding[];
|
|
433
|
-
checks: { referenceIntegrity: CheckStatus; structural: CheckStatus };
|
|
438
|
+
checks: { referenceIntegrity: CheckStatus; structural: CheckStatus; validator: ValidatorCheckStatus };
|
|
439
|
+
/** Why the whole-set validator did not run or threw. */
|
|
440
|
+
validatorReason?: string;
|
|
434
441
|
/** Honest coverage — present only when the reference check ran. */
|
|
435
442
|
scope?: { inScope: number; readable: number; truncated: boolean; incomingDiscovered: boolean };
|
|
436
443
|
}
|
|
@@ -480,7 +487,8 @@ import { buildOperationDigest } from '@gitkraken/conflict-tools';
|
|
|
480
487
|
|
|
481
488
|
const digest = buildOperationDigest(step);
|
|
482
489
|
// digest.verdict — 'clean' | 'needs-review' | 'blocked'
|
|
483
|
-
// digest.detectionComplete — did every detection check run to completion
|
|
490
|
+
// digest.detectionComplete — did every detection check run to completion (a supplied validator that did not run counts as incomplete)
|
|
491
|
+
// digest.validation — ValidatorCheckStatus of the whole-set validator call; digest.validationReason says why when it did not run
|
|
484
492
|
// digest.findingCount — total detection findings
|
|
485
493
|
// digest.needsReview — ReviewItem[] (findings first, then lowest confidence first)
|
|
486
494
|
// digest.minConfidence — lowest file confidence, or null
|
|
@@ -609,7 +617,7 @@ score(resolution, goldenFile);
|
|
|
609
617
|
| `refine:final-text` | Refine model emitted its final description (`text`) |
|
|
610
618
|
| `refine:completed` | Refine finished successfully (`appliedCount`) |
|
|
611
619
|
| `refine:failed` | Refine threw before completing (`reason`) |
|
|
612
|
-
| `refine:bailed` | Refine bailed early after consecutive apply errors, keeping edits that landed (`reason`) |
|
|
620
|
+
| `refine:bailed` | Refine bailed early after consecutive apply errors, or ran out of steps or model budget, keeping edits that landed (`reason` says which) |
|
|
613
621
|
| `refine:discarded` | A refine/fix edit batch was rejected — it reintroduced conflict markers, or (in the fix phase) it introduced an error the file did not have (`reason` says which) |
|
|
614
622
|
| `refine:lost-addition` | A side's added lines are missing from the resolution (`side`, `startLine`, `endLine`, `lines`, `phase`: `before-refine` or `after-refine`) |
|
|
615
623
|
| `refine:bracket-imbalance` | A merged chunk's bracket balance matches neither side nor the base (`markerIndex`, `pair`, `merged`, `ours`, `theirs`) |
|
|
@@ -618,7 +626,8 @@ score(resolution, goldenFile);
|
|
|
618
626
|
| `fix:completed` | The fix loop finished evaluating a file (`iterations`, `remainingFindings`) — emitted even for a clean file (iterations 0, remainingFindings 0). `iterations: 0` means "evaluated, spent no model call" |
|
|
619
627
|
| `fix:failed` | A non-abort error in the opt-in fix phase (`filePath?`, `reason`). With `filePath`: that one file threw and was left at its pre-fix resolution while the phase carried on — detection still runs. Without `filePath`: the whole phase threw before it could report, so both checks are forced to `errored` |
|
|
620
628
|
| `fix:budget-exhausted` | The global `maxModelCalls` budget was spent (`spent`, `limit`); the fix phase stopped and left the remainder in findings |
|
|
621
|
-
| `validation:failed` | The consumer validator threw; reported and swallowed, resolution kept (`reason`) |
|
|
629
|
+
| `validation:failed` | The consumer validator threw; reported and swallowed, resolution kept (`scope`: `file` or `set`, `filePath?` — absent for the set call, `reason`) |
|
|
630
|
+
| `validation:not-run` | The consumer validator answered `not-run` (`scope`, `filePath?` — absent for the set call, `reason`) |
|
|
622
631
|
|
|
623
632
|
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`.
|
|
624
633
|
|
package/dist/index.d.ts
CHANGED
|
@@ -11,12 +11,12 @@ import { ToolCall, ToolDefinition, ToolResult } from "./types/tool.js";
|
|
|
11
11
|
import { ConflictModelMessage, ConflictModelParams, ConflictModelPort, ConflictModelResult, ResolverConfig } from "./ports/model.js";
|
|
12
12
|
import { ParsedFile, ParserCapabilities, ParserPort, SourceOverlay, SymbolDefinition, SymbolReference, SyntaxIssue } from "./ports/parser.js";
|
|
13
13
|
import { Validator, ValidatorFinding } from "./ports/validator.js";
|
|
14
|
+
import { ConflictProgressEvent } from "./types/events.js";
|
|
14
15
|
import { DetectDeps, detect } from "./reference-integrity/detect.js";
|
|
15
16
|
import { createResolution } from "./resolution/create-resolution.js";
|
|
16
17
|
import { CodeLossDetection, detectFallbackCodeLoss } from "./resolution/detect-code-loss.js";
|
|
17
18
|
import { takeOurs, takeTheirs } from "./resolution/take-strategy.js";
|
|
18
19
|
import { applyResolutions } from "./resolver/apply-resolutions.js";
|
|
19
|
-
import { ConflictProgressEvent } from "./types/events.js";
|
|
20
20
|
import { resolveConflict } from "./resolver/resolve-conflict.js";
|
|
21
21
|
import { FailedEntry, FailureReason, FileRule, SkippedEntry, StepConfig, StepResult } from "./types/step.js";
|
|
22
22
|
import { resolveConflicts } from "./resolver/resolve-conflicts.js";
|
|
@@ -12,18 +12,34 @@ interface ValidatorFinding {
|
|
|
12
12
|
line?: number;
|
|
13
13
|
message: string;
|
|
14
14
|
}
|
|
15
|
+
interface ValidationCall {
|
|
16
|
+
/** `file`: one file's content, for a per-file check or a parent baseline. `set`: every resolved file of the step, once, after all files are resolved. */
|
|
17
|
+
scope: 'file' | 'set';
|
|
18
|
+
/** Which content a `file` call holds. Always `result` for `set`. */
|
|
19
|
+
role: 'result' | 'ours' | 'theirs';
|
|
20
|
+
/** `set` only: paths the step deletes. They are absent from `sources`; treat them as removed. */
|
|
21
|
+
deleted?: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
interface ValidatorNotRun {
|
|
24
|
+
status: 'not-run';
|
|
25
|
+
/** Short, human-readable: "siblings still conflicted", "unsupported language", "go.mod conflicted". */
|
|
26
|
+
reason: string;
|
|
27
|
+
}
|
|
28
|
+
type ValidatorOutcome = ValidatorFinding[] | ValidatorNotRun;
|
|
15
29
|
/**
|
|
16
30
|
* The consumer's validation port. Plug in any checks — syntax, real import resolution, types, lint,
|
|
17
31
|
* project rules. It runs after refine; the opt-in fix loop may route new error-severity findings back
|
|
18
32
|
* through refine. Residual conflict markers are the resolver's blocking gate and are checked separately.
|
|
19
33
|
*
|
|
20
|
-
* `
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
34
|
+
* `call` says what `sources` holds. Per-file calls (`scope: 'file'`) hold one file; `role` says which
|
|
35
|
+
* version: the `result`, or a pre-merge parent (`ours`/`theirs`) to build a baseline. The whole-set call
|
|
36
|
+
* (`scope: 'set'`) holds every resolved file of the step and runs once, after all files are resolved (and
|
|
37
|
+
* after the fix phase, when it runs); it has no baseline. Validate the content supplied rather than reading
|
|
38
|
+
* the working tree. A validator that cannot check a single file in isolation returns
|
|
39
|
+
* `{ status: 'not-run', reason }` for `file` calls and does its work on the `set` call.
|
|
24
40
|
*/
|
|
25
41
|
interface Validator {
|
|
26
|
-
validate(sources: SourceOverlay, signal
|
|
42
|
+
validate(sources: SourceOverlay, signal: AbortSignal | undefined, call: ValidationCall): Promise<ValidatorOutcome>;
|
|
27
43
|
}
|
|
28
44
|
//#endregion
|
|
29
|
-
export { Validator, ValidatorFinding };
|
|
45
|
+
export { ValidationCall, Validator, ValidatorFinding, ValidatorNotRun, ValidatorOutcome };
|
|
@@ -3,6 +3,7 @@ import { DetectionReport, Finding } from "../types/findings.js";
|
|
|
3
3
|
import { Resolution, ResolutionContext } from "../types/resolution.js";
|
|
4
4
|
import { ParserPort } from "../ports/parser.js";
|
|
5
5
|
import { Validator } from "../ports/validator.js";
|
|
6
|
+
import { ConflictProgressEvent } from "../types/events.js";
|
|
6
7
|
|
|
7
8
|
//#region src/reference-integrity/detect.d.ts
|
|
8
9
|
interface DetectDeps {
|
|
@@ -34,16 +35,7 @@ interface FileFindingsDeps {
|
|
|
34
35
|
parser?: ParserPort;
|
|
35
36
|
validator?: Validator;
|
|
36
37
|
signal?: AbortSignal;
|
|
37
|
-
|
|
38
|
-
/** Per-file findings split by who produced them. They are kept apart because only the `text` and
|
|
39
|
-
* `validator` halves are the file's own to keep: parser findings computed over a *single-file* overlay are
|
|
40
|
-
* provisional — a symbol defined in another file is unresolved there by construction — so the
|
|
41
|
-
* authoritative version is whatever `detect()` says over the whole set. The fix loop uses all of them to
|
|
42
|
-
* converge and persists `text` and `validator`. */
|
|
43
|
-
interface FileFindingsDeps {
|
|
44
|
-
parser?: ParserPort;
|
|
45
|
-
validator?: Validator;
|
|
46
|
-
signal?: AbortSignal;
|
|
38
|
+
onProgress?: (event: ConflictProgressEvent) => void;
|
|
47
39
|
}
|
|
48
40
|
/** Per-file findings split by who produced them. They are kept apart because only the `text` and
|
|
49
41
|
* `validator` halves are the file's own to keep: parser findings computed over a *single-file* overlay are provisional — a
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{duplicateBlockFindings as e}from"../resolution/duplicate-blocks.js";import{toFileFinding as t}from"../validation/findings.js";import{buildSnapshots as n,carriesNoOwnContent as r,safeReadFile as i}from"./snapshot.js";async function a(e,t,a){let s=a.parser;if(!s)return{findings:[],checks:{referenceIntegrity:`skipped-no-parser`,structural:`skipped-no-parser`}};try{let c=t.detectionRefs??t.refs?await n(e,t,a):null;if(c){let t=await s.analyze(c.ours,a.signal),n=await s.analyze(c.theirs,a.signal),r=await s.analyze(c.result,a.signal);return{findings:[...u(t,n,r).findings,...f(r,[t,n]).findings,...m(e,c)],checks:{referenceIntegrity:o(c,s,[t,n,r]),structural:g(r)?`partial-unsupported-language`:`ran`},scope:{inScope:c.inScope,readable:c.readable,truncated:c.truncated,incomingDiscovered:c.incomingDiscovered}}}let l=new Map;for(let t of e){let e=r(t)?await i(a.git,t.filePath,{signal:a.signal}):t.content;e!=null&&l.set(t.filePath,e)}let d=await s.analyze(l,a.signal);return{findings:f(d,[]).findings,checks:{referenceIntegrity:`skipped-no-refs`,structural:g(d)?`partial-unsupported-language`:`ran`}}}catch(e){if(a.signal?.aborted)throw e;return{findings:[],checks:{referenceIntegrity:`errored`,structural:`errored`}}}}function o(e,t,n){return e.inScope>0&&e.readable===0?`skipped-unreadable-source`:t.capabilities?.crossFileResolution?n.some(g)?`partial-unsupported-language`:`ran`:`partial-no-cross-file-resolution`}async function s(n,r,i,a){let o=a.parser,s=[],l=[];if(o){let e=async e=>e==null?new Map:o.analyze(new Map([[n,e]]),a.signal),t=await e(r.ours),c=await e(r.theirs),d=await o.analyze(new Map([[n,i]]),a.signal),p=u(t,c,d),m=f(d,[t,c]);s=[...p.findings.filter(e=>e.tier===`file`),...m.findings],l.push(...p.preExisting.filter(e=>e.tier===`file`),...m.preExisting)}let d=[],p=a.validator;if(p){let e=async
|
|
1
|
+
import{duplicateBlockFindings as e}from"../resolution/duplicate-blocks.js";import{toFileFinding as t}from"../validation/findings.js";import{buildSnapshots as n,carriesNoOwnContent as r,safeReadFile as i}from"./snapshot.js";async function a(e,t,a){let s=a.parser;if(!s)return{findings:[],checks:{referenceIntegrity:`skipped-no-parser`,structural:`skipped-no-parser`,validator:`skipped-no-validator`}};try{let c=t.detectionRefs??t.refs?await n(e,t,a):null;if(c){let t=await s.analyze(c.ours,a.signal),n=await s.analyze(c.theirs,a.signal),r=await s.analyze(c.result,a.signal);return{findings:[...u(t,n,r).findings,...f(r,[t,n]).findings,...m(e,c)],checks:{referenceIntegrity:o(c,s,[t,n,r]),structural:g(r)?`partial-unsupported-language`:`ran`,validator:`skipped-no-validator`},scope:{inScope:c.inScope,readable:c.readable,truncated:c.truncated,incomingDiscovered:c.incomingDiscovered}}}let l=new Map;for(let t of e){let e=r(t)?await i(a.git,t.filePath,{signal:a.signal}):t.content;e!=null&&l.set(t.filePath,e)}let d=await s.analyze(l,a.signal);return{findings:f(d,[]).findings,checks:{referenceIntegrity:`skipped-no-refs`,structural:g(d)?`partial-unsupported-language`:`ran`,validator:`skipped-no-validator`}}}catch(e){if(a.signal?.aborted)throw e;return{findings:[],checks:{referenceIntegrity:`errored`,structural:`errored`,validator:`skipped-no-validator`}}}}function o(e,t,n){return e.inScope>0&&e.readable===0?`skipped-unreadable-source`:t.capabilities?.crossFileResolution?n.some(g)?`partial-unsupported-language`:`ran`:`partial-no-cross-file-resolution`}async function s(n,r,i,a){let o=a.parser,s=[],l=[];if(o){let e=async e=>e==null?new Map:o.analyze(new Map([[n,e]]),a.signal),t=await e(r.ours),c=await e(r.theirs),d=await o.analyze(new Map([[n,i]]),a.signal),p=u(t,c,d),m=f(d,[t,c]);s=[...p.findings.filter(e=>e.tier===`file`),...m.findings],l.push(...p.preExisting.filter(e=>e.tier===`file`),...m.preExisting)}let d=[],p=a.validator;if(p){let e=async(e,r)=>{let i=await p.validate(new Map([[n,e]]),a.signal,{scope:`file`,role:r});return Array.isArray(i)?i.map(t):(a.onProgress?.({type:`validation:not-run`,filePath:n,scope:`file`,reason:i.reason}),[])},o=[];r.ours!=null&&o.push(await e(r.ours,`ours`)),r.theirs!=null&&o.push(await e(r.theirs,`theirs`));let s=c(await e(i,`result`),o,e=>JSON.stringify([e.category,e.message]));d=s.kept,l.push(...s.preExisting)}return{parser:s,validator:d,preExisting:l,text:e(n,i,r)}}function c(e,t,n){let r=new Map;for(let e of t){let t=new Map;for(let r of e){let e=n(r);t.set(e,(t.get(e)??0)+1)}for(let[e,n]of t)r.set(e,Math.max(r.get(e)??0,n))}let i=[],a=[];for(let t of[...e].sort((e,t)=>e.location.line-t.location.line)){let e=n(t),o=r.get(e)??0;o>0?(a.push(t),r.set(e,o-1)):i.push(t)}return{kept:i,preExisting:a}}function l(e){let t=new Set;for(let n of e.definitions)t.add(n.name);for(let n of e.bindings??[])t.add(n.name);return t}function u(e,t,n){let r=d(e,t),i=[],a=[];for(let[o,s]of n){let n=l(s);for(let c of s.references){if(c.resolved||n.has(c.name))continue;let s=r.get(_(o,c.name));s&&([e,t].some(e=>{let t=e.get(o);return t!=null&&!l(t).has(c.name)&&t.references.some(e=>e.name===c.name&&!e.resolved)})?a:i).push({category:`broken-reference`,severity:`error`,tier:s.crossFile?`project`:`file`,location:{file:o,line:c.line,symbol:c.name},provenance:{side:s.side,line:s.line},...s.crossFile&&s.targetFile?{definitionFile:s.targetFile}:{},message:`Reference "${c.name}" resolved before the merge (on ${s.side}) but no longer resolves.`})}}return{findings:i,preExisting:a}}function d(e,t){let n=new Map,r=(e,t,r,i,a,o)=>{let s=_(t,r);n.has(s)||n.set(s,{side:e,line:i,crossFile:a,targetFile:o})},i=[[`ours`,e],[`theirs`,t]];for(let[e,t]of i)for(let[n,i]of t){for(let t of i.definitions)r(e,n,t.name,t.line,!1);for(let t of i.bindings??[])r(e,n,t.name,t.line,!1)}for(let[e,t]of i)for(let[n,i]of t)for(let t of i.references)t.resolved&&r(e,n,t.name,t.line,t.target?t.target.file!==n:!0,t.target?.file);return n}function f(e,t){let n=p(t),r=new Set;for(let e of t)for(let t of e.keys())r.add(t);let i=[],a=[];for(let[o,s]of e){let e=r.has(o),l=new Map;for(let t of s.definitions){let r=l.get(t.name);if(r==null){l.set(t.name,t.line);continue}!e||n.has(_(o,t.name))||i.push({category:`duplicate-definition`,severity:`warning`,tier:`file`,location:{file:o,line:t.line,symbol:t.name},message:`Duplicate definition of "${t.name}" (also defined at line ${r}).`})}let u=e=>e.map(e=>({category:`syntax-error`,severity:`error`,tier:`file`,location:{file:o,line:e.line},message:e.message})),d=t.map(e=>e.get(o)?.syntaxErrors??[]),f=c(u(s.syntaxErrors),d.map(u),e=>e.message),p=d.some(e=>e.length>0);for(let e of f.kept)p&&(e.severity=`warning`),i.push(e);a.push(...f.preExisting)}return{findings:i,preExisting:a}}function p(e){let t=new Set;for(let n of e)for(let[e,r]of n){let n=new Set;for(let i of r.definitions){let r=_(e,i.name);n.has(r)&&t.add(r),n.add(r)}}return t}function m(e,t){let n=[];for(let r of e){if(!r.fallback||r.strategy!==`take-ours`&&r.strategy!==`take-theirs`)continue;let e=r.strategy===`take-ours`?`theirs`:`ours`,i=(e===`ours`?t.ours:t.theirs).get(r.filePath);if(i==null)continue;let a=h(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 h(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 g(e){for(let t of e.values())if(!t.supported)return!0;return!1}function _(e,t){return`${e}\u0000${t}`}export{s as collectFileFindings,a as detect};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DetectionReport, Finding, ValidatorCheckStatus } from "../types/findings.js";
|
|
2
|
+
import { Resolution } from "../types/resolution.js";
|
|
3
|
+
import { Validator } from "../ports/validator.js";
|
|
4
|
+
import { ConflictProgressEvent } from "../types/events.js";
|
|
5
|
+
import { DetectionSnapshots } from "./snapshot.js";
|
|
6
|
+
|
|
7
|
+
//#region src/reference-integrity/validate-set.d.ts
|
|
8
|
+
interface SetValidation {
|
|
9
|
+
findings: Finding[];
|
|
10
|
+
status: ValidatorCheckStatus;
|
|
11
|
+
reason?: string;
|
|
12
|
+
}
|
|
13
|
+
interface SetValidationDeps {
|
|
14
|
+
validator?: Validator;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
onProgress?: (event: ConflictProgressEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
/** The one whole-set validator call of an operation: every resolution's final content, and the paths the
|
|
19
|
+
* step deletes. A resolution carrying no content of its own contributes the taken side's content from the
|
|
20
|
+
* snapshots when they hold it, and is left out otherwise. Never throws except on an aborted signal. */
|
|
21
|
+
declare function validateResolvedSet(resolutions: Resolution[], snapshots: DetectionSnapshots | null, deps: SetValidationDeps): Promise<SetValidation>;
|
|
22
|
+
declare function withSetValidation(detection: DetectionReport, set: SetValidation): DetectionReport;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { SetValidation, SetValidationDeps, validateResolvedSet, withSetValidation };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{toFileFinding as e}from"../validation/findings.js";import{carriesNoOwnContent as t}from"./snapshot.js";async function n(n,r,i){let a=i.validator;if(!a)return{findings:[],status:`skipped-no-validator`};let o=new Map,s=[];for(let e of n){if(e.strategy===`deleted`){s.push(e.filePath,...e.retiredPaths??[]);continue}let n=t(e)?r?.result.get(e.filePath):e.content;n!=null&&o.set(e.filePath,n)}if(o.size===0&&s.length===0)return{findings:[],status:`ran`};try{let t=await a.validate(o,i.signal,{scope:`set`,role:`result`,deleted:s});return Array.isArray(t)?{findings:t.map(e),status:`ran`}:(i.onProgress?.({type:`validation:not-run`,scope:`set`,reason:t.reason}),{findings:[],status:`not-run`,reason:t.reason})}catch(e){if(i.signal?.aborted)throw e;let t=e instanceof Error?e.message:String(e);return i.onProgress?.({type:`validation:failed`,scope:`set`,reason:t}),{findings:[],status:`errored`,reason:t}}}function r(e,t){return{...e,findings:[...e.findings,...t.findings],checks:{...e.checks,validator:t.status},...t.reason?{validatorReason:t.reason}:{}}}export{n as validateResolvedSet,r as withSetValidation};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
function e(e,t){let n=e.split(`
|
|
2
|
-
`);if(t.kind===`insert`){if(t.startLine<1||t.startLine>n.length)return{valid:!1,reason:`out-of-bounds`};let e=n[t.startLine-1]??``;return e===t.anchor?{valid:!0}:{valid:!1,reason:`anchor-mismatch`,expected:t.anchor,actual:e}}
|
|
3
|
-
`);return
|
|
4
|
-
`);
|
|
5
|
-
`),u=[];for(let e of c){let t=r[e];if(t){if(t.kind===`verify`){i.set(e,{opIndex:e,status:`verified`});continue}t.kind===`insert`?l.splice(t.startLine,0,...t.content.split(`
|
|
2
|
+
`);if(t.kind===`insert`){if(t.startLine<1||t.startLine>n.length)return{valid:!1,reason:`out-of-bounds`};let e=n[t.startLine-1]??``;return e===t.anchor?{valid:!0}:{valid:!1,reason:`anchor-mismatch`,expected:t.anchor,actual:e}}let r=t.endLine;if(t.startLine<1||t.startLine>r||r>n.length)return{valid:!1,reason:`out-of-bounds`};let i=n.slice(t.startLine-1,r).join(`
|
|
3
|
+
`);return i===t.anchor?{valid:!0}:{valid:!1,reason:`anchor-mismatch`,expected:t.anchor,actual:i}}function t(e,t){if(e.kind===`insert`&&t.kind===`insert`)return!1;let n=e.startLine,r=e.kind===`insert`?e.startLine:e.endLine,i=t.startLine;return n<=(t.kind===`insert`?t.startLine:t.endLine)&&i<=r}function n(n,r){let i=new Map,a=r.map(t=>e(n,t));for(let e=0;e<r.length;e++){let t=a[e];t&&!t.valid&&(t.reason===`anchor-mismatch`?i.set(e,{opIndex:e,status:`skipped`,reason:`anchor-mismatch`,expected:t.expected,actual:t.actual}):i.set(e,{opIndex:e,status:`skipped`,reason:t.reason}))}let o=[];for(let e=0;e<r.length;e++)i.has(e)||o.push(e);let s=new Set;for(let e=0;e<o.length;e++)for(let n=e+1;n<o.length;n++){let i=o[e],a=o[n],c=i===void 0?void 0:r[i],l=a===void 0?void 0:r[a];i!==void 0&&a!==void 0&&c&&l&&t(c,l)&&(s.add(i),s.add(a))}for(let e of s)i.set(e,{opIndex:e,status:`skipped`,reason:`overlap`});let c=o.filter(e=>!s.has(e));c.sort((e,t)=>(r[t]?.startLine??0)-(r[e]?.startLine??0));let l=n.split(`
|
|
4
|
+
`),u=[];for(let e of c){let t=r[e];t&&(t.kind===`insert`?l.splice(t.startLine,0,...t.content.split(`
|
|
6
5
|
`)):t.kind===`replace`?l.splice(t.startLine-1,t.endLine-t.startLine+1,...t.content.split(`
|
|
7
|
-
`)):l.splice(t.startLine-1,t.endLine-t.startLine+1),u.push(t),i.set(e,{opIndex:e,status:`applied`})}
|
|
6
|
+
`)):l.splice(t.startLine-1,t.endLine-t.startLine+1),u.push(t),i.set(e,{opIndex:e,status:`applied`}))}let d=[];for(let e=0;e<r.length;e++){let t=i.get(e);t&&d.push(t)}return{content:l.join(`
|
|
8
7
|
`),edits:u,results:d}}export{n as applyEditOps};
|
|
@@ -4,8 +4,8 @@ import { Resolution, ResolutionContext } from "../types/resolution.js";
|
|
|
4
4
|
import { ConflictModelPort } from "../ports/model.js";
|
|
5
5
|
import { ParserPort } from "../ports/parser.js";
|
|
6
6
|
import { Validator } from "../ports/validator.js";
|
|
7
|
-
import { ParentContents } from "../reference-integrity/detect.js";
|
|
8
7
|
import { ConflictProgressEvent } from "../types/events.js";
|
|
8
|
+
import { ParentContents } from "../reference-integrity/detect.js";
|
|
9
9
|
import { StepConfig } from "../types/step.js";
|
|
10
10
|
import { DetectionSnapshots } from "../reference-integrity/snapshot.js";
|
|
11
11
|
import { ModelBudget } from "./model-budget.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{buildSnapshots as e}from"../reference-integrity/snapshot.js";import{collectFileFindings as t,detect as n}from"../reference-integrity/detect.js";import{checkResidualMarkers as r}from"../validation/markers.js";import{matchFileGuidance as i}from"./file-guidance.js";import{runRefinePass as a}from"./refine-pass.js";import{CONFIDENCE_CAPS as o}from"./resolve-conflict.js";async function
|
|
2
|
-
`)}function
|
|
1
|
+
import{buildSnapshots as e}from"../reference-integrity/snapshot.js";import{collectFileFindings as t,detect as n}from"../reference-integrity/detect.js";import{checkResidualMarkers as r}from"../validation/markers.js";import{matchFileGuidance as i}from"./file-guidance.js";import{runRefinePass as a}from"./refine-pass.js";import{CONFIDENCE_CAPS as o}from"./resolve-conflict.js";import{validateResolvedSet as s,withSetValidation as c}from"../reference-integrity/validate-set.js";async function l(e,n,s){let c=s.config?.fixLoopMaxIterations??3,l={parser:s.parser,validator:s.validator,signal:s.signal,onProgress:s.onProgress},g={content:e.content,findings:await(r=>t(e.filePath,n,r,l))(e.content)};g.findings.preExisting.length>0&&s.onProgress?.({type:`findings:pre-existing`,filePath:e.filePath,findings:g.findings.preExisting});let _=f(e,g.findings),v=d(g.findings);if(v.length===0)return m(s,e.filePath,0,0),p(e,_,g.findings);if(s.budget?.exhausted())return s.onBudgetExhausted?.(),m(s,e.filePath,0,v.length),u(p(e,_,g.findings),v);s.onProgress?.({type:`fix:started`,filePath:e.filePath,findingCount:v.length});let x=[],S=0,C=y(v);for(;S<c&&v.length>0;){if(s.budget?.exhausted()){s.onBudgetExhausted?.();break}S++;let o=await a(g.content,e.description,h(v),{model:s.model,signal:s.signal,temperature:s.config?.temperature,refineMaxSteps:s.config?.refineMaxSteps,modelCallTimeoutMs:s.config?.modelCallTimeoutMs,onProgress:s.onProgress,filePath:e.filePath,fileGuidance:i(s.fileGuidance,[e.filePath,...e.retiredPaths??[]]),budget:s.budget});if(o.edits.length===0)break;if(!r(e.filePath,o.content).valid){s.onProgress?.({type:`refine:discarded`,filePath:e.filePath,reason:`Fix edits introduced conflict markers.`});break}let c=await t(e.filePath,n,o.content,l),u=d(c);if(b(g.findings,c)){s.onProgress?.({type:`refine:discarded`,filePath:e.filePath,reason:`Fix edits regressed the file (was ${v.length} error(s), now ${u.length}).`});break}g={content:o.content,findings:c},x.push(...o.edits),v=u;let f=y(v);if(f===C)break;C=f}m(s,e.filePath,S,v.length);let w=p({...e,content:g.content},_,g.findings),T=d(g.findings);return x.length===0?u(w,T):u({...w,strategy:`ai`,edits:[...e.edits??[],...x],confidence:Math.min(w.confidence,o.refineApplied)},T)}function u(e,t){let n=t.filter(e=>e.severity===`error`);if(n.length===0)return e;let r=[...n].sort((e,t)=>e.location.line-t.location.line).slice(0,3).map(e=>`${e.category} at line ${e.location.line}`).join(`, `),i=n.length>3?`, and ${n.length-3} more`:``;return{...e,confidence:Math.min(e.confidence,o.errorsRemain),description:`Unresolved: ${n.length} error(s) remain after the fix pass (${r}${i}); fix them by hand before committing. ${e.description}`}}function d(e){return[...e.parser,...e.validator,...e.text].filter(e=>e.severity===`error`||e.category===`duplicate-block`)}function f(e,t){let n=(e,t)=>e.category===t.category&&e.location.file===t.location.file&&e.location.line===t.location.line&&e.location.symbol===t.location.symbol&&e.message===t.message;return(e.findings??[]).filter(e=>e.category!==`duplicate-block`&&![...t.validator,...t.preExisting].some(t=>n(e,t)))}function p(e,t,n){let r=[...t,...n.text,...n.validator];if(r.length===0){if(!e.findings)return e;let{findings:t,...n}=e;return n}return{...e,findings:r}}function m(e,t,n,r){e.onProgress?.({type:`fix:completed`,filePath:t,iterations:n,remainingFindings:r})}function h(e){return e.slice(0,20).map(g)}function g(e){let t=e.location.symbol?_(e.location.symbol):void 0,n=t?`"${t}" (line ${e.location.line})`:`line ${e.location.line}`;return`${_(e.category)} at ${n}: ${_(e.message)}`}function _(e){return e.replace(/[\p{Cc}\p{Cf}]/gu,` `).replace(/\s+/g,` `).trim().slice(0,300)}function v(e){return JSON.stringify([e.category,e.location.file,e.location.symbol??``])}function y(e){return e.map(v).sort().join(`
|
|
2
|
+
`)}function b(e,t){let n=e=>[...e.parser,...e.validator,...e.text].filter(e=>e.severity===`error`||e.category===`syntax-error`||e.category===`duplicate-block`),r=n(e),i=n(t);if(i.length>r.length)return!0;let a=new Set(r.map(v));return i.some(e=>!a.has(v(e)))}async function x(t,r,i){let a=i.config?.maxDetectionScopeFiles,o={git:i.git,parser:i.parser,signal:i.signal,excludePaths:i.excludePaths,maxScopeFiles:a},l=await e(t,r,{git:i.git,signal:i.signal,excludePaths:i.excludePaths,maxScopeFiles:a}),u=()=>i.onProgress?.({type:`fix:budget-exhausted`,spent:i.budget?.spent()??0,limit:i.budget?.limit()??0}),d={model:i.model,fileGuidance:r.fileGuidance,parser:i.parser,validator:i.validator,config:i.config,onProgress:i.onProgress,signal:i.signal,budget:i.budget,onBudgetExhausted:()=>i.budget?.notifyExhaustedOnce(u)};return await C(t,l,d),c(l?await w(t,r,l,o,d,i):await n(t,r,o),await s(t,l,{validator:i.validator,signal:i.signal,onProgress:i.onProgress}))}function S(e){return e.strategy===`ai`?!0:e.strategy===`take-ours`||e.strategy===`take-theirs`?!e.fallback&&!e.deliberateTake:!1}async function C(e,t,n){for(let r=0;r<e.length;r++){let i=e[r];if(S(i)){if(n.signal?.aborted)return;try{e[r]=await l(i,T(t,i.filePath),n)}catch(e){if(n.signal?.aborted)throw e;n.onProgress?.({type:`fix:failed`,filePath:i.filePath,reason:e instanceof Error?e.message:String(e)})}}}}async function w(e,t,r,i,a,o){let s=o.config?.crossFileMaxIterations??2,c=await n(e,t,i);for(let l=0;l<s&&!o.signal?.aborted;l++){if(o.budget?.exhausted()){a.onBudgetExhausted?.();break}let s=D(c.findings,e);if(s.size===0)break;let l=!1;for(let[t,n]of s){if(o.budget?.exhausted()){a.onBudgetExhausted?.();break}let i=e.findIndex(e=>e.filePath===t);if(i<0)continue;let s=e[i],c=s.content;try{let o=await E(s,n,T(r,t),r.result,a);e[i]=o,o.content!==c&&(l=!0)}catch(e){if(o.signal?.aborted)throw e;o.onProgress?.({type:`fix:failed`,filePath:t,reason:e instanceof Error?e.message:String(e)})}}if(!l)break;c=await n(e,t,i)}return c}function T(e,t){return{ours:e?.ours.get(t),theirs:e?.theirs.get(t)}}async function E(e,n,s,c,l){let u={parser:l.parser,validator:l.validator,signal:l.signal,onProgress:l.onProgress},d=await t(e.filePath,s,e.content,u),m=l.parser?await O(n,c,l.parser,l.signal):void 0,g=await a(e.content,e.description,h(n),{model:l.model,signal:l.signal,temperature:l.config?.temperature,refineMaxSteps:l.config?.refineMaxSteps,modelCallTimeoutMs:l.config?.modelCallTimeoutMs,onProgress:l.onProgress,filePath:e.filePath,fileGuidance:i(l.fileGuidance,[e.filePath,...e.retiredPaths??[]]),budget:l.budget,relatedDefinitions:m});if(g.edits.length===0)return e;if(!r(e.filePath,g.content).valid)return l.onProgress?.({type:`refine:discarded`,filePath:e.filePath,reason:`Cross-file fix introduced conflict markers.`}),e;let _=await t(e.filePath,s,g.content,u);return b(d,_)?(l.onProgress?.({type:`refine:discarded`,filePath:e.filePath,reason:`Cross-file fix left the file with a new or additional error.`}),e):p({...e,content:g.content,strategy:`ai`,edits:[...e.edits??[],...g.edits],confidence:Math.min(e.confidence,o.refineApplied)},f(e,d),_)}function D(e,t){let n=new Set(t.filter(S).map(e=>e.filePath)),r=new Map;for(let t of e){if(t.tier!==`project`||t.severity!==`error`||!n.has(t.location.file))continue;let e=r.get(t.location.file)??[];e.push(t),r.set(t.location.file,e)}return r}async function O(e,t,n,r){let i=new Set;for(let n of e){let e=n.definitionFile;e&&t.has(e)&&i.add(e)}if(i.size===0)return;let a=[];for(let e of i){let i=t.get(e);if(i==null)continue;let o=(await n.analyze(new Map([[e,i]]),r)).get(e);if(!o||o.definitions.length===0)continue;let s=o.definitions.slice(0,50).map(e=>{let t=_(e.name),n=e.kind?_(e.kind):void 0;return n?`${t} (${n}, line ${e.line})`:`${t} (line ${e.line})`});a.push({file:_(e),symbols:s})}return a.length>0?a:void 0}export{S as isFixable,T as parentsOf,l as runFixLoop,x as runFixPhase};
|
package/dist/resolver/prompt.js
CHANGED
|
@@ -125,11 +125,10 @@ When the user message says "Delete-modify conflict", one side deleted the file w
|
|
|
125
125
|
|
|
126
126
|
## Optional Refinement Phase (Phase 2)
|
|
127
127
|
|
|
128
|
-
After you respond, an optional Phase 2 may run on the resolved file. Phase 2 can apply targeted line-level edits using an \`edit_file_lines\` tool with
|
|
128
|
+
After you respond, an optional Phase 2 may run on the resolved file. Phase 2 can apply targeted line-level edits using an \`edit_file_lines\` tool with three operation kinds:
|
|
129
129
|
- \`insert\`: add new lines after a specified line
|
|
130
130
|
- \`replace\`: rewrite a line range
|
|
131
131
|
- \`delete\`: remove a line range
|
|
132
|
-
- \`verify\`: confirm specific lines are present without modifying them
|
|
133
132
|
|
|
134
133
|
Phase 2 is driven by the \`followUpInstructions\` you provide. This means you should:
|
|
135
134
|
- Prefer a clean base (file-level "ours"/"theirs" or simple per-chunk resolution) + specific followUpInstructions over large "merged" chunks that attempt to inline everything.
|
|
@@ -166,7 +165,6 @@ The \`edit_file_lines\` tool accepts an \`ops\` array of structured operations.
|
|
|
166
165
|
- \`insert\`: inserts \`content\` after \`startLine\`. Anchor must equal the text of line \`startLine\`. No \`endLine\`.
|
|
167
166
|
- \`replace\`: replaces lines \`startLine\`–\`endLine\` (inclusive) with \`content\`. Anchor must equal the joined text of those lines.
|
|
168
167
|
- \`delete\`: deletes lines \`startLine\`–\`endLine\` (inclusive). Anchor must equal the joined text of those lines.
|
|
169
|
-
- \`verify\`: asserts that lines \`startLine\`–\`endLine\` (or just \`startLine\`) match the anchor WITHOUT modifying them. Use to confirm critical content is present. No \`content\` field needed.
|
|
170
168
|
|
|
171
169
|
All ops in a single call are validated atomically server-side. If any anchor mismatches or range is out-of-bounds, no changes are applied and the error is returned.
|
|
172
170
|
|
|
@@ -176,8 +174,7 @@ Example call:
|
|
|
176
174
|
"ops": [
|
|
177
175
|
{ "kind": "insert", "startLine": 3, "anchor": "import { Foo } from './foo';", "content": "import { Bar } from './bar';" },
|
|
178
176
|
{ "kind": "replace", "startLine": 10, "endLine": 12, "anchor": " old line A\\n old line B\\n old line C", "content": " new merged line" },
|
|
179
|
-
{ "kind": "delete", "startLine": 20, "endLine": 21, "anchor": " duplicate line\\n duplicate line 2" }
|
|
180
|
-
{ "kind": "verify", "startLine": 15, "anchor": " const result = await fetchData();" }
|
|
177
|
+
{ "kind": "delete", "startLine": 20, "endLine": 21, "anchor": " duplicate line\\n duplicate line 2" }
|
|
181
178
|
]
|
|
182
179
|
}
|
|
183
180
|
\`\`\`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{sumUsages as e}from"../resolution/metrics.js";import{buildRefinePrompt as t}from"./prompt.js";import{generateWithTimeout as n}from"./model-timeout.js";import{createRefineTools as r}from"./refine-tools.js";async function i(i,a,o,s){let c=s.refineMaxSteps??5,{system:l,userMessage:u}=t(i,a,o,s.relatedDefinitions,s.fileGuidance),{definitions:d,execute:f}=r(),p=[{role:`user`,content:u}],m=i,h=[],g=0,_=
|
|
1
|
+
import{sumUsages as e}from"../resolution/metrics.js";import{buildRefinePrompt as t}from"./prompt.js";import{generateWithTimeout as n}from"./model-timeout.js";import{createRefineTools as r}from"./refine-tools.js";async function i(i,a,o,s){let c=s.refineMaxSteps??5,{system:l,userMessage:u}=t(i,a,o,s.relatedDefinitions,s.fileGuidance),{definitions:d,execute:f}=r(),p=[{role:`user`,content:u}],m=i,h=[],g=0,_=[],v=t=>({edits:h,content:m,description:t,usage:e(_)});for(let e=0;e<c;e++){if(s.budget?.exhausted())return s.onProgress?.({type:`refine:bailed`,filePath:s.filePath,reason:`Stopped: model budget exhausted.`}),v(``);let t=e+1,r=await n(s.model,{system:l,messages:[...p],tools:d,temperature:s.temperature??0,signal:s.signal},s.modelCallTimeoutMs);if(r.usage&&_.push(r.usage),r.toolCalls&&r.toolCalls.length>0){p.push({role:`assistant`,content:r.text,toolCalls:r.toolCalls});for(let e of r.toolCalls){let n=e.args.ops,r=Array.isArray(n)?n:[];s.onProgress?.({type:`refine:tool-call`,filePath:s.filePath,stepNumber:t,ops:r});let{content:i,edits:a,feedback:o,isError:c}=f(e,m);if(c?g++:(m=i,h.push(...a),g=0),s.onProgress?.({type:`refine:tool-result`,filePath:s.filePath,stepNumber:t,appliedCount:a.length,isError:c,feedback:o}),p.push({role:`tool`,toolCallId:e.id,toolName:e.name,content:o}),g>=3)return s.onProgress?.({type:`refine:bailed`,filePath:s.filePath,reason:`Stopped after ${g} consecutive apply errors.`}),v(``)}continue}let i=(r.text??``).trim();return s.onProgress?.({type:`refine:final-text`,filePath:s.filePath,text:i}),v(i)}return s.onProgress?.({type:`refine:bailed`,filePath:s.filePath,reason:`Stopped after ${c} step(s) without a final reply.`}),v(``)}export{i as runRefinePass};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{truncateChars as e}from"../git/truncation.js";import{applyEditOps as t}from"../resolution/edit-apply.js";const n=new Set([`insert`,`replace`,`delete
|
|
2
|
-
`);return{content:n,edits:[],
|
|
1
|
+
import{truncateChars as e}from"../git/truncation.js";import{applyEditOps as t}from"../resolution/edit-apply.js";const n=new Set([`insert`,`replace`,`delete`]);function r(e){if(typeof e!=`object`||!e)return{ok:!1,error:`"args" must be an object.`};let t=e;if(!Array.isArray(t.ops))return{ok:!1,error:`"ops" must be an array.`};if(t.ops.length===0)return{ok:!1,error:`"ops" must contain at least one item.`};let r=[];for(let e=0;e<t.ops.length;e++){let i=t.ops[e];if(typeof i!=`object`||!i)return{ok:!1,error:`op[${e}] must be an object.`};let a=i;if(!n.has(a.kind))return{ok:!1,error:`op[${e}].kind must be 'insert', 'replace', or 'delete' (got ${JSON.stringify(a.kind)}). If no edit is needed, reply with text instead of calling the tool.`};if(!Number.isInteger(a.startLine))return{ok:!1,error:`op[${e}].startLine must be an integer (got ${JSON.stringify(a.startLine)}).`};if(typeof a.anchor!=`string`)return{ok:!1,error:`op[${e}].anchor must be a string (got ${JSON.stringify(a.anchor)}).`};if(a.endLine!==void 0&&!Number.isInteger(a.endLine))return{ok:!1,error:`op[${e}].endLine must be an integer when provided (got ${JSON.stringify(a.endLine)}).`};if(a.content!==void 0&&typeof a.content!=`string`)return{ok:!1,error:`op[${e}].content must be a string when provided (got ${JSON.stringify(a.content)}).`};if(a.kind===`insert`){if(a.endLine!==void 0)return{ok:!1,error:`op[${e}].endLine must not be provided for 'insert' operations.`};if(a.content===void 0)return{ok:!1,error:`op[${e}].content is required for 'insert' operations.`};r.push({kind:`insert`,startLine:a.startLine,anchor:a.anchor,content:a.content})}else if(a.kind===`replace`){if(a.endLine===void 0)return{ok:!1,error:`op[${e}].endLine is required for 'replace' operations.`};if(a.content===void 0)return{ok:!1,error:`op[${e}].content is required for 'replace' operations.`};r.push({kind:`replace`,startLine:a.startLine,endLine:a.endLine,anchor:a.anchor,content:a.content})}else if(a.kind===`delete`){if(a.endLine===void 0)return{ok:!1,error:`op[${e}].endLine is required for 'delete' operations.`};if(a.content!==void 0)return{ok:!1,error:`op[${e}].content must not be provided for 'delete' operations.`};r.push({kind:`delete`,startLine:a.startLine,endLine:a.endLine,anchor:a.anchor})}}return{ok:!0,ops:r}}function i(){let n=[{name:`edit_file_lines`,description:`Apply structured edit operations to the resolved file. Each op targets a specific line range validated by an anchor. All ops in a single call are validated atomically — if any fail, no changes are applied.`,parameters:{type:`object`,required:[`ops`],properties:{ops:{type:`array`,minItems:1,description:`Edit operations, applied together. Each item is an object with "kind" (one of insert, replace, delete), "startLine", "anchor", and, depending on kind, "endLine" and "content". insert: startLine, anchor, content (no endLine). replace: startLine, endLine, anchor, content. delete: startLine, endLine, anchor (no content).`,items:{type:`object`,required:[`kind`,`startLine`,`anchor`],properties:{kind:{type:`string`,enum:[`insert`,`replace`,`delete`],description:`Operation: "insert" adds content after startLine (needs content, no endLine); "replace" rewrites startLine-endLine with content (needs endLine and content); "delete" removes startLine-endLine (needs endLine, no content).`},startLine:{type:`integer`,description:`1-based first line the operation targets.`},endLine:{type:`integer`,description:`1-based last line, inclusive. Required for replace and delete, not allowed for insert.`},anchor:{type:`string`,description:`Exact current text of the targeted lines (startLine only for insert), joined with newlines.`},content:{type:`string`,description:`New text. Required for insert and replace, not allowed for delete.`}}}}}}}];function i(e,n){let i=r(e.args);if(!i.ok)return{content:n,edits:[],feedback:i.error,isError:!0};let a=i.ops,{content:o,edits:s,results:c}=t(n,a),l=c.filter(e=>e.status===`skipped`);if(l.length>0){let e=n.split(`
|
|
2
|
+
`);return{content:n,edits:[],feedback:`Failed to apply ops:\n${l.map(t=>{if(t.status!==`skipped`)return``;let n=a[t.opIndex],r=n&&n.kind!==`insert`?n.endLine:void 0,i=n?.kind===`insert`||r===void 0?String(n?.startLine??`?`):`${n.startLine}-${r}`,o=` op[${t.opIndex}] (${n?.kind??`unknown`} startLine=${i}): ${t.reason}`;if(t.reason===`anchor-mismatch`&&n){let i=n.startLine,a=r??n.startLine,s=e.slice(i-1,a).map((e,t)=>` ${i+t}: ${e}`).join(`
|
|
3
3
|
`);return[o,` Your anchor:`,t.expected.split(`
|
|
4
4
|
`).map(e=>` | ${e}`).join(`
|
|
5
5
|
`),` Actual lines ${i}-${a} in current content:`,s].join(`
|
|
6
6
|
`)}return t.reason===`out-of-bounds`&&n?`${o}\n Content has ${e.length} lines; you targeted startLine=${n.startLine}${r===void 0?``:`, endLine=${r}`}.`:o}).join(`
|
|
7
|
-
`)}\n\nHint: anchor must EXACTLY match the content (including whitespace and indentation) at the specified line range. Re-read the resolved content and copy the lines verbatim.`,isError:!0}}let u=
|
|
8
|
-
`).length;return{content:o,edits:s,
|
|
7
|
+
`)}\n\nHint: anchor must EXACTLY match the content (including whitespace and indentation) at the specified line range. Re-read the resolved content and copy the lines verbatim.`,isError:!0}}let u=o.split(`
|
|
8
|
+
`).length;return{content:o,edits:s,feedback:`Applied ${s.length} op(s). New content length: ${u} lines.`,isError:!1}}function a(t,n){let r=i(t,n);return{...r,feedback:e(r.feedback,`Retry with fewer operations and smaller targeted line ranges/anchors.`).content}}return{definitions:n,execute:a}}export{i as createRefineTools};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ConflictGitPortMissingOpError as e}from"../ports/git.js";import{log as t,mergeBase as n}from"../git/port-dispatch.js";import{toFileFinding as r}from"../validation/findings.js";import{resolveChunkContent as i}from"../resolution/chunk-apply.js";import{createResolution as a}from"../resolution/create-resolution.js";import{mergeUsages as o}from"../resolution/metrics.js";import{checkResidualMarkers as s}from"../validation/markers.js";import{bracketImbalances as c,bracketInstruction as l,fileBracketFinding as u}from"./bracket-balance.js";import{matchFileGuidance as d}from"./file-guidance.js";import{runResolverLoop as f}from"./loop.js";import{lostAdditionFinding as p,lostAdditions as m,refineInstructions as h}from"./lost-additions.js";import{runRefinePass as g}from"./refine-pass.js";import{buildThreeWayDiff as _}from"./three-way-diff.js";const v={refineNoAction:.6,refineApplied:.9,errorsRemain:.5};async function y(e,t,n,y=[e.filePath]){let x={...t,fileGuidance:d(t.fileGuidance,y)},S=x;if(x.refs){let t=x.operation!==void 0&&x.upstreamCommits===void 0,r=x.threeWayDiff??await _(e.filePath,x.refs,{git:n.git,signal:n.signal}),i=t?await b(e.filePath,x.refs,x.operation===`merge`?x.refs.theirs:x.refs.ours,n):x.upstreamCommits;S={...x,threeWayDiff:r,...i===void 0?{}:{upstreamCommits:i}}}let{chunks:C,strategy:w,confidence:T,description:E,metrics:D,followUpInstructions:O}=await f(e,S,{model:n.model,git:n.git,config:n.config,onProgress:n.onProgress,signal:n.signal}),k=a(e,C,{confidence:T,description:E,metrics:D,...w?{fileStrategy:w}:{},...S.threeWayDiff?{threeWayDiff:S.threeWayDiff}:{}}),A=(t,r)=>{let i=S.threeWayDiff;if(e.type!==`text`||!i)return[];let a=[...m(i.oursDiff,`ours`,r),...m(i.theirsDiff,`theirs`,r)];for(let r of a)n.onProgress?.({type:`refine:lost-addition`,filePath:e.filePath,side:r.side,startLine:r.startLine,endLine:r.endLine,lines:r.lines.length,phase:t});return a},j=e.type===`text`?c(C,e):[];for(let t of j)n.onProgress?.({type:`refine:bracket-imbalance`,filePath:e.filePath,...t});let M=h(O,A(`before-refine`,k.content),j.map(e=>l(e,S.operation)));if(e.type===`text`&&M.length>0){n.onProgress?.({type:`refine:started`,filePath:e.filePath});let t=0;try{let r=await g(k.content,k.description,M,{model:n.model,signal:n.signal,temperature:n.config?.temperature,refineMaxSteps:n.config?.refineMaxSteps,modelCallTimeoutMs:n.config?.modelCallTimeoutMs,fileGuidance:S.fileGuidance,onProgress:n.onProgress,filePath:e.filePath});if(r.edits.length>0){let i=s(e.filePath,r.content);if(i.valid)k={...k,content:r.content,edits:r.edits,strategy:`ai`},t=r.edits.length;else{let t=(i.issues??[]).map(e=>e.message).join(` `);n.onProgress?.({type:`refine:discarded`,filePath:e.filePath,reason:`Edits introduced conflict markers: ${t}`})}}if(t>0){k={...k,confidence:Math.min(k.confidence,v.refineApplied)};let t=[];for(let n=0;n<e.markers.length;n++){let a=e.markers[n];if(!a||a.userResolution==null)continue;let o=i({markerIndex:n,...a.userResolution},a);o!==``&&(r.content.includes(o)||t.push(n))}t.length>0&&(n.onProgress?.({type:`refine:touched-user-region`,filePath:e.filePath,markerIndexes:t}),k={...k,note:`Refine modified user-resolved region(s): marker ${t.join(`, `)}.`})}else
|
|
1
|
+
import{ConflictGitPortMissingOpError as e}from"../ports/git.js";import{log as t,mergeBase as n}from"../git/port-dispatch.js";import{toFileFinding as r}from"../validation/findings.js";import{resolveChunkContent as i}from"../resolution/chunk-apply.js";import{createResolution as a}from"../resolution/create-resolution.js";import{mergeUsages as o}from"../resolution/metrics.js";import{checkResidualMarkers as s}from"../validation/markers.js";import{bracketImbalances as c,bracketInstruction as l,fileBracketFinding as u}from"./bracket-balance.js";import{matchFileGuidance as d}from"./file-guidance.js";import{runResolverLoop as f}from"./loop.js";import{lostAdditionFinding as p,lostAdditions as m,refineInstructions as h}from"./lost-additions.js";import{runRefinePass as g}from"./refine-pass.js";import{buildThreeWayDiff as _}from"./three-way-diff.js";const v={refineNoAction:.6,refineApplied:.9,errorsRemain:.5};async function y(e,t,n,y=[e.filePath]){let x={...t,fileGuidance:d(t.fileGuidance,y)},S=x;if(x.refs){let t=x.operation!==void 0&&x.upstreamCommits===void 0,r=x.threeWayDiff??await _(e.filePath,x.refs,{git:n.git,signal:n.signal}),i=t?await b(e.filePath,x.refs,x.operation===`merge`?x.refs.theirs:x.refs.ours,n):x.upstreamCommits;S={...x,threeWayDiff:r,...i===void 0?{}:{upstreamCommits:i}}}let{chunks:C,strategy:w,confidence:T,description:E,metrics:D,followUpInstructions:O}=await f(e,S,{model:n.model,git:n.git,config:n.config,onProgress:n.onProgress,signal:n.signal}),k=a(e,C,{confidence:T,description:E,metrics:D,...w?{fileStrategy:w}:{},...S.threeWayDiff?{threeWayDiff:S.threeWayDiff}:{}}),A=(t,r)=>{let i=S.threeWayDiff;if(e.type!==`text`||!i)return[];let a=[...m(i.oursDiff,`ours`,r),...m(i.theirsDiff,`theirs`,r)];for(let r of a)n.onProgress?.({type:`refine:lost-addition`,filePath:e.filePath,side:r.side,startLine:r.startLine,endLine:r.endLine,lines:r.lines.length,phase:t});return a},j=e.type===`text`?c(C,e):[];for(let t of j)n.onProgress?.({type:`refine:bracket-imbalance`,filePath:e.filePath,...t});let M=h(O,A(`before-refine`,k.content),j.map(e=>l(e,S.operation)));if(e.type===`text`&&M.length>0){n.onProgress?.({type:`refine:started`,filePath:e.filePath});let t=0;try{let r=await g(k.content,k.description,M,{model:n.model,signal:n.signal,temperature:n.config?.temperature,refineMaxSteps:n.config?.refineMaxSteps,modelCallTimeoutMs:n.config?.modelCallTimeoutMs,fileGuidance:S.fileGuidance,onProgress:n.onProgress,filePath:e.filePath});if(r.edits.length>0){let i=s(e.filePath,r.content);if(i.valid)k={...k,content:r.content,edits:r.edits,strategy:`ai`},t=r.edits.length;else{let t=(i.issues??[]).map(e=>e.message).join(` `);n.onProgress?.({type:`refine:discarded`,filePath:e.filePath,reason:`Edits introduced conflict markers: ${t}`})}}if(t>0){k={...k,confidence:Math.min(k.confidence,v.refineApplied)};let t=[];for(let n=0;n<e.markers.length;n++){let a=e.markers[n];if(!a||a.userResolution==null)continue;let o=i({markerIndex:n,...a.userResolution},a);o!==``&&(r.content.includes(o)||t.push(n))}t.length>0&&(n.onProgress?.({type:`refine:touched-user-region`,filePath:e.filePath,markerIndexes:t}),k={...k,note:`Refine modified user-resolved region(s): marker ${t.join(`, `)}.`})}else k={...k,confidence:Math.min(k.confidence,v.refineNoAction)};if(r.usage){let e=o(k.metrics??{inputTokens:0,outputTokens:0},r.usage);k={...k,metrics:{...k.metrics,...e}}}n.onProgress?.({type:`refine:completed`,filePath:e.filePath,appliedCount:t})}catch(t){if(n.signal?.aborted)throw t;let r=t instanceof Error?t.message:String(t);n.onProgress?.({type:`refine:failed`,filePath:e.filePath,reason:r}),k={...k,confidence:Math.min(k.confidence,v.refineNoAction)}}}else n.onProgress?.({type:`refine:skipped`,filePath:e.filePath,reason:`no-follow-ups`});let N=A(`after-refine`,k.content);N.length>0&&(k={...k,findings:[...k.findings??[],...N.map(t=>p(t,e.filePath))]});let P=e.type===`text`?u(e,k.content,S.operation,j):void 0;if(P&&(k={...k,findings:[...k.findings??[],P]}),n.validator&&e.type===`text`)try{let t=await n.validator.validate(new Map([[e.filePath,k.content]]),n.signal,{scope:`file`,role:`result`});Array.isArray(t)?t.length>0&&(k={...k,findings:[...k.findings??[],...t.map(r)]}):n.onProgress?.({type:`validation:not-run`,filePath:e.filePath,scope:`file`,reason:t.reason})}catch(t){if(n.signal?.aborted)throw t;let r=t instanceof Error?t.message:String(t);n.onProgress?.({type:`validation:failed`,filePath:e.filePath,scope:`file`,reason:r})}return n.onProgress?.({type:`resolver:completed`,filePath:e.filePath,stepCount:D.stepCount??0}),k}async function b(r,i,a,o){try{let e=i.base??await n(o.git,i.ours,i.theirs,{signal:o.signal});return await t(o.git,{ref:`${e}..${a}`,path:r,signal:o.signal})}catch(t){if(o.signal?.aborted||!(t instanceof e))throw t;return}}export{v as CONFIDENCE_CAPS,y as resolveConflict};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AIError as e}from"../errors/ai.js";import{ConflictError as t}from"../errors/conflict.js";import{mergeAttributes as n,readFile as r,unmergedEntries as i,unmergedStages as a}from"../git/port-dispatch.js";import{extractConflict as o,extractConflictWithSource as s}from"../extraction/extract.js";import{duplicateBlockFindings as c}from"../resolution/duplicate-blocks.js";import{buildSnapshots as l}from"../reference-integrity/snapshot.js";import{detect as u}from"../reference-integrity/detect.js";import{createResolution as d}from"../resolution/create-resolution.js";import{detectFallbackCodeLoss as f}from"../resolution/detect-code-loss.js";import{takeOurs as p,takeTheirs as m}from"../resolution/take-strategy.js";import{buildPathMatcher as h,guidanceNamesFile as g,matchFileGuidance as _}from"./file-guidance.js";import{resolveConflict as v}from"./resolve-conflict.js";import{
|
|
1
|
+
import{AIError as e}from"../errors/ai.js";import{ConflictError as t}from"../errors/conflict.js";import{mergeAttributes as n,readFile as r,unmergedEntries as i,unmergedStages as a}from"../git/port-dispatch.js";import{extractConflict as o,extractConflictWithSource as s}from"../extraction/extract.js";import{duplicateBlockFindings as c}from"../resolution/duplicate-blocks.js";import{buildSnapshots as l}from"../reference-integrity/snapshot.js";import{detect as u}from"../reference-integrity/detect.js";import{createResolution as d}from"../resolution/create-resolution.js";import{detectFallbackCodeLoss as f}from"../resolution/detect-code-loss.js";import{takeOurs as p,takeTheirs as m}from"../resolution/take-strategy.js";import{buildPathMatcher as h,guidanceNamesFile as g,matchFileGuidance as _}from"./file-guidance.js";import{resolveConflict as v}from"./resolve-conflict.js";import{validateResolvedSet as y,withSetValidation as b}from"../reference-integrity/validate-set.js";import{classifiedResolution as x,deleteModifyResolution as S,noMarkerResolution as C}from"./entry-decision.js";import{isFixable as w,parentsOf as T,runFixPhase as E}from"./fix-loop.js";import{classifyUnmergedEntries as D,loneSideStageMode as O}from"./mechanical.js";import{createModelBudget as k}from"./model-budget.js";import{chooseSurvivingName as A}from"./rename-choice.js";import{findRenameRenameGroup as j}from"./rename-group.js";function M(e){let t=e.map(e=>({isMatch:h(e.match),strategy:e.strategy}));return e=>{for(let{isMatch:n,strategy:r}of t)if(n(e))return r}}async function N(e,t){let r=e.config?.rules??[],a=e.config?.defaultStrategy??`ai`,o=M(r),c=await F(await i(e.git,{signal:e.signal}),e),l=D(c),u=k(e.config?.maxModelCalls),f=u.wrap(e.model),h={...t??{}},_=[],v=[],y=[],b=new Set,w,T=()=>(w??=n(e.git,c.map(e=>e.path),{signal:e.signal}).catch(()=>new Map),w),E=j(c),A=E?[E.basePath,E.oursPath,E.theirsPath]:[],N=E!==null&&A.every(e=>(o(e)??a)===`ai`),B=new Set;if(E&&N&&!e.signal?.aborted&&await L(E,e)){await R(E,c,h,{...e,model:f},{resolutions:_,errors:v,skipped:y});for(let e of A)B.add(e)}for(let t of c){if(e.signal?.aborted)break;if(B.has(t.path))continue;let n=o(t.path)??a;if(n===`skip`){y.push({filePath:t.path,reason:`excluded`}),e.onProgress?.({type:`conflict:excluded`,filePath:t.path});continue}let r=l.get(t.path),i=x(t.path,r,n!==`ai`);if(i){P(i,_,e),i.strategy===`mechanical`&&t.stages!=null&&O(t.stages)===`120000`&&b.add(t.path);continue}if(n===`ai`&&r?.kind===`skip`){y.push({filePath:t.path,reason:r.reason,entryReason:t.reason,stages:t.stages}),e.onProgress?.({type:`conflict:skipped`,filePath:t.path,reason:r.reason,entryReason:t.reason});continue}let c,u;try{({conflict:c,rawContent:u}=await s(t.path,{git:e.git,signal:e.signal},t.reason))}catch(n){let r=U(n);v.push({filePath:t.path,error:r,reason:H(r,e.signal)});continue}if(!c){if(n===`take-ours`||n===`take-theirs`||n===`deleted`){let r=C(t.path,n,t.stages);P({...r,...r.strategy===`deleted`?{}:{deliberateTake:!0}},_,e);continue}let r=await V(t,u,T)?`unmergeable`:`no-markers`;y.push({filePath:t.path,reason:r,entryReason:t.reason,stages:t.stages}),e.onProgress?.({type:`conflict:skipped`,filePath:t.path,reason:r,entryReason:t.reason});continue}e.onProgress?.({type:`conflict:found`,filePath:c.filePath,conflictType:c.type,markerCount:c.markers.length});let w=null;w=n===`take-ours`?{...p(c),deliberateTake:!0}:n===`take-theirs`?{...m(c),deliberateTake:!0}:n===`deleted`?d(c,[],{confidence:1,fileStrategy:`deleted`}):c.type===`delete-modify`&&!g(c.filePath,h)?await S(c,h,e):await z(c,h,{...e,model:f},v),w&&(h.previousResolutions=[...h.previousResolutions??[],w],P(w,_,e))}return{resolutions:_,errors:v,skipped:y,detection:await I(_,h,y,v,b,u,{...e,model:f})}}function P(e,t,n){t.push(e),n.onProgress?.({type:`resolution:applied`,filePath:e.filePath,strategy:e.strategy,confidence:e.confidence,description:e.description})}async function F(e,t){if(!t.git.exec||!e.some(e=>e.stages===void 0))return e;let n=await a(t.git,{signal:t.signal});return e.map(e=>{if(e.stages)return e;let t=n.get(e.path);return t?{...e,stages:t}:e})}async function I(e,t,n,r,i,a,o){let s=new Set([...n.map(e=>e.filePath),...r.map(e=>e.filePath),...e.flatMap(e=>e.retiredPaths??[]),...i]),d={git:o.git,parser:o.parser,signal:o.signal,excludePaths:s,maxScopeFiles:o.config?.maxDetectionScopeFiles},f;if(o.signal?.aborted)f={findings:[],checks:{referenceIntegrity:`errored`,structural:`errored`,validator:`errored`}};else if(o.config?.fix)try{f=await E(e,t,{model:o.model,git:o.git,parser:o.parser,validator:o.validator,config:o.config,onProgress:o.onProgress,signal:o.signal,budget:a,excludePaths:s})}catch(n){if(o.signal?.aborted)throw n;o.onProgress?.({type:`fix:failed`,reason:U(n).message}),f=await u(e,t,d),f={...f,checks:{referenceIntegrity:`errored`,structural:`errored`,validator:o.validator?`errored`:`skipped-no-validator`}}}else{f=await u(e,t,d);let n=await l(e,t,d);for(let t of e.filter(w))f.findings.push(...c(t.filePath,t.content,T(n,t.filePath)));f=b(f,await y(e,n,{validator:o.validator,signal:o.signal,onProgress:o.onProgress}))}let p=e.flatMap(e=>e.findings??[]);return{...f,findings:W([...f.findings,...p])}}async function L(e,t){try{return await r(t.git,e.oursPath,{signal:t.signal})===await r(t.git,e.theirsPath,{signal:t.signal})}catch(e){if(t.signal?.aborted)throw U(e);return!1}}async function R(e,t,n,r,i){let a=e=>{for(let n of e){let e=t.find(e=>e.path===n);i.skipped.push({filePath:n,reason:`rename-choice`,entryReason:e?.reason,stages:e?.stages}),r.onProgress?.({type:`conflict:skipped`,filePath:n,reason:`rename-choice`,entryReason:e?.reason})}},s={...n,fileGuidance:_(n.fileGuidance,[e.basePath,e.oursPath,e.theirsPath])},c;try{c=await A(e,s,{model:r.model,config:r.config,signal:r.signal})}catch(t){if(r.signal?.aborted)throw U(t);a([e.basePath,e.oursPath,e.theirsPath]);return}let l=c.path,u=[l===e.oursPath?e.theirsPath:e.oursPath,e.basePath],d=t.find(e=>e.path===l),f;try{f=await o(l,{git:r.git,signal:r.signal},d?.reason)}catch(e){if(r.signal?.aborted)throw U(e);let t=U(e);i.errors.push({filePath:l,error:t,reason:H(t,r.signal)}),a(u);return}let p;if(!f)p={filePath:l,content:``,strategy:`mechanical`,confidence:c.confidence,description:c.description??`Both sides renamed ${e.basePath}; kept ${l}. Git settled the content itself, which is staged unchanged.`,retiredPaths:u};else{r.onProgress?.({type:`conflict:found`,filePath:f.filePath,conflictType:f.type,markerCount:f.markers.length});let t=await z(f,n,r,i.errors,[e.basePath,e.oursPath,e.theirsPath]);if(!t){a(u);return}let o=[t.note,c.description].filter(e=>!!e).join(` `);p={...t,confidence:Math.min(t.confidence,c.confidence),retiredPaths:u,...o?{note:o}:{}}}i.resolutions.push(p),n.previousResolutions=[...n.previousResolutions??[],p],r.onProgress?.({type:`resolution:applied`,filePath:p.filePath,strategy:p.strategy,confidence:p.confidence,description:p.description})}async function z(n,r,i,a,o){let s,c={generate:async e=>{try{return await i.model.generate(e)}catch(e){throw s=e,e}}};try{return await v(n,r,{model:c,git:i.git,config:i.config,onProgress:i.onProgress,signal:i.signal,validator:i.validator},o)}catch(r){let o=U(r);if(i.signal?.aborted)throw o;let c=r===s||o instanceof DOMException&&o.name===`TimeoutError`?`model-error`:o instanceof e&&o.code===`VALIDATION_EXHAUSTED`?`validation-exhausted`:`unknown`,l=i.config?.fallbackStrategy;if(!l)return a.push({filePath:n.filePath,error:o,reason:c}),i.onProgress?.({type:`resolution:failed`,filePath:n.filePath,error:o}),null;let u=i.config?.allowLossyFallback?null:f(n,l);if(u){let e=new t(`CODE_LOSS_RISK`,`AI resolution failed and the '${l}' fallback would take one side at every conflict marker in ${n.filePath} without a decision, discarding the ${u.discardedSide} side's conflicting changes. Stopping for manual resolution. Resolver failure: ${o.message}`,{cause:o});return a.push({filePath:n.filePath,error:e,reason:`code-loss-risk`,causeReason:c}),i.onProgress?.({type:`resolution:failed`,filePath:n.filePath,error:e}),null}return i.onProgress?.({type:`resolution:fallback`,filePath:n.filePath,error:o,cause:o instanceof e?o.code:void 0}),{...l===`take-ours`?p(n):m(n),confidence:0,fallback:!0}}}const B=new Set([`unspecified`,`set`,`text`]);async function V(e,t,n){if(e.stages?.stage2==null||e.stages?.stage3==null)return!1;if(t.includes(`\0`))return!0;let r=(await n()).get(e.path);return r!==void 0&&!B.has(r)}function H(e,n){return n?.aborted?`aborted`:e instanceof t&&e.code===`PARSE_FAILED`?`parse-failed`:`read-failed`}function U(e){return e instanceof Error?e:Error(String(e))}function W(e){let t=new Set,n=[];for(let r of e){let{category:e,location:i}=r,a=`${e}\u0000${i.file}\u0000${i.line}\u0000${i.symbol??``}`;t.has(a)||(t.add(a),n.push(r))}return n}export{N as resolveConflicts};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildSummaryPrompt as e}from"./prompt.js";const t=.8;function n(e,n=t){let s={ai:0,"take-ours":0,"take-theirs":0,deleted:0,skipped:0,mechanical:0},c=0,l=null,u=new Map;for(let t of e.resolutions)s[t.strategy]++,c+=t.chunks?.length??0,l=l===null?t.confidence:Math.min(l,t.confidence),t.confidence<n&&u.set(t.filePath,{filePath:t.filePath,confidence:t.confidence,reason:`low-confidence`});let d=e.detection,f=d?.findings??[],p=new Map,m=!1;for(let e of f){e.severity===`error`&&(m=!0);let t=p.get(e.location.file)??new Set;t.add(e.category),p.set(e.location.file,t)}for(let[e,t]of p){let n=u.get(e);u.set(e,{filePath:e,...n?.confidence==null?{}:{confidence:n.confidence},reason:`finding`,findingCategories:[...t].sort()})}for(let t of e.skipped??[])t.reason!==`excluded`&&(u.has(t.filePath)||u.set(t.filePath,{filePath:t.filePath,reason:`unresolved`}));let h=[...u.values()].sort(a),g=d!=null&&r(d.checks.referenceIntegrity)&&r(d.checks.structural)&&i(d.scope),_=o({hasErrorFinding:m,hasWarningFinding:f.some(e=>e.severity===`warning`),needsReview:h.length>0,detectionComplete:g,hasResolutionErrors:e.errors.length>0});return{files:e.resolutions.length,conflicts:c,byStrategy:s,minConfidence:l,needsReview:h,skipped:e.skipped?.length??0,errors:e.errors.length,detectionComplete:g,findingCount:f.length,verdict:_}}function r(e){return e===`ran`||e===`partial-unsupported-language`}function i(e){return e?!e.truncated&&e.incomingDiscovered:!0}function a(e,t){return e.reason===t.reason?(e.confidence??1)-(t.confidence??1):e.reason===`finding`?-1:1}function o(e){return e.hasErrorFinding?`blocked`:e.needsReview||!e.detectionComplete||e.hasResolutionErrors||e.hasWarningFinding?`needs-review`:`clean`}async function s(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,s as summarizeOperation};
|
|
1
|
+
import{buildSummaryPrompt as e}from"./prompt.js";const t=.8;function n(e,n=t){let s={ai:0,"take-ours":0,"take-theirs":0,deleted:0,skipped:0,mechanical:0},c=0,l=null,u=new Map;for(let t of e.resolutions)s[t.strategy]++,c+=t.chunks?.length??0,l=l===null?t.confidence:Math.min(l,t.confidence),t.confidence<n&&u.set(t.filePath,{filePath:t.filePath,confidence:t.confidence,reason:`low-confidence`});let d=e.detection,f=d?.findings??[],p=new Map,m=!1;for(let e of f){e.severity===`error`&&(m=!0);let t=p.get(e.location.file)??new Set;t.add(e.category),p.set(e.location.file,t)}for(let[e,t]of p){let n=u.get(e);u.set(e,{filePath:e,...n?.confidence==null?{}:{confidence:n.confidence},reason:`finding`,findingCategories:[...t].sort()})}for(let t of e.skipped??[])t.reason!==`excluded`&&(u.has(t.filePath)||u.set(t.filePath,{filePath:t.filePath,reason:`unresolved`}));let h=[...u.values()].sort(a),g=d!=null&&r(d.checks.referenceIntegrity)&&r(d.checks.structural)&&(d.checks.validator===`ran`||d.checks.validator===`skipped-no-validator`)&&i(d.scope),_=o({hasErrorFinding:m,hasWarningFinding:f.some(e=>e.severity===`warning`),needsReview:h.length>0,detectionComplete:g,hasResolutionErrors:e.errors.length>0});return{files:e.resolutions.length,conflicts:c,byStrategy:s,minConfidence:l,needsReview:h,skipped:e.skipped?.length??0,errors:e.errors.length,detectionComplete:g,validation:d?.checks.validator??`skipped-no-validator`,...d?.validatorReason?{validationReason:d.validatorReason}:{},findingCount:f.length,verdict:_}}function r(e){return e===`ran`||e===`partial-unsupported-language`}function i(e){return e?!e.truncated&&e.incomingDiscovered:!0}function a(e,t){return e.reason===t.reason?(e.confidence??1)-(t.confidence??1):e.reason===`finding`?-1:1}function o(e){return e.hasErrorFinding?`blocked`:e.needsReview||!e.detectionComplete||e.hasResolutionErrors||e.hasWarningFinding?`needs-review`:`clean`}async function s(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,s as summarizeOperation};
|
package/dist/types/events.d.ts
CHANGED
|
@@ -156,7 +156,13 @@ type ConflictProgressEvent = {
|
|
|
156
156
|
limit: number;
|
|
157
157
|
} | {
|
|
158
158
|
type: 'validation:failed';
|
|
159
|
-
filePath
|
|
159
|
+
filePath?: string;
|
|
160
|
+
scope: 'file' | 'set';
|
|
161
|
+
reason: string;
|
|
162
|
+
} | {
|
|
163
|
+
type: 'validation:not-run';
|
|
164
|
+
filePath?: string;
|
|
165
|
+
scope: 'file' | 'set';
|
|
160
166
|
reason: string;
|
|
161
167
|
} | {
|
|
162
168
|
type: 'resolver:tool-result';
|
package/dist/types/findings.d.ts
CHANGED
|
@@ -34,12 +34,18 @@ interface Finding {
|
|
|
34
34
|
* `skipped-unreadable-source` means the parent snapshots came back empty (unreadable source), so the
|
|
35
35
|
* before/after comparison had nothing to compare against. */
|
|
36
36
|
type CheckStatus = 'ran' | 'skipped-no-parser' | 'skipped-no-refs' | 'skipped-unreadable-source' | 'partial-unsupported-language' | 'partial-no-cross-file-resolution' | 'errored';
|
|
37
|
+
/** Whether the consumer's validator ran on the whole resolved set. `not-run` means it answered that it
|
|
38
|
+
* did not; `errored` means it threw. */
|
|
39
|
+
type ValidatorCheckStatus = 'ran' | 'skipped-no-validator' | 'not-run' | 'errored';
|
|
37
40
|
interface DetectionReport {
|
|
38
41
|
findings: Finding[];
|
|
39
42
|
checks: {
|
|
40
43
|
referenceIntegrity: CheckStatus;
|
|
41
44
|
structural: CheckStatus;
|
|
45
|
+
validator: ValidatorCheckStatus;
|
|
42
46
|
};
|
|
47
|
+
/** Why the whole-set validator did not run or threw. Present with `checks.validator` `not-run` or `errored`. */
|
|
48
|
+
validatorReason?: string;
|
|
43
49
|
/** Honest coverage signal so empty findings are not read as "clean". Present only when the reference
|
|
44
50
|
* check ran (refs available). */
|
|
45
51
|
scope?: {
|
|
@@ -54,4 +60,4 @@ interface DetectionReport {
|
|
|
54
60
|
};
|
|
55
61
|
}
|
|
56
62
|
//#endregion
|
|
57
|
-
export { CheckStatus, DetectionReport, Finding, FindingCategory, FindingTier };
|
|
63
|
+
export { CheckStatus, DetectionReport, Finding, FindingCategory, FindingTier, ValidatorCheckStatus };
|
package/dist/types/hints.d.ts
CHANGED
|
@@ -15,18 +15,10 @@ type IntraFileEditOp = {
|
|
|
15
15
|
startLine: number;
|
|
16
16
|
endLine: number;
|
|
17
17
|
anchor: string;
|
|
18
|
-
} | {
|
|
19
|
-
kind: 'verify';
|
|
20
|
-
startLine: number;
|
|
21
|
-
endLine?: number;
|
|
22
|
-
anchor: string;
|
|
23
18
|
};
|
|
24
19
|
type EditOpApplyResult = {
|
|
25
20
|
opIndex: number;
|
|
26
21
|
status: 'applied';
|
|
27
|
-
} | {
|
|
28
|
-
opIndex: number;
|
|
29
|
-
status: 'verified';
|
|
30
22
|
} | {
|
|
31
23
|
opIndex: number;
|
|
32
24
|
status: 'skipped';
|
package/dist/types/summary.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ValidatorCheckStatus } from "./findings.js";
|
|
1
2
|
import { ResolutionStrategy } from "./resolution.js";
|
|
2
3
|
import { AiTokenUsage } from "@gitkraken/shared-tools";
|
|
3
4
|
|
|
@@ -45,6 +46,11 @@ interface OperationDigest {
|
|
|
45
46
|
* references, or part of the scope was never looked at — so empty findings must NOT be read as
|
|
46
47
|
* "clean". */
|
|
47
48
|
detectionComplete: boolean;
|
|
49
|
+
/** Whether the consumer's validator ran on the whole resolved set. `'skipped-no-validator'` when none
|
|
50
|
+
* was supplied or no detection was attached. */
|
|
51
|
+
validation: ValidatorCheckStatus;
|
|
52
|
+
/** Why the validator did not run or threw; see {@link validation}. */
|
|
53
|
+
validationReason?: string;
|
|
48
54
|
/** Total detection findings across the operation. */
|
|
49
55
|
findingCount: number;
|
|
50
56
|
/** Suggested honest verdict; see {@link OperationVerdict}. */
|