@gitkraken/conflict-tools 0.2.4-beta.9 → 0.4.0
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 +55 -7
- package/README.md +308 -59
- package/dist/errors/conflict.d.ts +1 -1
- package/dist/extraction/extract.d.ts +15 -1
- package/dist/extraction/extract.js +1 -1
- package/dist/extraction/parser.js +5 -5
- package/dist/git/port-dispatch.d.ts +14 -2
- package/dist/git/port-dispatch.js +1 -3
- package/dist/index.d.ts +6 -6
- package/dist/index.js +1 -1
- package/dist/ports/git.d.ts +20 -1
- package/dist/ports/parser.d.ts +27 -6
- package/dist/ports/validator.d.ts +29 -0
- package/dist/ports/validator.js +1 -0
- package/dist/reference-integrity/detect.d.ts +39 -4
- package/dist/reference-integrity/detect.js +2 -2
- package/dist/reference-integrity/snapshot.d.ts +28 -6
- package/dist/reference-integrity/snapshot.js +1 -1
- package/dist/resolution/detect-code-loss.d.ts +23 -0
- package/dist/resolution/detect-code-loss.js +1 -0
- package/dist/resolver/apply-resolutions.js +1 -1
- package/dist/resolver/deps.d.ts +1 -1
- package/dist/resolver/fix-loop.d.ts +67 -0
- package/dist/resolver/fix-loop.js +2 -0
- package/dist/resolver/mechanical.d.ts +25 -0
- package/dist/resolver/mechanical.js +1 -0
- package/dist/resolver/model-budget.d.ts +26 -0
- package/dist/resolver/model-budget.js +1 -0
- package/dist/resolver/parse-response.d.ts +9 -1
- package/dist/resolver/parse-response.js +1 -1
- package/dist/resolver/prompt.d.ts +13 -2
- package/dist/resolver/prompt.js +27 -7
- package/dist/resolver/refine-pass.d.ts +8 -0
- package/dist/resolver/refine-pass.js +1 -1
- package/dist/resolver/rename-choice.d.ts +15 -0
- package/dist/resolver/rename-choice.js +1 -0
- package/dist/resolver/rename-group.d.ts +22 -0
- package/dist/resolver/rename-group.js +1 -0
- package/dist/resolver/resolve-conflict.d.ts +7 -1
- 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/resolver/validate.js +1 -1
- package/dist/types/events.d.ts +30 -3
- package/dist/types/findings.d.ts +25 -15
- package/dist/types/resolution.d.ts +17 -1
- package/dist/types/step.d.ts +47 -5
- package/dist/types/summary.d.ts +17 -10
- package/dist/validation/findings.d.ts +9 -0
- package/dist/validation/findings.js +1 -0
- package/dist/validation/markers.d.ts +11 -0
- package/dist/{ports/verify.js → validation/markers.js} +1 -1
- package/package.json +1 -1
- package/dist/ports/verify.d.ts +0 -30
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import { BlameOptions, ConflictDiffOptions, ConflictGitPort, GrepOptions, LogOptions, OpOptions, ShowFileOptions, ShowOptions, UnmergedEntry } from "../ports/git.js";
|
|
1
|
+
import { BlameOptions, ConflictDiffOptions, ConflictGitPort, GrepOptions, LogOptions, OpOptions, ShowFileOptions, ShowOptions, UnmergedEntry, UnmergedStages } from "../ports/git.js";
|
|
2
2
|
|
|
3
3
|
//#region src/git/port-dispatch.d.ts
|
|
4
4
|
declare function readFile(git: ConflictGitPort, path: string, opts?: OpOptions): Promise<string>;
|
|
5
5
|
declare function showFile(git: ConflictGitPort, ref: string, path: string, opts?: ShowFileOptions): Promise<string>;
|
|
6
6
|
declare function unmergedEntries(git: ConflictGitPort, opts?: OpOptions): Promise<UnmergedEntry[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Reads the index-stage signature of every unmerged path in one `git ls-files -u` call. Used when a
|
|
9
|
+
* host-supplied `unmergedEntries` op reports no signature of its own. Returns an empty map when the
|
|
10
|
+
* port has no `exec` fallback.
|
|
11
|
+
*/
|
|
12
|
+
declare function unmergedStages(git: ConflictGitPort, opts?: OpOptions): Promise<Map<string, UnmergedStages>>;
|
|
13
|
+
/**
|
|
14
|
+
* Reads the effective `merge` gitattribute of every given path in one `git check-attr` call. Values are
|
|
15
|
+
* git's own: `set`, `unset`, `binary`, `unspecified`, or the name of a merge driver. Returns an empty map
|
|
16
|
+
* when the port has no `exec` fallback.
|
|
17
|
+
*/
|
|
18
|
+
declare function mergeAttributes(git: ConflictGitPort, paths: string[], opts?: OpOptions): Promise<Map<string, string>>;
|
|
7
19
|
declare function mergeBase(git: ConflictGitPort, a: string, b: string, opts?: OpOptions): Promise<string>;
|
|
8
20
|
declare function blame(git: ConflictGitPort, path: string, opts?: BlameOptions): Promise<string>;
|
|
9
21
|
declare function grep(git: ConflictGitPort, pattern: string, opts?: GrepOptions): Promise<string>;
|
|
@@ -16,4 +28,4 @@ declare function stageFiles(git: ConflictGitPort, paths: string[], opts?: OpOpti
|
|
|
16
28
|
declare function checkoutFile(git: ConflictGitPort, path: string, side: 'ours' | 'theirs', opts?: OpOptions): Promise<void>;
|
|
17
29
|
declare function removeFile(git: ConflictGitPort, path: string, opts?: OpOptions): Promise<void>;
|
|
18
30
|
//#endregion
|
|
19
|
-
export { blame, changedFiles, checkoutFile, diff, grep, log, mergeBase, readFile, removeFile, show, showFile, stageFiles, unmergedEntries, writeFile };
|
|
31
|
+
export { blame, changedFiles, checkoutFile, diff, grep, log, mergeAttributes, mergeBase, readFile, removeFile, show, showFile, stageFiles, unmergedEntries, unmergedStages, writeFile };
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
import{ConflictGitPortMissingOpError as e}from"../ports/git.js";import{sliceLines as t,truncateLines as n,truncateMatches as r}from"./truncation.js";const i={DD:`both-deleted`,AU:`added-by-us`,UD:`deleted-by-them`,UA:`added-by-them`,DU:`deleted-by-us`,AA:`both-added`,UU:`both-modified`};function a(e){return i[e]??e}function o(e){return`Search capped at ${e} matches. There may be more — narrow with a more specific pattern or path.`}function s(t,n){throw new e(t,n)}function c(e,t){return e!=null&&t!=null?`startLine=${e}, endLine=${t}`:e==null?`endLine=${t}`:`startLine=${e}`}async function l(e,t,n){return e.readFile?e.readFile(t,n):s(`readFile`)}async function u(e,r,i,a){let o;if(e.showFile)o=await e.showFile(r,i,a);else if(e.exec){let n=t(await e.exec([`show`,`${r}:${i}`],{signal:a?.signal}),a?.startLine,a?.endLine);if(!n.ok)return n.reason===`invalid-range`?`[Invalid range: ${c(n.startLine,n.endLine)} (startLine must be <= endLine).]`:`[Range out of bounds: file has ${n.total} lines, requested ${c(n.startLine,n.endLine)}.]`;o=n.content}else return s(`showFile`);return n(o,1e3,`Output capped at 1000 lines. Use startLine/endLine to read other regions of the file.`)}async function d(e,t){if(e.unmergedEntries)return e.unmergedEntries(t);if(e.exec){let n=await e.exec([
|
|
2
|
-
`)){if(!e.startsWith(`u `))continue;let t=e.split(` `),n=t[1]??``,i=t.slice(10).join(` `);r.push({path:i,reason:a(n)})}return r}return s(`unmergedEntries`)}async function f(e,t,n,r){return e.mergeBase?e.mergeBase(t,n,r):e.exec?(await e.exec([`merge-base`,`--`,t,n],{signal:r?.signal})).trim():s(`mergeBase`)}async function p(e,t,r){let i=r?.startLine,a=r?.endLine,o=Number.isFinite(i),l=Number.isFinite(a);if(o&&l&&i>a)return`[Invalid range: ${c(i,a)} (startLine must be <= endLine).]`;let u;if(e.blame)u=await e.blame(t,r);else if(e.exec){let n=[`blame`,`--porcelain`],s=o?Math.max(1,i):void 0,c=l?Math.max(1,a):void 0;s!=null&&c!=null?n.push(`-L`,`${s},${c}`):s==null?c!=null&&n.push(`-L`,`1,${c}`):n.push(`-L`,`${s},`),r?.ref&&n.push(r.ref),n.push(`--`,t),u=await e.exec(n,{signal:r?.signal})}else return s(`blame`);return n(u,1e3,`Output capped at 1000 lines. Use startLine/endLine to inspect other regions.`)}async function m(e,t,n){let i=n?.maxResults,a=i!=null&&Number.isFinite(i)?Math.min(Math.max(1,i),100):100,c;if(e.grep)c=await e.grep(t,n);else if(e.exec){let r=[`grep`,`-n`,`--`,t];n?.ref&&r.push(n.ref),c=await e.exec(r,{signal:n?.signal})}else return s(`grep`);return r(c,a,o(a))}async function h(e,t,n,r){if(e.diff)return e.diff(t,n,r);if(e.exec){let i=[`diff`,t,n];return r?.path&&i.push(`--`,r.path),e.exec(i,{signal:r?.signal})}return s(`diff`)}async function g(e,t,n,r){return e.changedFiles?e.changedFiles(t,n,r):e.exec?(await e.exec([`diff`,`--name-only`,t,n],{signal:r?.signal})).split(`
|
|
3
|
-
`).map(e=>e.trim()).filter(e=>e.length>0):s(`changedFiles`)}async function _(e,t){let r=t?.maxCount,i=r!=null&&Number.isFinite(r)?Math.min(Math.max(1,r),100):100,a;if(e.log)a=await e.log({...t,maxCount:i});else if(e.exec){let n=[`log`,`--oneline`,`-n`,String(i)];t?.ref&&n.push(t.ref),t?.path&&n.push(`--`,t.path),a=await e.exec(n,{signal:t?.signal})}else return s(`log`);return n(a,100,`Output capped at 100 commits. Use path to scope to a file or refine the ref range.`)}async function v(e,t,r){let i;if(e.show)i=await e.show(t,r);else if(e.exec){let n=[`show`,t];r?.path&&n.push(`--`,r.path),i=await e.exec(n,{signal:r?.signal})}else return s(`show`);return n(i,500,`Output capped at 500 lines. Use show_file_at_ref with startLine/endLine to read specific regions.`)}async function y(e,t,n,r){return e.writeFile?e.writeFile(t,n,r):s(`writeFile`)}async function b(e,t,n){if(e.stageFiles)return e.stageFiles(t,n);if(e.exec){await e.exec([`add`,`--`,...t],{signal:n?.signal});return}return s(`stageFiles`)}async function x(e,t,n,r){if(e.checkoutFile)return e.checkoutFile(t,n,r);if(e.exec){await e.exec([`checkout`,`--${n}`,`--`,t],{signal:r?.signal});return}return s(`checkoutFile`)}async function S(e,t,n){if(e.removeFile)return e.removeFile(t,n);if(e.exec){await e.exec([`rm`,`--`,t],{signal:n?.signal});return}return s(`removeFile`)}export{p as blame,g as changedFiles,x as checkoutFile,h as diff,m as grep,_ as log,f as mergeBase,l as readFile,S as removeFile,v as show,u as showFile,b as stageFiles,d as unmergedEntries,y as writeFile};
|
|
1
|
+
import{ConflictGitPortMissingOpError as e}from"../ports/git.js";import{sliceLines as t,truncateLines as n,truncateMatches as r}from"./truncation.js";const i={DD:`both-deleted`,AU:`added-by-us`,UD:`deleted-by-them`,UA:`added-by-them`,DU:`deleted-by-us`,AA:`both-added`,UU:`both-modified`};function a(e){return i[e]??e}function o(e){return`Search capped at ${e} matches. There may be more — narrow with a more specific pattern or path.`}function s(t,n){throw new e(t,n)}function c(e,t){return e!=null&&t!=null?`startLine=${e}, endLine=${t}`:e==null?`endLine=${t}`:`startLine=${e}`}async function l(e,t,n){return e.readFile?e.readFile(t,n):s(`readFile`)}async function u(e,r,i,a){let o;if(e.showFile)o=await e.showFile(r,i,a);else if(e.exec){let n=t(await e.exec([`show`,`${r}:${i}`],{signal:a?.signal}),a?.startLine,a?.endLine);if(!n.ok)return n.reason===`invalid-range`?`[Invalid range: ${c(n.startLine,n.endLine)} (startLine must be <= endLine).]`:`[Range out of bounds: file has ${n.total} lines, requested ${c(n.startLine,n.endLine)}.]`;o=n.content}else return s(`showFile`);return a?.fullContent?o:n(o,1e3,`Output capped at 1000 lines. Use startLine/endLine to read other regions of the file.`)}async function d(e,t){if(e.unmergedEntries)return e.unmergedEntries(t);if(e.exec){let n=await e.exec([`-c`,`core.quotepath=false`,`status`,`--porcelain=v2`,`-z`],{signal:t?.signal}),r=[];for(let e of n.split(`\0`)){if(!e.startsWith(`u `))continue;let t=e.split(` `),n=t[1]??``,i=t.slice(10).join(` `);r.push({path:i,reason:a(n),stages:{stage1:f(t[3]),stage2:f(t[4]),stage3:f(t[5])}})}return r}return s(`unmergedEntries`)}function f(e){return e===void 0||e===`000000`?null:e}async function p(e,t){let n=new Map;if(!e.exec)return n;let r=await e.exec([`ls-files`,`-u`,`-z`],{signal:t?.signal});for(let e of r.split(`\0`)){let t=e.indexOf(` `);if(t===-1)continue;let[r,,i]=e.slice(0,t).split(` `),a=e.slice(t+1);if(r===void 0||a.length===0)continue;let o=n.get(a)??{stage1:null,stage2:null,stage3:null};i===`1`?o.stage1=r:i===`2`?o.stage2=r:i===`3`&&(o.stage3=r),n.set(a,o)}return n}async function m(e,t,n){let r=new Map;if(!e.exec)return r;let i=(await e.exec([`check-attr`,`-z`,`--stdin`,`merge`],{stdin:t.join(`\0`),signal:n?.signal})).split(`\0`);for(let e=0;e+2<i.length;e+=3)r.set(i[e],i[e+2]);return r}async function h(e,t,n,r){return e.mergeBase?e.mergeBase(t,n,r):e.exec?(await e.exec([`merge-base`,`--`,t,n],{signal:r?.signal})).trim():s(`mergeBase`)}async function g(e,t,r){let i=r?.startLine,a=r?.endLine,o=Number.isFinite(i),l=Number.isFinite(a);if(o&&l&&i>a)return`[Invalid range: ${c(i,a)} (startLine must be <= endLine).]`;let u;if(e.blame)u=await e.blame(t,r);else if(e.exec){let n=[`blame`,`--porcelain`],s=o?Math.max(1,i):void 0,c=l?Math.max(1,a):void 0;s!=null&&c!=null?n.push(`-L`,`${s},${c}`):s==null?c!=null&&n.push(`-L`,`1,${c}`):n.push(`-L`,`${s},`),r?.ref&&n.push(r.ref),n.push(`--`,t),u=await e.exec(n,{signal:r?.signal})}else return s(`blame`);return n(u,1e3,`Output capped at 1000 lines. Use startLine/endLine to inspect other regions.`)}async function _(e,t,n){let i=n?.maxResults,a=i!=null&&Number.isFinite(i)?Math.min(Math.max(1,i),100):100,c;if(e.grep)c=await e.grep(t,n);else if(e.exec){let r=[`grep`,`-n`,`--`,t];n?.ref&&r.push(n.ref),c=await e.exec(r,{signal:n?.signal})}else return s(`grep`);return r(c,a,o(a))}async function v(e,t,n,r){if(e.diff)return e.diff(t,n,r);if(e.exec){let i=[`diff`,t,n];return r?.path&&i.push(`--`,r.path),e.exec(i,{signal:r?.signal})}return s(`diff`)}async function y(e,t,n,r){return e.changedFiles?e.changedFiles(t,n,r):e.exec?(await e.exec([`-c`,`core.quotepath=false`,`diff`,`--name-only`,`-z`,`--end-of-options`,t,n],{signal:r?.signal})).split(`\0`).filter(e=>e.length>0):s(`changedFiles`)}async function b(e,t){let r=t?.maxCount,i=r!=null&&Number.isFinite(r)?Math.min(Math.max(1,r),100):100,a;if(e.log)a=await e.log({...t,maxCount:i});else if(e.exec){let n=[`log`,`--oneline`,`-n`,String(i)];t?.ref&&n.push(t.ref),t?.path&&n.push(`--`,t.path),a=await e.exec(n,{signal:t?.signal})}else return s(`log`);return n(a,100,`Output capped at 100 commits. Use path to scope to a file or refine the ref range.`)}async function x(e,t,r){let i;if(e.show)i=await e.show(t,r);else if(e.exec){let n=[`show`,t];r?.path&&n.push(`--`,r.path),i=await e.exec(n,{signal:r?.signal})}else return s(`show`);return n(i,500,`Output capped at 500 lines. Use show_file_at_ref with startLine/endLine to read specific regions.`)}async function S(e,t,n,r){return e.writeFile?e.writeFile(t,n,r):s(`writeFile`)}async function C(e,t,n){if(e.stageFiles)return e.stageFiles(t,n);if(e.exec){await e.exec([`add`,`--`,...t],{signal:n?.signal});return}return s(`stageFiles`)}async function w(e,t,n,r){if(e.checkoutFile)return e.checkoutFile(t,n,r);if(e.exec){await e.exec([`checkout`,`--${n}`,`--`,t],{signal:r?.signal});return}return s(`checkoutFile`)}async function T(e,t,n){if(e.removeFile)return e.removeFile(t,n);if(e.exec){await e.exec([`rm`,`--`,t],{signal:n?.signal});return}return s(`removeFile`)}export{g as blame,y as changedFiles,w as checkoutFile,v as diff,_ as grep,b as log,m as mergeAttributes,h as mergeBase,l as readFile,T as removeFile,x as show,u as showFile,C as stageFiles,d as unmergedEntries,p as unmergedStages,S as writeFile};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AIError, AIErrorCode } from "./errors/ai.js";
|
|
2
2
|
import { ConflictError, ConflictErrorCode } from "./errors/conflict.js";
|
|
3
|
-
import { BlameOptions, ConflictDiffOptions, ConflictGitOps, ConflictGitPort, ConflictGitPortMissingOpError, GrepOptions, LogOptions, OpOptions, ShowFileOptions, ShowOptions, UnmergedEntry, UnmergedReason } from "./ports/git.js";
|
|
3
|
+
import { BlameOptions, ConflictDiffOptions, ConflictGitOps, ConflictGitPort, ConflictGitPortMissingOpError, GrepOptions, LogOptions, OpOptions, ShowFileOptions, ShowOptions, UnmergedEntry, UnmergedReason, UnmergedStages } from "./ports/git.js";
|
|
4
4
|
import { CheckStatus, DetectionReport, Finding, FindingCategory, FindingTier } from "./types/findings.js";
|
|
5
5
|
import { IntraFileEditOp } from "./types/hints.js";
|
|
6
6
|
import { DiffStats, Resolution, ResolutionContext, ResolutionMetrics, ResolutionRawSignals, ResolutionRefs, ResolutionStrategy, ResolvedChunk, ThreeWayDiff } from "./types/resolution.js";
|
|
@@ -9,17 +9,17 @@ import { extractConflict } from "./extraction/extract.js";
|
|
|
9
9
|
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
|
-
import { ParsedFile, ParserPort, SourceOverlay, SymbolDefinition, SymbolReference, SyntaxIssue } from "./ports/parser.js";
|
|
13
|
-
import {
|
|
14
|
-
import { Validator, ValidatorFinding } from "./ports/verify.js";
|
|
12
|
+
import { ParsedFile, ParserCapabilities, ParserPort, SourceOverlay, SymbolDefinition, SymbolReference, SyntaxIssue } from "./ports/parser.js";
|
|
13
|
+
import { Validator, ValidatorFinding } from "./ports/validator.js";
|
|
15
14
|
import { DetectDeps, detect } from "./reference-integrity/detect.js";
|
|
16
15
|
import { createResolution } from "./resolution/create-resolution.js";
|
|
16
|
+
import { CodeLossDetection, detectFallbackCodeLoss } from "./resolution/detect-code-loss.js";
|
|
17
17
|
import { takeOurs, takeTheirs } from "./resolution/take-strategy.js";
|
|
18
18
|
import { applyResolutions } from "./resolver/apply-resolutions.js";
|
|
19
19
|
import { ConflictProgressEvent } from "./types/events.js";
|
|
20
20
|
import { resolveConflict } from "./resolver/resolve-conflict.js";
|
|
21
|
-
import { FileRule, StepConfig, StepResult } from "./types/step.js";
|
|
21
|
+
import { FileRule, SkippedEntry, StepConfig, StepResult } from "./types/step.js";
|
|
22
22
|
import { resolveConflicts } from "./resolver/resolve-conflicts.js";
|
|
23
23
|
import { OperationDigest, OperationSummary, OperationVerdict, ReviewItem } from "./types/summary.js";
|
|
24
24
|
import { SummarizeOperationOptions, buildOperationDigest, summarizeOperation } from "./resolver/summarize-operation.js";
|
|
25
|
-
export { AIError, type AIErrorCode, type BlameOptions, type CheckStatus, type Conflict, type ConflictDiffOptions, ConflictError, type ConflictErrorCode, type ConflictGitOps, type ConflictGitPort, ConflictGitPortMissingOpError, type ConflictMarker, type ConflictMarkerUserInput, type ConflictModelMessage, type ConflictModelParams, type ConflictModelPort, type ConflictModelResult, type ConflictProgressEvent, type ConflictType, type DetectDeps, type DetectionReport, type DiffStats, type FileRule, type Finding, type FindingCategory, type FindingTier, type GrepOptions, type IntraFileEditOp, type
|
|
25
|
+
export { AIError, type AIErrorCode, type BlameOptions, type CheckStatus, type CodeLossDetection, 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 ParserCapabilities, 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 SkippedEntry, 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 UnmergedStages, type Validator, type ValidatorFinding, applyResolutions, buildOperationDigest, checkoutFile, createResolution, detect, detectFallbackCodeLoss, extractConflict, removeFile, resolveConflict, resolveConflicts, show, summarizeOperation, takeOurs, takeTheirs };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AIError as e}from"./errors/ai.js";import{ConflictError as t}from"./errors/conflict.js";import{ConflictGitPortMissingOpError as n}from"./ports/git.js";import{checkoutFile as r,removeFile as i,show as a}from"./git/port-dispatch.js";import{extractConflict as o}from"./extraction/extract.js";import{detect as s}from"./reference-integrity/detect.js";import{createResolution as c}from"./resolution/create-resolution.js";import{takeOurs as
|
|
1
|
+
import{AIError as e}from"./errors/ai.js";import{ConflictError as t}from"./errors/conflict.js";import{ConflictGitPortMissingOpError as n}from"./ports/git.js";import{checkoutFile as r,removeFile as i,show as a}from"./git/port-dispatch.js";import{extractConflict as o}from"./extraction/extract.js";import{detect as s}from"./reference-integrity/detect.js";import{createResolution as c}from"./resolution/create-resolution.js";import{detectFallbackCodeLoss as l}from"./resolution/detect-code-loss.js";import{takeOurs as u,takeTheirs as d}from"./resolution/take-strategy.js";import{applyResolutions as f}from"./resolver/apply-resolutions.js";import{resolveConflict as p}from"./resolver/resolve-conflict.js";import{resolveConflicts as m}from"./resolver/resolve-conflicts.js";import{buildOperationDigest as h,summarizeOperation as g}from"./resolver/summarize-operation.js";export{e as AIError,t as ConflictError,n as ConflictGitPortMissingOpError,f as applyResolutions,h as buildOperationDigest,r as checkoutFile,c as createResolution,s as detect,l as detectFallbackCodeLoss,o as extractConflict,i as removeFile,p as resolveConflict,m as resolveConflicts,a as show,g as summarizeOperation,u as takeOurs,d as takeTheirs};
|
package/dist/ports/git.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ interface ConflictDiffOptions extends OpOptions {
|
|
|
11
11
|
interface ShowFileOptions extends OpOptions {
|
|
12
12
|
startLine?: number;
|
|
13
13
|
endLine?: number;
|
|
14
|
+
/** Skip the line cap the dispatcher applies by default. The cap keeps file content read for model
|
|
15
|
+
* prompts small; a caller feeding the content to a parser (not a prompt) needs the whole file, since
|
|
16
|
+
* a definition past the cap would silently drop out of the before/after comparison. */
|
|
17
|
+
fullContent?: boolean;
|
|
14
18
|
}
|
|
15
19
|
interface BlameOptions extends OpOptions {
|
|
16
20
|
ref?: string;
|
|
@@ -29,10 +33,20 @@ interface LogOptions extends OpOptions {
|
|
|
29
33
|
interface ShowOptions extends OpOptions {
|
|
30
34
|
path?: string;
|
|
31
35
|
}
|
|
36
|
+
/** Index modes of a path's unmerged stages: 1 = merge base, 2 = ours, 3 = theirs. Each value is the
|
|
37
|
+
* six-digit mode git records for that stage, or `null` when the stage is absent. */
|
|
38
|
+
interface UnmergedStages {
|
|
39
|
+
stage1: string | null;
|
|
40
|
+
stage2: string | null;
|
|
41
|
+
stage3: string | null;
|
|
42
|
+
}
|
|
32
43
|
interface UnmergedEntry {
|
|
33
44
|
path: string;
|
|
34
45
|
/** Canonical reason parsed from the XY status code. See {@link UnmergedReason}. */
|
|
35
46
|
reason: UnmergedReason;
|
|
47
|
+
/** Which index stages hold this path, and at what mode. Absent when the host's own
|
|
48
|
+
* `unmergedEntries` op does not report it. */
|
|
49
|
+
stages?: UnmergedStages;
|
|
36
50
|
}
|
|
37
51
|
interface ConflictGitOps {
|
|
38
52
|
readFile?: (path: string, opts?: OpOptions) => Promise<string>;
|
|
@@ -74,6 +88,11 @@ interface ConflictGitOps {
|
|
|
74
88
|
writeFile?: (path: string, content: string, opts?: OpOptions) => Promise<void>;
|
|
75
89
|
stageFiles?: (paths: string[], opts?: OpOptions) => Promise<void>;
|
|
76
90
|
checkoutFile?: (path: string, side: 'ours' | 'theirs', opts?: OpOptions) => Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Removes `path` from the index and working tree. Must tolerate a path with no working-tree file to
|
|
93
|
+
* remove — a stage-1-only rename/rename base path is unmerged with nothing checked out — so a host
|
|
94
|
+
* that unlinks the file before untracking it must not fail on `ENOENT`.
|
|
95
|
+
*/
|
|
77
96
|
removeFile?: (path: string, opts?: OpOptions) => Promise<void>;
|
|
78
97
|
}
|
|
79
98
|
type ConflictGitPort = ConflictGitOps & {
|
|
@@ -85,4 +104,4 @@ declare class ConflictGitPortMissingOpError extends Error {
|
|
|
85
104
|
constructor(op: string, hint?: string);
|
|
86
105
|
}
|
|
87
106
|
//#endregion
|
|
88
|
-
export { BlameOptions, ConflictDiffOptions, ConflictGitOps, ConflictGitPort, ConflictGitPortMissingOpError, GrepOptions, LogOptions, OpOptions, ShowFileOptions, ShowOptions, UnmergedEntry, UnmergedReason };
|
|
107
|
+
export { BlameOptions, ConflictDiffOptions, ConflictGitOps, ConflictGitPort, ConflictGitPortMissingOpError, GrepOptions, LogOptions, OpOptions, ShowFileOptions, ShowOptions, UnmergedEntry, UnmergedReason, UnmergedStages };
|
package/dist/ports/parser.d.ts
CHANGED
|
@@ -26,9 +26,15 @@ interface SyntaxIssue {
|
|
|
26
26
|
line: number;
|
|
27
27
|
message: string;
|
|
28
28
|
}
|
|
29
|
-
/** Per-file parse result.
|
|
30
|
-
* detector then skips it rather than emitting false findings. */
|
|
29
|
+
/** Per-file parse result. */
|
|
31
30
|
interface ParsedFile {
|
|
31
|
+
/** `false` when the backend has no grammar for this file type at all (a lockfile, an image, a
|
|
32
|
+
* language it does not implement). The detector skips the file and reports reduced coverage rather
|
|
33
|
+
* than emitting false findings. A file the backend *does* handle but whose content failed to parse
|
|
34
|
+
* is `supported: true` with the failure in `syntaxErrors` — that is a finding, not a coverage gap,
|
|
35
|
+
* and conflating the two lets a broken merge result read as clean. */
|
|
36
|
+
supported: boolean;
|
|
37
|
+
/** Backend's language id. Free-form; only meaningful when `supported`. */
|
|
32
38
|
language: string;
|
|
33
39
|
definitions: SymbolDefinition[];
|
|
34
40
|
references: SymbolReference[];
|
|
@@ -40,18 +46,33 @@ interface ParsedFile {
|
|
|
40
46
|
bindings?: SymbolDefinition[];
|
|
41
47
|
syntaxErrors: SyntaxIssue[];
|
|
42
48
|
}
|
|
49
|
+
/** What a backend can actually do, so the detector reports the coverage it really achieved. */
|
|
50
|
+
interface ParserCapabilities {
|
|
51
|
+
/** Whether the backend resolves references across files (imports and their targets). A backend that
|
|
52
|
+
* cannot — a plain tree-sitter parser, which sees one file at a time — reports every cross-file use
|
|
53
|
+
* as unresolved on both parents *and* the result, so no before/after transition exists and no
|
|
54
|
+
* cross-file break can ever be found. Declaring `false` makes that limit visible in the report
|
|
55
|
+
* instead of letting an empty findings list read as a clean cross-file check. */
|
|
56
|
+
crossFileResolution: boolean;
|
|
57
|
+
}
|
|
43
58
|
/**
|
|
44
59
|
* Parsing / symbol-resolution capability supplied per surface — merge-mate via tree-sitter,
|
|
45
60
|
* GitLens/GKD via a language server. The detector stays free of any parser dependency; the port
|
|
46
|
-
* abstracts the precision difference between backends.
|
|
61
|
+
* abstracts the precision difference between backends and asks them to declare it.
|
|
47
62
|
*/
|
|
48
63
|
interface ParserPort {
|
|
64
|
+
/** What this backend can resolve. Omitted means the conservative reading: no cross-file resolution. */
|
|
65
|
+
capabilities?: ParserCapabilities;
|
|
49
66
|
/**
|
|
50
67
|
* Parse and resolve every file in `sources`, treating those contents as authoritative for their
|
|
51
68
|
* paths. Returns one {@link ParsedFile} per input path. Works only from the provided content — it
|
|
52
|
-
* never reads the filesystem itself.
|
|
69
|
+
* never reads the filesystem itself. `signal`, when provided, should abort an in-flight analysis.
|
|
70
|
+
*
|
|
71
|
+
* Calls are never overlapped: the package awaits each `analyze` before issuing the next, so a
|
|
72
|
+
* single-instance backend (a language server holding one document per path) is not asked to hold
|
|
73
|
+
* two versions of the same path at once. Implementations need no internal locking.
|
|
53
74
|
*/
|
|
54
|
-
analyze(sources: SourceOverlay): Promise<ReadonlyMap<string, ParsedFile>>;
|
|
75
|
+
analyze(sources: SourceOverlay, signal?: AbortSignal): Promise<ReadonlyMap<string, ParsedFile>>;
|
|
55
76
|
}
|
|
56
77
|
//#endregion
|
|
57
|
-
export { ParsedFile, ParserPort, SourceOverlay, SymbolDefinition, SymbolReference, SyntaxIssue };
|
|
78
|
+
export { ParsedFile, ParserCapabilities, ParserPort, SourceOverlay, SymbolDefinition, SymbolReference, SyntaxIssue };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FindingCategory } from "../types/findings.js";
|
|
2
|
+
import { SourceOverlay } from "./parser.js";
|
|
3
|
+
|
|
4
|
+
//#region src/ports/validator.d.ts
|
|
5
|
+
interface ValidatorFinding {
|
|
6
|
+
/** Consumer's own category (e.g. `'type-error'`, `'lint:no-console'`). The package's own
|
|
7
|
+
* {@link FindingCategory} values are accepted too, and kept discoverable, but nothing requires them. */
|
|
8
|
+
category: FindingCategory | (string & {});
|
|
9
|
+
severity: 'error' | 'warning';
|
|
10
|
+
file: string;
|
|
11
|
+
/** 1-indexed. */
|
|
12
|
+
line?: number;
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The consumer's validation port. Plug in any checks — syntax, real import resolution, types, lint,
|
|
17
|
+
* project rules. **Non-blocking**: it runs post-refine and its findings are advisory (folded into the
|
|
18
|
+
* operation's findings and verdict), never a re-resolve. The only blocking gate is residual conflict
|
|
19
|
+
* markers, which the resolver checks built-in and language-agnostic — that is not this port's job.
|
|
20
|
+
*
|
|
21
|
+
* `sources` is currently always a single-file overlay: the package validates one file at a time, after
|
|
22
|
+
* that file's refine. The parameter is an overlay so a whole-set call can be added without a breaking
|
|
23
|
+
* change, but no such call site exists yet — do not write a validator that only works across files.
|
|
24
|
+
*/
|
|
25
|
+
interface Validator {
|
|
26
|
+
validate(sources: SourceOverlay, signal?: AbortSignal): Promise<ValidatorFinding[]>;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { Validator, ValidatorFinding };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ConflictGitPort } from "../ports/git.js";
|
|
2
|
-
import { DetectionReport } from "../types/findings.js";
|
|
2
|
+
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
|
+
import { Validator } from "../ports/validator.js";
|
|
5
6
|
|
|
6
7
|
//#region src/reference-integrity/detect.d.ts
|
|
7
8
|
interface DetectDeps {
|
|
@@ -9,13 +10,47 @@ interface DetectDeps {
|
|
|
9
10
|
/** Parsing/symbol-resolution capability. When absent, detection is skipped and reported as such. */
|
|
10
11
|
parser?: ParserPort;
|
|
11
12
|
signal?: AbortSignal;
|
|
13
|
+
/** Paths to keep out of scope — the caller's skipped and failed files, which still carry conflict
|
|
14
|
+
* markers on disk. See {@link import('./snapshot.js').SnapshotDeps.excludePaths}. */
|
|
15
|
+
excludePaths?: ReadonlySet<string>;
|
|
16
|
+
/** Override the per-operation scope cap. See {@link import('./snapshot.js').SnapshotDeps.maxScopeFiles}. */
|
|
17
|
+
maxScopeFiles?: number;
|
|
18
|
+
}
|
|
19
|
+
/** Parent contents for one file, named so the two sides cannot be transposed at a call site. */
|
|
20
|
+
interface ParentContents {
|
|
21
|
+
ours?: string;
|
|
22
|
+
theirs?: string;
|
|
12
23
|
}
|
|
13
24
|
/**
|
|
14
25
|
* Deterministic post-resolution detection: flags references that resolved in a pre-merge parent
|
|
15
26
|
* (ours/theirs) but no longer resolve in the merged result, plus duplicate definitions and syntax
|
|
16
|
-
* errors
|
|
17
|
-
*
|
|
27
|
+
* errors across the analyzed scope (the resolved files and the merge's auto-merged/incoming set).
|
|
28
|
+
* Report-only — it never mutates state or fails the operation. When no parser is supplied, or refs are
|
|
29
|
+
* missing, the report says so instead of implying a clean result.
|
|
18
30
|
*/
|
|
19
31
|
declare function detect(resolutions: Resolution[], context: ResolutionContext, deps: DetectDeps): Promise<DetectionReport>;
|
|
32
|
+
interface FileFindingsDeps {
|
|
33
|
+
parser: ParserPort;
|
|
34
|
+
validator?: Validator;
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}
|
|
37
|
+
/** Per-file findings split by who produced them. They are kept apart because only the validator half is
|
|
38
|
+
* the file's own to keep: parser findings computed over a *single-file* overlay are provisional — a
|
|
39
|
+
* symbol defined in another file is unresolved there by construction — so the authoritative version is
|
|
40
|
+
* whatever `detect()` says over the whole set. The fix loop uses both to converge and persists only
|
|
41
|
+
* `validator`. */
|
|
42
|
+
interface FileFindings {
|
|
43
|
+
parser: Finding[];
|
|
44
|
+
validator: Finding[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Per-file findings for one file's current content, compared against its pre-merge parents: structural
|
|
48
|
+
* (syntax/duplicate) + intra-file broken references (a local definition/binding the merge dropped while
|
|
49
|
+
* a use of it remains in the same file) + the consumer validator's per-file issues. All `tier: 'file'`.
|
|
50
|
+
* Cross-file references need the full set and are not computed here — `detect()` remains the
|
|
51
|
+
* authoritative whole-set report. This is the lightweight unit the per-file fix loop re-runs after each
|
|
52
|
+
* edit, over already-loaded parent snapshots (no git reads, no incoming-set discovery).
|
|
53
|
+
*/
|
|
54
|
+
declare function collectFileFindings(file: string, parents: ParentContents, resultContent: string, deps: FileFindingsDeps): Promise<FileFindings>;
|
|
20
55
|
//#endregion
|
|
21
|
-
export { DetectDeps, detect };
|
|
56
|
+
export { DetectDeps, FileFindings, FileFindingsDeps, ParentContents, collectFileFindings, detect };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{toFileFinding as e}from"../validation/findings.js";import{buildSnapshots as t,carriesNoOwnContent as n,safeReadFile as r}from"./snapshot.js";async function i(e,i,o){let s=o.parser;if(!s)return{findings:[],checks:{referenceIntegrity:`skipped-no-parser`,structural:`skipped-no-parser`}};try{let l=i.detectionRefs??i.refs?await t(e,i,o):null;if(l){let t=await s.analyze(l.ours,o.signal),n=await s.analyze(l.theirs,o.signal),r=await s.analyze(l.result,o.signal);return{findings:[...c(t,n,r),...u(r,[t,n]),...f(e,l)],checks:{referenceIntegrity:a(l,s,[t,n,r]),structural:m(r)?`partial-unsupported-language`:`ran`},scope:{inScope:l.inScope,readable:l.readable,truncated:l.truncated,incomingDiscovered:l.incomingDiscovered}}}let d=new Map;for(let t of e){let e=n(t)?await r(o.git,t.filePath,{signal:o.signal}):t.content;e!=null&&d.set(t.filePath,e)}let p=await s.analyze(d,o.signal);return{findings:u(p,[]),checks:{referenceIntegrity:`skipped-no-refs`,structural:m(p)?`partial-unsupported-language`:`ran`}}}catch(e){if(o.signal?.aborted)throw e;return{findings:[],checks:{referenceIntegrity:`errored`,structural:`errored`}}}}function a(e,t,n){return e.inScope>0&&e.readable===0?`skipped-unreadable-source`:t.capabilities?.crossFileResolution?n.some(m)?`partial-unsupported-language`:`ran`:`partial-no-cross-file-resolution`}async function o(t,n,r,i){let a=async e=>e==null?new Map:i.parser.analyze(new Map([[t,e]]),i.signal),o=await a(n.ours),s=await a(n.theirs),l=await i.parser.analyze(new Map([[t,r]]),i.signal),d=[...c(o,s,l).filter(e=>e.tier===`file`),...u(l,[o,s])],f=[];if(i.validator){let n=await i.validator.validate(new Map([[t,r]]),i.signal);for(let t of n)f.push(e(t))}return{parser:d,validator:f}}function s(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 c(e,t,n){let r=l(e,t),i=[];for(let[e,t]of n){let n=s(t);for(let a of t.references){if(a.resolved||n.has(a.name))continue;let t=r.get(h(e,a.name));t&&i.push({category:`broken-reference`,severity:`error`,tier:t.crossFile?`project`:`file`,location:{file:e,line:a.line,symbol:a.name},provenance:{side:t.side,line:t.line},...t.crossFile&&t.targetFile?{definitionFile:t.targetFile}:{},message:`Reference "${a.name}" resolved before the merge (on ${t.side}) but no longer resolves.`})}}return i}function l(e,t){let n=new Map,r=(e,t,r,i,a,o)=>{let s=h(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 u(e,t){let n=d(t),r=new Set;for(let e of t)for(let t of e.keys())r.add(t);let i=[];for(let[t,a]of e){let e=r.has(t),o=new Map;for(let r of a.definitions){let a=o.get(r.name);if(a==null){o.set(r.name,r.line);continue}!e||n.has(h(t,r.name))||i.push({category:`duplicate-definition`,severity:`warning`,tier:`file`,location:{file:t,line:r.line,symbol:r.name},message:`Duplicate definition of "${r.name}" (also defined at line ${a}).`})}for(let e of a.syntaxErrors)i.push({category:`syntax-error`,severity:`error`,tier:`file`,location:{file:t,line:e.line},message:e.message})}return i}function d(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=h(e,i.name);n.has(r)&&t.add(r),n.add(r)}}return t}function f(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=p(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 p(e,t){let n=new Set(t.split(`
|
|
2
2
|
`)),r=0;for(let t of e.split(`
|
|
3
|
-
`))t.trim()!==``&&!n.has(t)&&r++;return r}function
|
|
3
|
+
`))t.trim()!==``&&!n.has(t)&&r++;return r}function m(e){for(let t of e.values())if(!t.supported)return!0;return!1}function h(e,t){return`${e}\u0000${t}`}export{o as collectFileFindings,i as detect};
|
|
@@ -1,32 +1,54 @@
|
|
|
1
|
-
import { ConflictGitPort } from "../ports/git.js";
|
|
1
|
+
import { ConflictGitPort, OpOptions } from "../ports/git.js";
|
|
2
2
|
import { Resolution, ResolutionContext } from "../types/resolution.js";
|
|
3
3
|
import { SourceOverlay } from "../ports/parser.js";
|
|
4
4
|
|
|
5
5
|
//#region src/reference-integrity/snapshot.d.ts
|
|
6
|
+
/** Default backstop on how many files the detector will parse in one operation, overridable via
|
|
7
|
+
* `SnapshotDeps.maxScopeFiles`. The scope is ordered resolved-first, so a cap drops the least-important
|
|
8
|
+
* tail; truncation is reported, never silent. */
|
|
9
|
+
declare const DEFAULT_MAX_SCOPE_FILES = 400;
|
|
6
10
|
interface DetectionSnapshots {
|
|
7
11
|
ours: SourceOverlay;
|
|
8
12
|
theirs: SourceOverlay;
|
|
9
13
|
result: SourceOverlay;
|
|
10
|
-
/** Number of files
|
|
11
|
-
|
|
14
|
+
/** Number of files taken into scope (after the cap). */
|
|
15
|
+
inScope: number;
|
|
12
16
|
/** How many scope files had loadable parent content on `ours` or `theirs`. When this is 0 the
|
|
13
17
|
* before/after comparison had nothing to read — a total read failure, not a clean result. */
|
|
14
18
|
readable: number;
|
|
15
19
|
/** Whether the scope exceeded the cap and was truncated. */
|
|
16
20
|
truncated: boolean;
|
|
21
|
+
/** Whether the incoming/auto-merged set could be discovered (or was supplied by the consumer). */
|
|
22
|
+
incomingDiscovered: boolean;
|
|
17
23
|
}
|
|
18
24
|
interface SnapshotDeps {
|
|
19
25
|
git: ConflictGitPort;
|
|
20
26
|
signal?: AbortSignal;
|
|
27
|
+
/** Paths to keep out of scope entirely. The caller passes the files it skipped or failed on: those
|
|
28
|
+
* still carry conflict markers in the working tree, so parsing them would report the marker text as
|
|
29
|
+
* syntax errors and blame the merge for a file nobody resolved. */
|
|
30
|
+
excludePaths?: ReadonlySet<string>;
|
|
31
|
+
/** Cap on files parsed per operation. Default {@link DEFAULT_MAX_SCOPE_FILES}. Exceeding it sets
|
|
32
|
+
* `truncated`, which keeps the verdict off `clean`; raise it so a large merge can still auto-apply. */
|
|
33
|
+
maxScopeFiles?: number;
|
|
21
34
|
}
|
|
22
35
|
/**
|
|
23
36
|
* Builds the file-content overlays the detector compares: both pre-merge parents (read from git
|
|
24
37
|
* objects — no working tree needed) and the resolved result (in-memory resolutions overlaid on the
|
|
25
38
|
* surrounding files). Scope is the resolved files plus the merge's incoming/auto-merged set, which
|
|
26
39
|
* the detector discovers itself via the git port (`changedFiles(base, theirs)`), unless the consumer
|
|
27
|
-
* supplies `context.touchedFiles` as an override. Returns `null` when `context.
|
|
28
|
-
* before/after comparison is impossible without the parent refs.
|
|
40
|
+
* supplies `context.touchedFiles` as an override. Returns `null` when neither `context.detectionRefs`
|
|
41
|
+
* nor `context.refs` is present — the before/after comparison is impossible without the parent refs.
|
|
29
42
|
*/
|
|
30
43
|
declare function buildSnapshots(resolutions: Resolution[], context: ResolutionContext, deps: SnapshotDeps): Promise<DetectionSnapshots | null>;
|
|
44
|
+
/** True for a resolution whose `content` is empty by contract because its result lives outside the
|
|
45
|
+
* resolution: `mechanical` (git merged the path into the working tree itself) or a whole-side take on
|
|
46
|
+
* a marker-free entry. Such a path is kept out of the result overlay; overlaying the empty string would
|
|
47
|
+
* show the parser an emptied file and report every symbol in it as dropped. `detect()`'s no-refs branch
|
|
48
|
+
* has no parent snapshots to prefer, so there it always falls back to the working tree. */
|
|
49
|
+
declare function carriesNoOwnContent(resolution: Resolution): boolean;
|
|
50
|
+
/** Working-tree read that reports an unreadable file as `null` rather than throwing. Shared with
|
|
51
|
+
* `detect()`, whose no-refs branch needs the same fallback for a resolution that carries no content. */
|
|
52
|
+
declare function safeReadFile(git: ConflictGitPort, path: string, opts: OpOptions | undefined): Promise<string | null>;
|
|
31
53
|
//#endregion
|
|
32
|
-
export { DetectionSnapshots, SnapshotDeps, buildSnapshots };
|
|
54
|
+
export { DEFAULT_MAX_SCOPE_FILES, DetectionSnapshots, SnapshotDeps, buildSnapshots, carriesNoOwnContent, safeReadFile };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{changedFiles as e,mergeBase as t,readFile as n,showFile as r}from"../git/port-dispatch.js";async function
|
|
1
|
+
import{changedFiles as e,mergeBase as t,readFile as n,showFile as r}from"../git/port-dispatch.js";const i=400;async function a(e,t,n){let r=t.detectionRefs??t.refs;if(!r)return null;let i=n.signal?{signal:n.signal}:void 0,a={...i,fullContent:!0},d=new Map(e.filter(e=>!c(e)).map(e=>[e.filePath,e.content])),f=t.touchedFiles?{files:t.touchedFiles,discovered:!0}:await o(n.git,r,i),p=new Map(e.flatMap(e=>{let t=l(e);return t?[[e.filePath,t]]:[]})),m=n.excludePaths,h=[...new Set([...e.map(e=>e.filePath),...f.files,...t.fileNeighbors??[]])].filter(e=>!m?.has(e)),g=n.maxScopeFiles??400,_=h.length>g,v=_?h.slice(0,g):h,y=new Map,b=new Map,x=new Map,S={ours:y,theirs:b},C=0;for(let e of v){let t=await s(n.git,r.ours,e,a);t&&y.set(e,t);let o=await s(n.git,r.theirs,e,a);o&&b.set(e,o),(t||o)&&C++;let c=p.get(e),l=c?S[c].get(e):void 0,f=d.get(e)??l??await u(n.git,e,i);f!=null&&x.set(e,f)}return{ours:y,theirs:b,result:x,inScope:v.length,readable:C,truncated:_,incomingDiscovered:f.discovered}}async function o(n,r,i){try{return{files:await e(n,r.base??await t(n,r.ours,r.theirs,i),r.theirs,i),discovered:!0}}catch(e){return d(e,i),{files:[],discovered:!1}}}async function s(e,t,n,i){try{return await r(e,t,n,i)}catch(e){return d(e,i),null}}function c(e){return e.strategy===`mechanical`||l(e)!==null}function l(e){return e.content!==``||e.chunks!=null?null:e.strategy===`take-ours`?`ours`:e.strategy===`take-theirs`?`theirs`:null}async function u(e,t,r){try{return await n(e,t,r)}catch(e){return d(e,r),null}}function d(e,t){if(t?.signal?.aborted)throw e}export{i as DEFAULT_MAX_SCOPE_FILES,a as buildSnapshots,c as carriesNoOwnContent,u as safeReadFile};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Conflict } from "../types/conflict.js";
|
|
2
|
+
|
|
3
|
+
//#region src/resolution/detect-code-loss.d.ts
|
|
4
|
+
interface CodeLossDetection {
|
|
5
|
+
/** The side thrown away by the wholesale fallback. */
|
|
6
|
+
discardedSide: 'ours' | 'theirs';
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Reports whether a wholesale `take-ours`/`take-theirs` fallback would discard already-integrated
|
|
10
|
+
* work. On rebase "ours" is everything resolved so far, so taking the other side wholesale can wipe
|
|
11
|
+
* it (AGP-430).
|
|
12
|
+
*
|
|
13
|
+
* Any text conflict counts as loss. The fallback is applied with `git checkout --ours/--theirs`,
|
|
14
|
+
* which swaps the whole file for one stage, so whatever git auto-merged outside the conflict markers
|
|
15
|
+
* is discarded with it. Comparing marker regions cannot rule that out: it answers a narrower question
|
|
16
|
+
* than the write asks.
|
|
17
|
+
*
|
|
18
|
+
* Returns `null` only for the delete-modify direction that keeps content, the one case where the
|
|
19
|
+
* fallback is safe by construction.
|
|
20
|
+
*/
|
|
21
|
+
declare function detectFallbackCodeLoss(conflict: Conflict, strategy: 'take-ours' | 'take-theirs'): CodeLossDetection | null;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { CodeLossDetection, detectFallbackCodeLoss };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e,t){let n=t===`take-theirs`?`ours`:`theirs`;return e.type===`delete-modify`?t===`take-theirs`&&e.deletedBy===`theirs`||t===`take-ours`&&e.deletedBy===`ours`?{discardedSide:n}:null:e.markers.length>0?{discardedSide:n}:null}export{e as detectFallbackCodeLoss};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{checkoutFile as e,removeFile as t,stageFiles as n,writeFile as r}from"../git/port-dispatch.js";async function i(i,a){let o=i.filter(e=>e.strategy!==`skipped`);if(o.length===0)return;let s=new Set;for(let n of o)switch(n.strategy){case`deleted`:await t(a.git,n.filePath),s.add(n.filePath);break;case`take-ours`:await e(a.git,n.filePath,`ours`);break;case`take-theirs`:await e(a.git,n.filePath,`theirs`);break;default:await r(a.git,n.filePath,n.content)}let c=o.filter(e=>!s.has(e.filePath)).map(e=>e.filePath);c.length>0&&await n(a.git,c)}export{i as applyResolutions};
|
|
1
|
+
import{checkoutFile as e,removeFile as t,stageFiles as n,writeFile as r}from"../git/port-dispatch.js";async function i(i,a){let o=i.filter(e=>e.strategy!==`skipped`);if(o.length===0)return;let s=new Set;for(let n of o){switch(n.strategy){case`deleted`:await t(a.git,n.filePath),s.add(n.filePath);break;case`take-ours`:await e(a.git,n.filePath,`ours`);break;case`take-theirs`:await e(a.git,n.filePath,`theirs`);break;case`mechanical`:break;default:await r(a.git,n.filePath,n.content)}for(let e of n.retiredPaths??[])await t(a.git,e),s.add(e)}let c=o.filter(e=>!s.has(e.filePath)).map(e=>e.filePath);c.length>0&&await n(a.git,c)}export{i as applyResolutions};
|
package/dist/resolver/deps.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ConflictGitPort } from "../ports/git.js";
|
|
2
2
|
import { ConflictModelPort, ResolverConfig } from "../ports/model.js";
|
|
3
3
|
import { ParserPort } from "../ports/parser.js";
|
|
4
|
-
import { Validator } from "../ports/
|
|
4
|
+
import { Validator } from "../ports/validator.js";
|
|
5
5
|
import { ConflictProgressEvent } from "../types/events.js";
|
|
6
6
|
|
|
7
7
|
//#region src/resolver/deps.d.ts
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ConflictGitPort } from "../ports/git.js";
|
|
2
|
+
import { DetectionReport } from "../types/findings.js";
|
|
3
|
+
import { Resolution, ResolutionContext } from "../types/resolution.js";
|
|
4
|
+
import { ConflictModelPort } from "../ports/model.js";
|
|
5
|
+
import { ParserPort } from "../ports/parser.js";
|
|
6
|
+
import { Validator } from "../ports/validator.js";
|
|
7
|
+
import { ParentContents } from "../reference-integrity/detect.js";
|
|
8
|
+
import { ConflictProgressEvent } from "../types/events.js";
|
|
9
|
+
import { StepConfig } from "../types/step.js";
|
|
10
|
+
import { ModelBudget } from "./model-budget.js";
|
|
11
|
+
|
|
12
|
+
//#region src/resolver/fix-loop.d.ts
|
|
13
|
+
interface FixLoopDeps {
|
|
14
|
+
model: ConflictModelPort;
|
|
15
|
+
parser: ParserPort;
|
|
16
|
+
validator?: Validator;
|
|
17
|
+
config?: StepConfig;
|
|
18
|
+
onProgress?: (event: ConflictProgressEvent) => void;
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
/** Global model-call budget for the operation. When exhausted, the loop stops before any further
|
|
21
|
+
* model call and leaves the remaining findings unfixed. Absent = unlimited. */
|
|
22
|
+
budget?: ModelBudget;
|
|
23
|
+
/** Invoked when the budget stops work. Deduped by {@link ModelBudget}, so a caller may wire this
|
|
24
|
+
* straight to a progress emit. */
|
|
25
|
+
onBudgetExhausted?: () => void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Per-file convergence loop (opt-in `config.fix`). Re-detects the file's per-file findings, feeds the
|
|
29
|
+
* error-severity ones back through refine as fix instructions, and repeats until the file is clean, a
|
|
30
|
+
* round makes no progress or makes things worse, or the iteration budget runs out. What it cannot fix
|
|
31
|
+
* stays in the report — the verdict is computed from that remainder, never from a stale pre-fix set.
|
|
32
|
+
*
|
|
33
|
+
* Only intra-file findings are addressed here (from {@link collectFileFindings}); cross-file references
|
|
34
|
+
* are a separate set-level stage. `dropped-side` is not produced per-file and so is never routed. The
|
|
35
|
+
* caller should invoke this only for fixable text files (see {@link isFixable}): AI resolutions and
|
|
36
|
+
* non-deliberate whole-side takes, whose edited content the apply step can write.
|
|
37
|
+
*/
|
|
38
|
+
declare function runFixLoop(resolution: Resolution, parents: ParentContents, deps: FixLoopDeps): Promise<Resolution>;
|
|
39
|
+
interface FixPhaseDeps {
|
|
40
|
+
model: ConflictModelPort;
|
|
41
|
+
git: ConflictGitPort;
|
|
42
|
+
parser: ParserPort;
|
|
43
|
+
validator?: Validator;
|
|
44
|
+
config?: StepConfig;
|
|
45
|
+
onProgress?: (event: ConflictProgressEvent) => void;
|
|
46
|
+
signal?: AbortSignal;
|
|
47
|
+
/** Global model-call budget for the operation. When exhausted, both fix stages stop before any
|
|
48
|
+
* further model call, leaving the remainder in the report. Absent = unlimited. */
|
|
49
|
+
budget?: ModelBudget;
|
|
50
|
+
/** Paths kept out of the detection scope — the caller's skipped and failed files. */
|
|
51
|
+
excludePaths?: ReadonlySet<string>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The fix phase (opt-in). Stage 2: per-file convergence over each AI resolution ({@link runFixLoop}).
|
|
55
|
+
* Stage 3: route cross-file (`tier: 'project'`) broken references back to the refine of their use-site
|
|
56
|
+
* file and re-detect over the whole set, up to `crossFileMaxIterations` rounds. Mutates `resolutions`
|
|
57
|
+
* in place and returns the final whole-set {@link DetectionReport} — which, being whole-set, also
|
|
58
|
+
* re-checks per-file for files touched by cross-file fixes.
|
|
59
|
+
*
|
|
60
|
+
* Cross-file findings on files with no fixable resolution (e.g. auto-merged, or a deliberate/fallback
|
|
61
|
+
* take) are not routed: they have no carrier for an edit yet (needs a patches channel), so they remain
|
|
62
|
+
* in the report unchanged. When routing a break, the defining file's current definitions are shown to
|
|
63
|
+
* the refining model so a renamed/moved symbol is fixable from the use site (see {@link buildRelatedDefinitions}).
|
|
64
|
+
*/
|
|
65
|
+
declare function runFixPhase(resolutions: Resolution[], context: ResolutionContext, deps: FixPhaseDeps): Promise<DetectionReport>;
|
|
66
|
+
//#endregion
|
|
67
|
+
export { FixLoopDeps, FixPhaseDeps, runFixLoop, runFixPhase };
|
|
@@ -0,0 +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{runRefinePass as i}from"./refine-pass.js";import{CONFIDENCE_CAPS as a}from"./resolve-conflict.js";async function o(e,n,o){let d=o.config?.fixLoopMaxIterations??3,f={parser:o.parser,validator:o.validator,signal:o.signal},p={content:e.content,findings:await(r=>t(e.filePath,n,r,f))(e.content)},g=s(p.findings);if(g.length===0)return l(o,e.filePath,0,0),c(e,p.findings.validator);if(o.budget?.exhausted())return o.onBudgetExhausted?.(),l(o,e.filePath,0,g.length),c(e,p.findings.validator);o.onProgress?.({type:`fix:started`,filePath:e.filePath,findingCount:g.length});let _=[],v=0,y=m(g);for(;v<d&&g.length>0;){if(o.budget?.exhausted()){o.onBudgetExhausted?.();break}v++;let a=await i(p.content,e.description,u(g),{model:o.model,signal:o.signal,temperature:o.config?.temperature,refineMaxSteps:o.config?.refineMaxSteps,onProgress:o.onProgress,filePath:e.filePath,budget:o.budget});if(a.edits.length===0)break;if(!r(e.filePath,a.content).valid){o.onProgress?.({type:`refine:discarded`,filePath:e.filePath,reason:`Fix edits introduced conflict markers.`});break}let c=await t(e.filePath,n,a.content,f),l=s(c);if(h(g,l)){o.onProgress?.({type:`refine:discarded`,filePath:e.filePath,reason:`Fix edits regressed the file (was ${g.length} error(s), now ${l.length}).`});break}p={content:a.content,findings:c},_.push(...a.edits),g=l;let d=m(g);if(d===y)break;y=d}l(o,e.filePath,v,g.length);let b=c({...e,content:p.content},p.findings.validator);return _.length===0?b:{...b,strategy:`ai`,edits:[...e.edits??[],..._],confidence:Math.min(b.confidence,a.refineApplied)}}function s(e){return[...e.parser,...e.validator].filter(e=>e.severity===`error`)}function c(e,t){if(t.length===0){if(!e.findings)return e;let{findings:t,...n}=e;return n}return{...e,findings:t}}function l(e,t,n,r){e.onProgress?.({type:`fix:completed`,filePath:t,iterations:n,remainingFindings:r})}function u(e){return e.slice(0,20).map(d)}function d(e){let t=e.location.symbol?f(e.location.symbol):void 0,n=t?`"${t}" (line ${e.location.line})`:`line ${e.location.line}`;return`${f(e.category)} at ${n}: ${f(e.message)}`}function f(e){return e.replace(/[\p{Cc}\p{Cf}]/gu,` `).replace(/\s+/g,` `).trim().slice(0,300)}function p(e){return JSON.stringify([e.category,e.location.file,e.location.symbol??``])}function m(e){return e.map(p).sort().join(`
|
|
2
|
+
`)}function h(e,t){if(t.length>e.length)return!0;let n=new Set(e.map(p));return t.some(e=>!n.has(p(e)))}async function g(t,r,i){let a=i.config?.maxDetectionScopeFiles,o={git:i.git,parser:i.parser,signal:i.signal,excludePaths:i.excludePaths,maxScopeFiles:a},s=await e(t,r,{git:i.git,signal:i.signal,excludePaths:i.excludePaths,maxScopeFiles:a}),c=()=>i.onProgress?.({type:`fix:budget-exhausted`,spent:i.budget?.spent()??0,limit:i.budget?.limit()??0}),l={model:i.model,parser:i.parser,validator:i.validator,config:i.config,onProgress:i.onProgress,signal:i.signal,budget:i.budget,onBudgetExhausted:()=>i.budget?.notifyExhaustedOnce(c)};return await v(t,s,l),s?y(t,r,s,o,l,i):n(t,r,o)}function _(e){return e.strategy===`ai`?!0:e.strategy===`take-ours`||e.strategy===`take-theirs`?!e.fallback&&!e.deliberateTake:!1}async function v(e,t,n){for(let r=0;r<e.length;r++){let i=e[r];if(_(i)){if(n.signal?.aborted)return;try{e[r]=await o(i,b(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 y(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=S(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 x(s,n,b(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 b(e,t){return{ours:e?.ours.get(t),theirs:e?.theirs.get(t)}}async function x(e,n,o,l,d){let f={parser:d.parser,validator:d.validator,signal:d.signal},p=s(await t(e.filePath,o,e.content,f)),m=await C(n,l,d.parser,d.signal),g=await i(e.content,e.description,u(n),{model:d.model,signal:d.signal,temperature:d.config?.temperature,refineMaxSteps:d.config?.refineMaxSteps,onProgress:d.onProgress,filePath:e.filePath,budget:d.budget,relatedDefinitions:m});if(g.edits.length===0)return e;if(!r(e.filePath,g.content).valid)return d.onProgress?.({type:`refine:discarded`,filePath:e.filePath,reason:`Cross-file fix introduced conflict markers.`}),e;let _=await t(e.filePath,o,g.content,f);return h(p,s(_))?(d.onProgress?.({type:`refine:discarded`,filePath:e.filePath,reason:`Cross-file fix left the file with a new or additional error.`}),e):c({...e,content:g.content,strategy:`ai`,edits:[...e.edits??[],...g.edits],confidence:Math.min(e.confidence,a.refineApplied)},_.validator)}function S(e,t){let n=new Set(t.filter(_).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 C(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=f(e.name),n=e.kind?f(e.kind):void 0;return n?`${t} (${n}, line ${e.line})`:`${t} (line ${e.line})`});a.push({file:f(e),symbols:s})}return a.length>0?a:void 0}export{o as runFixLoop,g as runFixPhase};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { UnmergedEntry, UnmergedStages } from "../ports/git.js";
|
|
2
|
+
|
|
3
|
+
//#region src/resolver/mechanical.d.ts
|
|
4
|
+
declare const SYMLINK_MODE = "120000";
|
|
5
|
+
/** How an unmerged entry may be settled without a model call. `mechanical` — git already decided the
|
|
6
|
+
* content and placed it in the worktree; `removed` — both sides removed the path from their tree, so
|
|
7
|
+
* only the surviving name (not its deletion) is undecided; `skip` — a tree-level entry no content
|
|
8
|
+
* resolver can act on; `none` — ordinary, hand it to the normal resolve path. */
|
|
9
|
+
type UnmergedClassification = {
|
|
10
|
+
kind: 'mechanical';
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'removed';
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'skip';
|
|
15
|
+
reason: 'submodule';
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'none';
|
|
18
|
+
};
|
|
19
|
+
/** Classifies every unmerged entry of one operation by its index-stage signature, keyed by path. */
|
|
20
|
+
declare function classifyUnmergedEntries(entries: UnmergedEntry[]): Map<string, UnmergedClassification>;
|
|
21
|
+
/** The mode of the sole present stage when that stage is 2 or 3 — no base and one side's content means
|
|
22
|
+
* git already computed the result — and `null` for every other signature. */
|
|
23
|
+
declare function loneSideStageMode(stages: UnmergedStages): string | null;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { SYMLINK_MODE, UnmergedClassification, classifyUnmergedEntries, loneSideStageMode };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=`120000`;function t(e){let t=e.some(e=>e.stages!=null&&r(e.stages)),i=new Map;for(let r of e)i.set(r.path,n(r.stages,t));return i}function n(e,t){if(!e)return{kind:`none`};if(r(e))return{kind:`removed`};let n=i(e);return n===null?{kind:`none`}:n===`160000`?{kind:`skip`,reason:`submodule`}:t?{kind:`none`}:{kind:`mechanical`}}function r(e){return e.stage1!==null&&e.stage2===null&&e.stage3===null}function i(e){return e.stage1===null?e.stage2!==null&&e.stage3===null?e.stage2:e.stage3!==null&&e.stage2===null?e.stage3:null:null}export{e as SYMLINK_MODE,t as classifyUnmergedEntries,i as loneSideStageMode};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ConflictModelPort } from "../ports/model.js";
|
|
2
|
+
|
|
3
|
+
//#region src/resolver/model-budget.d.ts
|
|
4
|
+
/** A shared, per-operation counter of `model.generate` calls. Created once per {@link resolveConflicts}
|
|
5
|
+
* run; the wrapped model increments the counter but never blocks — enforcement is the caller's job via
|
|
6
|
+
* {@link ModelBudget.exhausted}, checked before fix-phase model calls only. Counting instead of throwing
|
|
7
|
+
* is deliberate: a throw inside the resolve loop would be caught by the fallback path and degrade a good
|
|
8
|
+
* resolution into a whole-side take — the exact data loss this package guards against. */
|
|
9
|
+
interface ModelBudget {
|
|
10
|
+
/** Wrap a model so every `generate` call increments this budget's shared counter, then delegates to
|
|
11
|
+
* the real model unchanged. Multiple wraps share one counter. */
|
|
12
|
+
wrap(model: ConflictModelPort): ConflictModelPort;
|
|
13
|
+
spent(): number;
|
|
14
|
+
/** The configured cap. 0 when the budget is unlimited — check {@link ModelBudget.exhausted} rather
|
|
15
|
+
* than comparing against this. */
|
|
16
|
+
limit(): number;
|
|
17
|
+
/** Calls left before the cap. `Infinity` when the budget is unlimited (`max` undefined). */
|
|
18
|
+
remaining(): number;
|
|
19
|
+
exhausted(): boolean;
|
|
20
|
+
/** Runs `notify` the first time it is called on this budget, and never again. The budget owns the
|
|
21
|
+
* once-only invariant so no caller can get the dedupe wrong across the two fix stages. */
|
|
22
|
+
notifyExhaustedOnce(notify: () => void): void;
|
|
23
|
+
}
|
|
24
|
+
declare function createModelBudget(max?: number): ModelBudget;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { ModelBudget, createModelBudget };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e){let t=Number.isFinite(e)?e:void 0,n=0,r=!1,i=()=>t===void 0?1/0:t-n;return{wrap(e){return{generate:t=>(n++,e.generate(t))}},spent:()=>n,limit:()=>t??0,remaining:i,exhausted:()=>i()<=0,notifyExhaustedOnce(e){r||(r=!0,e())}}}export{e as createModelBudget};
|
|
@@ -9,5 +9,13 @@ interface ParsedResolution {
|
|
|
9
9
|
followUpInstructions?: string[];
|
|
10
10
|
}
|
|
11
11
|
declare function parseModelResponse(text: string, markerCount: number): ParsedResolution;
|
|
12
|
+
/** The model's answer to a rename/rename name decision. `path` is always one of the two candidates it
|
|
13
|
+
* was offered — never the base path and never a new name. */
|
|
14
|
+
interface RenameChoice {
|
|
15
|
+
path: string;
|
|
16
|
+
confidence: number;
|
|
17
|
+
description?: string;
|
|
18
|
+
}
|
|
19
|
+
declare function parseRenameChoice(text: string, candidates: readonly string[]): RenameChoice;
|
|
12
20
|
//#endregion
|
|
13
|
-
export { ParsedResolution, parseModelResponse };
|
|
21
|
+
export { ParsedResolution, RenameChoice, parseModelResponse, parseRenameChoice };
|