@gitkraken/compose-tools 0.4.0 → 0.6.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 +29 -0
- package/dist/errors/task.d.ts +11 -1
- package/dist/errors/task.js +1 -1
- package/dist/git/commit-chain.js +3 -4
- package/dist/git/reparent.js +3 -4
- package/dist/git/safety-range.d.ts +14 -2
- package/dist/git/safety-range.js +4 -5
- package/dist/git/utils/conventions.d.ts +7 -0
- package/dist/git/utils/conventions.js +8 -9
- package/dist/git/utils/refs.d.ts +18 -0
- package/dist/git/utils/refs.js +2 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +1 -1
- package/dist/tasks/analyze/analyze.js +1 -1
- package/dist/tasks/analyze/prompt.d.ts +4 -1
- package/dist/tasks/analyze/prompt.js +5 -5
- package/dist/tasks/analyze/schema.d.ts +127 -36
- package/dist/tasks/analyze/schema.js +1 -1
- package/dist/tasks/analyze/types.d.ts +8 -0
- package/dist/tasks/analyze/validator.js +2 -2
- package/dist/tasks/compose-group/compose-group.js +1 -1
- package/dist/tasks/compose-group/prompt.d.ts +4 -1
- package/dist/tasks/compose-group/prompt.js +23 -29
- package/dist/tasks/compose-group/types.d.ts +5 -0
- package/dist/tasks/group/group.js +1 -1
- package/dist/tasks/group/prompt.d.ts +4 -1
- package/dist/tasks/group/prompt.js +15 -21
- package/dist/tasks/group/schema.d.ts +89 -17
- package/dist/tasks/group/schema.js +1 -1
- package/dist/tasks/group/types.d.ts +8 -0
- package/dist/tasks/group/validator.js +6 -6
- package/dist/tasks/order/prompt.js +5 -5
- package/dist/tasks/order/schema.d.ts +35 -10
- package/dist/tasks/order/schema.js +1 -1
- package/dist/tasks/order/validator.js +2 -2
- package/dist/tasks/partition/prompt.js +9 -9
- package/dist/tasks/partition/schema.d.ts +46 -12
- package/dist/tasks/partition/schema.js +1 -1
- package/dist/tasks/partition/validator.js +2 -2
- package/dist/tasks/quick-compose/prompt.js +8 -9
- package/dist/tasks/quick-compose/schema.d.ts +142 -25
- package/dist/tasks/quick-compose/schema.js +1 -1
- package/dist/tasks/quick-compose/validator.js +5 -5
- package/dist/tasks/refine/refine.js +1 -1
- package/dist/tasks/refine/types.d.ts +14 -0
- package/dist/tasks/refine/validator.d.ts +5 -1
- package/dist/tasks/refine/validator.js +1 -1
- package/dist/tasks/utils/budget.d.ts +40 -0
- package/dist/tasks/utils/budget.js +1 -0
- package/dist/tasks/utils/json-schema.d.ts +61 -0
- package/dist/tasks/utils/json-schema.js +1 -0
- package/dist/tasks/utils/noise.d.ts +21 -0
- package/dist/tasks/utils/noise.js +2 -0
- package/dist/tasks/utils/redact.d.ts +7 -1
- package/dist/tasks/utils/redact.js +1 -1
- package/dist/workflows/compose.d.ts +47 -2
- package/dist/workflows/compose.js +3 -3
- package/dist/workflows/refine.d.ts +9 -1
- package/dist/workflows/refine.js +1 -1
- package/dist/workflows/split.js +3 -3
- package/dist/workflows/types.d.ts +14 -1
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e,n){let r=[],i=t(e,n,[],r);return r.length>0?{success:!1,issues:r}:{success:!0,data:i}}function t(e,t,s,c){switch(e.type){case`object`:return n(e,t,s,c);case`array`:return r(e,t,s,c);case`string`:return i(e,t,s,c);case`number`:case`integer`:return a(e,t,s,c);case`boolean`:if(typeof t!=`boolean`){c.push({path:s,message:o(`boolean`,t)});return}return t}}function n(e,n,r,i){if(typeof n!=`object`||!n||Array.isArray(n)){i.push({path:r,message:o(`object`,n)});return}let a=n,s=new Set(e.required),c={};for(let[n,o]of Object.entries(e.properties)){let e=a[n];if(e===void 0){o.type===`array`&&o.default!==void 0?c[n]=structuredClone(o.default):s.has(n)&&t(o,void 0,[...r,n],i);continue}c[n]=t(o,e,[...r,n],i)}return c}function r(e,n,r,i){if(!Array.isArray(n)){i.push({path:r,message:o(`array`,n)});return}return e.minItems!==void 0&&n.length<e.minItems&&i.push({path:r,message:`Too small: expected array to have >=${e.minItems} items`}),n.map((n,a)=>t(e.items,n,[...r,a],i))}function i(e,t,n,r){if(e.enum!==void 0){if(typeof t!=`string`||!e.enum.includes(t)){let t=e.enum.map(e=>JSON.stringify(e)).join(`|`);r.push({path:n,message:`Invalid option: expected one of ${t}`});return}return t}if(typeof t!=`string`){r.push({path:n,message:o(`string`,t)});return}return e.minLength!==void 0&&t.length<e.minLength&&r.push({path:n,message:`Too small: expected string to have >=${e.minLength} characters`}),e.maxLength!==void 0&&t.length>e.maxLength&&r.push({path:n,message:`Too big: expected string to have <=${e.maxLength} characters`}),t}function a(e,t,n,r){if(typeof t!=`number`||!Number.isFinite(t)){r.push({path:n,message:o(`number`,t)});return}if(e.type===`integer`&&!Number.isInteger(t))return r.push({path:n,message:`Invalid input: expected int, received number`}),t;let i=e.type===`integer`?`int`:`number`;return e.minimum!==void 0&&t<e.minimum&&r.push({path:n,message:`Too small: expected ${i} to be >=${e.minimum}`}),e.maximum!==void 0&&t>e.maximum&&r.push({path:n,message:`Too big: expected ${i} to be <=${e.maximum}`}),t}function o(e,t){return`Invalid input: expected ${e}, received ${s(t)}`}function s(e){return e===void 0?`undefined`:e===null?`null`:Array.isArray(e)?`array`:typeof e==`number`&&Number.isNaN(e)?`NaN`:typeof e}export{e as validateJsonSchema};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ComposeHunk } from "../types.js";
|
|
2
|
+
import { RedactHunkContent } from "./redact.js";
|
|
3
|
+
|
|
4
|
+
//#region src/tasks/utils/noise.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Default {@link RedactHunkContent} predicate for hunks that carry near-zero grouping signal
|
|
7
|
+
* but can dominate token count: lockfiles, vendored/build-output trees, minified or generated
|
|
8
|
+
* files, and binaries. Matched hunks keep their metadata; only the body is masked. Detects by
|
|
9
|
+
* path, by content markers, by minified line length, and by `isBinary`; renames are matched on
|
|
10
|
+
* `originalFileName` too.
|
|
11
|
+
*/
|
|
12
|
+
declare function isNoiseHunk(hunk: ComposeHunk): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Combines the built-in {@link isNoiseHunk} detector with an optional caller predicate.
|
|
15
|
+
* The result redacts a hunk when either predicate matches, so consumers extend the
|
|
16
|
+
* default without losing noise reduction. Returns {@link isNoiseHunk} unchanged when no
|
|
17
|
+
* caller predicate is supplied.
|
|
18
|
+
*/
|
|
19
|
+
declare function withDefaultNoiseRedaction(caller: RedactHunkContent | undefined): RedactHunkContent;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { isNoiseHunk, withDefaultNoiseRedaction };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=new Set([`package-lock.json`,`npm-shrinkwrap.json`,`yarn.lock`,`pnpm-lock.yaml`,`bun.lock`,`bun.lockb`,`composer.lock`,`gemfile.lock`,`podfile.lock`,`cargo.lock`,`poetry.lock`,`pipfile.lock`,`go.sum`,`flake.lock`,`gradle.lockfile`,`packages.lock.json`,`mix.lock`,`pubspec.lock`]),t=new Set([`node_modules`,`vendor`,`bower_components`,`dist`,`build`,`out`,`.next`,`.nuxt`,`coverage`,`__generated__`]),n=[/\.min\.(js|css|mjs|cjs)$/,/\.bundle\.js$/,/\.map$/,/\.generated\./,/\.pb\.(go|cc|h)$/,/_pb2(_grpc)?\.py$/,/\.(g|freezed)\.dart$/,/\.designer\.cs$/],r=[/@generated\b/,/Code generated .* DO NOT EDIT/,/linguist-generated/,/This file (was|is) (auto(matically)?[- ]?)?generated/i];function i(e){let t=e.replace(/\\/g,`/`),n=t.lastIndexOf(`/`);return n===-1?t:t.slice(n+1)}function a(r){let a=r.replace(/\\/g,`/`),o=i(a).toLowerCase();return e.has(o)||n.some(e=>e.test(o))?!0:a.split(`/`).some(e=>t.has(e.toLowerCase()))}function o(e){if(!e)return!1;let t=e.slice(0,4e3);if(r.some(e=>e.test(t)))return!0;let n=0;for(let t=0;t<e.length;t++){if(e[t]===`
|
|
2
|
+
`){n=0;continue}if(n++,n>=5e3)return!0}return!1}function s(e){return e.isBinary||a(e.fileName)||e.originalFileName&&a(e.originalFileName)?!0:o(e.content)}function c(e){return e?t=>s(t)||e(t):s}export{s as isNoiseHunk,c as withDefaultNoiseRedaction};
|
|
@@ -10,5 +10,11 @@ declare const REDACTED_HUNK_CONTENT = "[content redacted because this file is ex
|
|
|
10
10
|
*/
|
|
11
11
|
type RedactHunkContent = (hunk: ComposeHunk) => boolean;
|
|
12
12
|
declare function hunkContentForPrompt(hunk: ComposeHunk, redact: RedactHunkContent | undefined): string;
|
|
13
|
+
/**
|
|
14
|
+
* Wraps a predicate so each `hunk.index` is evaluated once. The noise detector scans hunk
|
|
15
|
+
* content, and the same predicate feeds both the redacted-count tally and every prompt build,
|
|
16
|
+
* so caching avoids repeated scans and keeps the two in agreement for non-deterministic inputs.
|
|
17
|
+
*/
|
|
18
|
+
declare function memoizeRedactHunkContent(redact: RedactHunkContent): RedactHunkContent;
|
|
13
19
|
//#endregion
|
|
14
|
-
export { REDACTED_HUNK_CONTENT, RedactHunkContent, hunkContentForPrompt };
|
|
20
|
+
export { REDACTED_HUNK_CONTENT, RedactHunkContent, hunkContentForPrompt, memoizeRedactHunkContent };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=`[content redacted because this file is excluded from AI processing — still assign this hunk to a commit based on its filename]`;function t(t,n){return n?.(t)?e:t.content}export{e as REDACTED_HUNK_CONTENT,t as hunkContentForPrompt};
|
|
1
|
+
const e=`[content redacted because this file is excluded from AI processing — still assign this hunk to a commit based on its filename]`;function t(t,n){return n?.(t)?e:t.content}function n(e){let t=new Map;return n=>{let r=t.get(n.index);if(r!==void 0)return r;let i=e(n);return t.set(n.index,i),i}}export{e as REDACTED_HUNK_CONTENT,t as hunkContentForPrompt,n as memoizeRedactHunkContent};
|
|
@@ -9,7 +9,7 @@ import { GroupingResult, GroupingStrategy } from "../tasks/group/types.js";
|
|
|
9
9
|
import { OrderingResult } from "../tasks/order/types.js";
|
|
10
10
|
import { ComposeSession } from "../tasks/session.js";
|
|
11
11
|
import { PartitionStrategy } from "../tasks/partition/types.js";
|
|
12
|
-
import { ComposeContext, ComposeSource, ComposeTarget } from "./types.js";
|
|
12
|
+
import { ComposeContext, ComposeSource, ComposeTarget, OverrideSafetyFailures } from "./types.js";
|
|
13
13
|
import { SafetySnapshot, StashConflict } from "../git/types.js";
|
|
14
14
|
import { ComposeBranchPlan } from "../git/apply-target.js";
|
|
15
15
|
import { AiModelPort, UndoId } from "@gitkraken/shared-tools";
|
|
@@ -43,10 +43,16 @@ interface ComposePlanInput {
|
|
|
43
43
|
hunkFilter?: ComposeInput['hunkFilter'];
|
|
44
44
|
/** See {@link ComposeInput.redactHunkContent}. */
|
|
45
45
|
redactHunkContent?: ComposeInput['redactHunkContent'];
|
|
46
|
+
/** See {@link ComposeInput.disableDefaultNoiseReduction}. */
|
|
47
|
+
disableDefaultNoiseReduction?: ComposeInput['disableDefaultNoiseReduction'];
|
|
48
|
+
/** See {@link ComposeInput.inputTokenBudget}. */
|
|
49
|
+
inputTokenBudget?: ComposeInput['inputTokenBudget'];
|
|
46
50
|
/** See {@link ComposeInput.session}. */
|
|
47
51
|
session?: ComposeSession;
|
|
48
52
|
/** See {@link ComposeInput.promptOverrides}. */
|
|
49
53
|
promptOverrides?: ComposeInput['promptOverrides'];
|
|
54
|
+
/** See {@link ComposeInput.overrideSafetyFailures}. */
|
|
55
|
+
overrideSafetyFailures?: ComposeInput['overrideSafetyFailures'];
|
|
50
56
|
}
|
|
51
57
|
/**
|
|
52
58
|
* Size of the diff the AI operated on — files, hunks, and per-line counts.
|
|
@@ -71,6 +77,10 @@ interface ComposePlanResult {
|
|
|
71
77
|
feedbackTargetCallId?: string;
|
|
72
78
|
/** See {@link ComposeResult.hunksFilteredCount}. */
|
|
73
79
|
hunksFilteredCount?: number;
|
|
80
|
+
/** See {@link ComposeResult.hunksRedactedCount}. */
|
|
81
|
+
hunksRedactedCount?: number;
|
|
82
|
+
/** See {@link ComposeResult.hunksCondensedCount}. */
|
|
83
|
+
hunksCondensedCount?: number;
|
|
74
84
|
/** See {@link ComposeResult.diffStats}. */
|
|
75
85
|
diffStats?: ComposeDiffStats;
|
|
76
86
|
}
|
|
@@ -101,6 +111,8 @@ interface ApplyComposePlanInput {
|
|
|
101
111
|
* diff hash never equals the cached filtered one). See {@link ComposeInput.hunkFilter}.
|
|
102
112
|
*/
|
|
103
113
|
hunkFilter?: ComposeInput['hunkFilter'];
|
|
114
|
+
/** See {@link ComposeInput.overrideSafetyFailures}. */
|
|
115
|
+
overrideSafetyFailures?: ComposeInput['overrideSafetyFailures'];
|
|
104
116
|
}
|
|
105
117
|
/** Result of {@link applyComposePlan}. */
|
|
106
118
|
interface ApplyComposePlanResult {
|
|
@@ -213,8 +225,23 @@ interface ComposeInput {
|
|
|
213
225
|
*
|
|
214
226
|
* Intended for `.aiexclude`/`.aiignore`/`.cursorignore`-style features: the AI assigns
|
|
215
227
|
* matching hunks to commits by filename alone, never seeing the diff body.
|
|
228
|
+
*
|
|
229
|
+
* Combined (OR) with the built-in noise detector unless `disableDefaultNoiseReduction`
|
|
230
|
+
* is set: a caller predicate extends the default, it does not replace it.
|
|
216
231
|
*/
|
|
217
232
|
redactHunkContent?: RedactHunkContent;
|
|
233
|
+
/**
|
|
234
|
+
* Opt out of the built-in noise detector (lockfiles, vendored/build trees, minified or
|
|
235
|
+
* generated files, binaries). On by default so pathological diffs don't send megabytes
|
|
236
|
+
* of generated content verbatim. When true, only `redactHunkContent` (if any) applies.
|
|
237
|
+
*/
|
|
238
|
+
disableDefaultNoiseReduction?: boolean;
|
|
239
|
+
/**
|
|
240
|
+
* Input-token ceiling for the hunk-bearing prompts (compose-group, analyze, group). When
|
|
241
|
+
* exceeded, hunks are condensed to metadata biggest-first; a diff that cannot fit even then
|
|
242
|
+
* throws `DiffTooLargeError`. Defaults to `DEFAULT_INPUT_TOKEN_BUDGET` (180k).
|
|
243
|
+
*/
|
|
244
|
+
inputTokenBudget?: number;
|
|
218
245
|
/**
|
|
219
246
|
* Per-task prompt overrides, keyed by {@link ComposeTaskName}. Consumers with
|
|
220
247
|
* existing prompt template services (server-driven templates, provider-specific
|
|
@@ -279,6 +306,11 @@ interface ComposeInput {
|
|
|
279
306
|
applyCommitIds?: string[];
|
|
280
307
|
/** Stash message used by the apply path when stashing pending workdir state. Defaults to `'compose backup'`. */
|
|
281
308
|
stashLabel?: string;
|
|
309
|
+
/**
|
|
310
|
+
* Opt out of individual rewrite-range safety checks. Optional, as is each flag within it.
|
|
311
|
+
* Currently only `interiorRefs` is supported — see {@link OverrideSafetyFailures}.
|
|
312
|
+
*/
|
|
313
|
+
overrideSafetyFailures?: OverrideSafetyFailures;
|
|
282
314
|
}
|
|
283
315
|
interface ComposePlan {
|
|
284
316
|
analysis?: AnalysisResult;
|
|
@@ -406,6 +438,19 @@ interface ComposeResult {
|
|
|
406
438
|
* Undefined when no filter ran.
|
|
407
439
|
*/
|
|
408
440
|
hunksFilteredCount?: number;
|
|
441
|
+
/**
|
|
442
|
+
* Count of hunks whose patch body was masked before the AI saw it (built-in noise detector
|
|
443
|
+
* plus any consumer `redactHunkContent`), so consumers can audit the on-by-default noise
|
|
444
|
+
* reduction. Undefined when no redaction predicate ran (e.g. `disableDefaultNoiseReduction`
|
|
445
|
+
* with no consumer predicate, or the apply-only path).
|
|
446
|
+
*/
|
|
447
|
+
hunksRedactedCount?: number;
|
|
448
|
+
/**
|
|
449
|
+
* Count of hunks condensed to metadata to fit the input-token budget (full to condensed).
|
|
450
|
+
* Non-zero signals the AI reasoned over less than the full diff, so grouping quality may
|
|
451
|
+
* degrade. Undefined when no AI phase ran (apply-only path).
|
|
452
|
+
*/
|
|
453
|
+
hunksCondensedCount?: number;
|
|
409
454
|
/** See {@link ApplyComposePlanResult.appliedCommitIds}. */
|
|
410
455
|
appliedCommitIds?: string[];
|
|
411
456
|
/** See {@link ApplyComposePlanResult.unappliedHunkIndices}. */
|
|
@@ -460,4 +505,4 @@ declare function composePlan(input: ComposePlanInput): Promise<ComposePlanResult
|
|
|
460
505
|
*/
|
|
461
506
|
declare function applyComposePlan(input: ApplyComposePlanInput): Promise<ApplyComposePlanResult>;
|
|
462
507
|
//#endregion
|
|
463
|
-
export { ApplyComposePlanInput, ApplyComposePlanResult, ComposeApplyPlan, type ComposeBranchPlan, type ComposeContext, ComposeDepth, ComposeDiffStats, ComposeInput, ComposePlan, ComposePlanInput, ComposePlanResult, ComposeProgressEvent, ComposeResult, type ComposeSource, ComposeStrategy, type ComposeTarget, type StashConflict, applyComposePlan, compose, composePlan, defaultPhaseMessages };
|
|
508
|
+
export { ApplyComposePlanInput, ApplyComposePlanResult, ComposeApplyPlan, type ComposeBranchPlan, type ComposeContext, ComposeDepth, ComposeDiffStats, ComposeInput, ComposePlan, ComposePlanInput, ComposePlanResult, ComposeProgressEvent, ComposeResult, type ComposeSource, ComposeStrategy, type ComposeTarget, type OverrideSafetyFailures, type StashConflict, applyComposePlan, compose, composePlan, defaultPhaseMessages };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{GitError as e}from"../errors/git.js";import{ComposeWorkflowError as t,ComposeWorkflowInputError as n}from"../errors/workflow.js";import{addUsage as r,emptyUsage as i}from"../ai/types.js";import{analyze as a}from"../tasks/analyze/analyze.js";import{composeGroup as o}from"../tasks/compose-group/compose-group.js";import{group as s}from"../tasks/group/group.js";import{order as c}from"../tasks/order/order.js";import{partition as l}from"../tasks/partition/partition.js";import{applyPatchToIndex as u,currentBranch as d,diffTree as f,forEachRef as p,log as m,mergeBase as h,readTree as g,revParse as _,writeTree as v}from"../git/port-dispatch.js";import{buildPatchForCommit as y}from"../git/utils/patch-builder.js";import{sha256 as b}from"../git/utils/crypto.js";import{collectFromSource as x}from"../git/diff-collector.js";import{verifySnapshot as S}from"../git/safety.js";import{applyToTarget as C}from"../git/apply-target.js";import{requireExec as w}from"../git/require-exec.js";import{validateRewriteRangeSafety as T}from"../git/safety-range.js";import{extractConventions as E}from"../git/utils/conventions.js";import{rethrowAsWorkflowError as D}from"../utils/cancellation.js";import{randomUUID as O}from"node:crypto";import{unlinkSync as k}from"node:fs";import{tmpdir as A}from"node:os";import{join as j}from"node:path";import{UndoStore as M,capturePreOperationState as N,generateUndoId as P}from"@gitkraken/shared-tools";const F={collecting:{message:`Collecting changes…`,weight:5},composing:{message:`Composing commits…`,weight:40},"compose-grouping":{message:`Grouping changes into commits…`,weight:40},analyzing:{message:`Analyzing changes…`,weight:20},grouping:{message:`Grouping changes into commits…`,weight:25},ordering:{message:`Ordering commits…`,weight:15},partitioning:{message:`Subdividing branches…`,weight:20},verifying:{message:`Verifying…`,weight:5},applying:{message:`Applying to repository…`,weight:10}};async function I(r){if(!r.applyPlan&&!r.model)throw new n(`Either 'model' or 'applyPlan' must be provided`);if(r.applyPlan)return ne(r);let{git:i,model:a,source:o={type:`workdir`},onProgress:s,onBeforeApply:c,cancellation:l}=r,u=a,d=r.depth??`balanced`,f=r.target??await Z(i,o);re(o,f),await q(i,r.applyCommitIds,r.hunkFilter,o,f),await T({git:i,source:o,target:f});let p=[];s?.({phase:`collecting`,...F.collecting});let m=Date.now(),h=await x(i,o);if(p.push({name:`collecting`,durationMs:Date.now()-m,attempts:1}),h.hunks.length===0)throw new e(`NO_CHANGES`,Q(o));let{hunks:g,filteredCount:_}=await W(h.hunks,r.hunkFilter);if(g.length===0)throw new e(`NO_CHANGES`,Q(o));let v=H(g);s?.({phase:`collecting`,...F.collecting,stats:v});let y=_!==void 0&&_>0?await G(i,h.snapshot,g):h.snapshot,b={hunks:g,collection:h,conventions:r.conventions??await E({git:i,author:r.conventionAuthor}),existingBranchNames:await ie(i),groupMode:f.type===`auto`?`branches`:`commits`,target:f,model:u,instructions:r.instructions,context:r.context,strategy:r.strategy,maxRetries:r.maxRetries,maxTokens:r.maxTokens,onProgress:s,cancellation:l,onBeforeModelCall:r.onBeforeModelCall,onAfterModelCall:r.onAfterModelCall,onBeforePrompt:r.onBeforePrompt,session:r.session,promptOverrides:r.promptOverrides,redactHunkContent:r.redactHunkContent},O;try{O=await ee(d,b)}catch(e){D(e,l)}let k={analysis:O.analysis,grouping:O.grouping,ordering:O.ordering,branches:O.branchPlans,allOrderedCommits:O.allOrderedCommits};if(p.push(...O.phases),c&&!await c(k))return{plan:k,snapshot:y,session:O.session,usage:O.usage,phases:p,source:{type:o.type===`branch`?`branch`:`workdir`,hunks:g,commitMessages:h.commitMessages,branchName:h.branchName,baseBranch:h.baseBranch},diffStats:v,feedbackTargetCallId:U(p),hunksFilteredCount:_};let A=await N(w(i,`capturing pre-operation state`));s?.({phase:`verifying`,...F.verifying});let j=Date.now(),M=await S(i,h.snapshot,K(o,h));if(!M.valid)throw new t(`SAFETY_CHECK_FAILED`,`Safety check failed: ${M.error}`);p.push({name:`verifying`,durationMs:Date.now()-j,attempts:1});let P=J(O.allOrderedCommits,r.applyCommitIds),{leftoverHunks:I,leftoverPatch:L}=X(P,h.hunks),R=I.length>0?await G(i,y,Y(P,h.hunks)):y;s?.({phase:`applying`,...F.applying});let z=Date.now(),B=await C({git:i,source:o,target:f,branchPlans:O.branchPlans,allOrderedCommits:P,hunks:h.hunks,snapshot:R,collection:h,signing:r.signing,authorAttribution:r.authorAttribution,stashLabel:r.stashLabel,leftoverPatch:L});p.push({name:`applying`,durationMs:Date.now()-z,attempts:1});let V=await ae(i,A,B.mutations,f,o);return{plan:k,snapshot:R,partitions:B.partitions,partitionGroups:B.partitionGroups,session:O.session,usage:O.usage,phases:p,source:{type:o.type===`branch`?`branch`:`workdir`,hunks:g,commitMessages:h.commitMessages,branchName:h.branchName,baseBranch:h.baseBranch},diffStats:v,commitShas:B.commitShas,undoId:V,feedbackTargetCallId:U(p),hunksFilteredCount:_,appliedCommitIds:P.map(e=>e.id),unappliedHunkIndices:I.length>0?I.map(e=>e.index):void 0,stashConflict:B.stashConflict}}function ee(e,t){switch(e){case`quick`:return L(t);case`balanced`:return R(t);case`deep`:return B(t)}}async function L(e){return z(e,{lightweight:!0,skipOrder:!0})}async function R(e){return z(e,{includeAnalysis:!0})}async function z(e,t){let{hunks:n,collection:a,conventions:s,existingBranchNames:u,groupMode:d,target:f,model:p,instructions:m,context:h,strategy:g,maxRetries:_,maxTokens:v,onProgress:y,cancellation:b,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C}=e,w=i(),T=[],E=await o({hunks:n,commitMessages:a.commitMessages,commitOrder:a.commitOrder,branchName:a.branchName,baseBranch:a.baseBranch,issueUrl:h?.issueUrl,pullRequestUrl:h?.pullRequestUrl,instructions:m,conventions:s,mode:d,strategy:g?.grouping,existingBranchNames:d===`branches`?u:void 0,includeAnalysis:t.includeAnalysis,lightweight:t.lightweight,condensed:t.condensed,redactHunkContent:e.redactHunkContent,model:p,session:e.session,maxRetries:_,maxTokens:v,onProgress:e=>y?.({phase:`compose-grouping`,...F[`compose-grouping`],event:e}),cancellation:b,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.[`compose-group`]});E.usage&&r(w,E.usage),T.push({name:`compose-grouping`,durationMs:E.durationMs,attempts:E.attempts,usage:E.usage,callIds:E.callIds});let D=E.session,O,k;if(t.skipOrder)O=E.result.branches.map(e=>({branchGroup:e,orderedCommitIds:e.commits.map(e=>e.id)})),k={branches:O.map(e=>({branchId:e.branchGroup.id,orderedCommitIds:e.orderedCommitIds})),rationale:``};else{let t=await c({branches:E.result.branches,hunks:n,instructions:m,model:p,session:E.session,maxRetries:_,maxTokens:v,onProgress:e=>y?.({phase:`ordering`,...F.ordering,event:e}),cancellation:b,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.order});t.usage&&r(w,t.usage),T.push({name:`ordering`,durationMs:t.durationMs,attempts:t.attempts,usage:t.usage,callIds:t.callIds}),D=t.session,k=t.result,O=E.result.branches.map(e=>({branchGroup:e,orderedCommitIds:k.branches.find(t=>t.branchId===e.id)?.orderedCommitIds??e.commits.map(e=>e.id)}))}if(f.type===`auto`){let t=new Set(u);for(let e of E.result.branches)e.name&&t.add(e.name);for(let n of O){let i=await l({commits:$(n.branchGroup,n.orderedCommitIds),orderedCommitIds:n.orderedCommitIds,sourceBranchName:n.branchGroup.name,instructions:m,conventions:s,strategy:g?.partition,existingBranchNames:t,model:p,maxRetries:_,maxTokens:v,onProgress:e=>y?.({phase:`partitioning`,...F.partitioning,branch:n.branchGroup.name,event:e}),cancellation:b,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.partition});i.usage&&r(w,i.usage),T.push({name:`partitioning:${n.branchGroup.name}`,durationMs:i.durationMs,attempts:i.attempts,usage:i.usage,callIds:i.callIds}),n.partitioning=i.result,D=i.session;for(let e of i.result.partitions)t.add(e.branchName)}}let A=O.flatMap(e=>$(e.branchGroup,e.orderedCommitIds));return{analysis:void 0,grouping:E.result,ordering:k,branchPlans:O,allOrderedCommits:A,session:D,usage:w,phases:T}}async function B(e){let{hunks:t,collection:n,conventions:o,existingBranchNames:u,groupMode:d,target:f,model:p,instructions:m,context:h,strategy:g,maxRetries:_,maxTokens:v,onProgress:y,cancellation:b,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C}=e,w=i(),T=[],E=await a({hunks:t,commitMessages:n.commitMessages,commitOrder:n.commitOrder,branchName:n.branchName,baseBranch:n.baseBranch,issueUrl:h?.issueUrl,pullRequestUrl:h?.pullRequestUrl,instructions:m,redactHunkContent:e.redactHunkContent,model:p,session:e.session,maxRetries:_,maxTokens:v,onProgress:e=>y?.({phase:`analyzing`,...F.analyzing,event:e}),cancellation:b,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.analyze});E.usage&&r(w,E.usage),T.push({name:`analyzing`,durationMs:E.durationMs,attempts:E.attempts,usage:E.usage,callIds:E.callIds});let D=await s({hunks:t,commitMessages:n.commitMessages,instructions:m,conventions:o,mode:d,strategy:g?.grouping,existingBranchNames:d===`branches`?u:void 0,redactHunkContent:e.redactHunkContent,model:p,session:E.session,maxRetries:_,maxTokens:v,onProgress:e=>y?.({phase:`grouping`,...F.grouping,event:e}),cancellation:b,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.group});D.usage&&r(w,D.usage),T.push({name:`grouping`,durationMs:D.durationMs,attempts:D.attempts,usage:D.usage,callIds:D.callIds});let O=await c({branches:D.result.branches,hunks:t,instructions:m,model:p,session:D.session,maxRetries:_,maxTokens:v,onProgress:e=>y?.({phase:`ordering`,...F.ordering,event:e}),cancellation:b,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.order});O.usage&&r(w,O.usage),T.push({name:`ordering`,durationMs:O.durationMs,attempts:O.attempts,usage:O.usage,callIds:O.callIds});let k=D.result.branches.map(e=>({branchGroup:e,orderedCommitIds:O.result.branches.find(t=>t.branchId===e.id)?.orderedCommitIds??e.commits.map(e=>e.id)})),A=O.session;if(f.type===`auto`){let t=new Set(u);for(let e of D.result.branches)e.name&&t.add(e.name);for(let n of k){let i=await l({commits:$(n.branchGroup,n.orderedCommitIds),orderedCommitIds:n.orderedCommitIds,sourceBranchName:n.branchGroup.name,instructions:m,conventions:o,strategy:g?.partition,existingBranchNames:t,model:p,maxRetries:_,maxTokens:v,onProgress:e=>y?.({phase:`partitioning`,...F.partitioning,branch:n.branchGroup.name,event:e}),cancellation:b,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.partition});i.usage&&r(w,i.usage),T.push({name:`partitioning:${n.branchGroup.name}`,durationMs:i.durationMs,attempts:i.attempts,usage:i.usage,callIds:i.callIds}),n.partitioning=i.result,A=i.session;for(let e of i.result.partitions)t.add(e.branchName)}}let j=k.flatMap(e=>$(e.branchGroup,e.orderedCommitIds));return{analysis:E.result,grouping:D.result,ordering:O.result,branchPlans:k,allOrderedCommits:j,session:A,usage:w,phases:T}}async function V(e){let t=await I({...e,onBeforeApply:async()=>!1});return{plan:t.plan,snapshot:t.snapshot,source:t.source,session:t.session,usage:t.usage,phases:t.phases,feedbackTargetCallId:t.feedbackTargetCallId,hunksFilteredCount:t.hunksFilteredCount,diffStats:t.diffStats}}async function te(e){let t=await I({git:e.git,applyPlan:e.applyPlan,source:e.source,target:e.target,onProgress:e.onProgress,signing:e.signing,authorAttribution:e.authorAttribution,applyCommitIds:e.applyCommitIds,stashLabel:e.stashLabel,hunkFilter:e.hunkFilter});return{commitShas:t.commitShas,partitions:t.partitions,partitionGroups:t.partitionGroups,phases:t.phases,undoId:t.undoId,appliedCommitIds:t.appliedCommitIds,unappliedHunkIndices:t.unappliedHunkIndices,stashConflict:t.stashConflict}}async function ne(n){let{git:r,applyPlan:a,onProgress:o}=n,{plan:s,snapshot:c,source:l}=a,u=n.source??l,d=n.target??a?.target??await Z(r,u);re(u,d),await q(r,n.applyCommitIds,n.hunkFilter,u,d),await T({git:r,source:u,target:d});let f=await N(w(r,`capturing pre-operation state`)),p=[];o?.({phase:`collecting`,...F.collecting});let m=Date.now(),h=await x(r,u);if(p.push({name:`collecting`,durationMs:Date.now()-m,attempts:1}),h.hunks.length===0)throw new e(`NO_CHANGES`,Q(u));let{hunks:g}=await W(h.hunks,n.hunkFilter);if(g.length===0)throw new e(`NO_CHANGES`,Q(u));let _=H(g);o?.({phase:`collecting`,...F.collecting,stats:_});let v=g.length<h.hunks.length?await G(r,h.snapshot,g):h.snapshot,y={...h,hunks:g,snapshot:v};if(y.snapshot.diffHash!==c.diffHash)throw new t(`SAFETY_CHECK_FAILED`,`Working directory has changed since the plan was created. Re-run compose to generate a new plan.`);o?.({phase:`verifying`,...F.verifying});let b=Date.now(),E=await S(r,y.snapshot,K(u,y));if(!E.valid)throw new t(`SAFETY_CHECK_FAILED`,`Safety check failed: ${E.error}`);p.push({name:`verifying`,durationMs:Date.now()-b,attempts:1});let D=J(s.allOrderedCommits,n.applyCommitIds),{leftoverHunks:O,leftoverPatch:k}=X(D,y.hunks),A=O.length>0?await G(r,y.snapshot,Y(D,y.hunks)):y.snapshot;o?.({phase:`applying`,...F.applying});let j=Date.now(),M=await C({git:r,source:u,target:d,branchPlans:s.branches,allOrderedCommits:D,hunks:y.hunks,snapshot:A,collection:y,signing:n.signing,authorAttribution:n.authorAttribution,stashLabel:n.stashLabel,leftoverPatch:k});p.push({name:`applying`,durationMs:Date.now()-j,attempts:1});let P=await ae(r,f,M.mutations,d,u);return{plan:s,snapshot:A,partitions:M.partitions,partitionGroups:M.partitionGroups,session:{messages:[],systemPrompt:``,completedSteps:{}},usage:i(),phases:p,source:{type:u.type===`branch`?`branch`:`workdir`,hunks:y.hunks,commitMessages:y.commitMessages,branchName:y.branchName,baseBranch:y.baseBranch},diffStats:_,commitShas:M.commitShas,undoId:P,appliedCommitIds:D.map(e=>e.id),unappliedHunkIndices:O.length>0?O.map(e=>e.index):void 0,stashConflict:M.stashConflict}}function H(e){let t=new Set,n=0,r=0;for(let i of e)t.add(i.fileName),n+=i.additions,r+=i.deletions;return{fileCount:t.size,hunkCount:e.length,addedLines:n,removedLines:r}}function U(e){for(let t=e.length-1;t>=0;t--){let n=e[t].callIds;if(n&&n.length>0)return n[n.length-1]}}async function W(e,t){if(!t)return{hunks:e,filteredCount:void 0};let n=await t(e);return{hunks:n,filteredCount:e.length-n.length}}async function G(e,t,n){let r=t.baseSha??t.headSha,i=j(A(),`compose-filtered-idx-${O()}`),a={GIT_INDEX_FILE:i};try{await g(e,r,{indexEnv:a});let i=y(n);i.length>0&&await u(e,i,{indexEnv:a});let o=await f(e,r,await v(e,{indexEnv:a}));return{...t,diffHash:b(o)}}finally{try{k(i)}catch{}}}function K(e,t){return e.type===`workdir`?{includeUnstaged:!e.stagedOnly,includeUntracked:t.untrackedIncluded}:e.type===`commit-range`&&e.includeWorkdir?{includeStaged:e.includeWorkdir.includeStaged??!0,includeUnstaged:e.includeWorkdir.includeUnstaged??!0,includeUntracked:e.includeWorkdir.includeUntracked??!0}:{includeUnstaged:!1,includeUntracked:!1}}async function q(e,t,r,i,a){if(r==null&&t==null||i.type===`workdir`||!(a.type===`rewrite-range`||(a.type===`branch`||a.type===`auto`)&&a.preserveOriginal!==!0))return;let o=i.type===`branch`?await _(e,i.name):await _(e,i.to),s=await _(e,`HEAD`);if(await d(e)==null)throw new n(`Filtering and partial apply require HEAD to be on a branch (non-detached) when rewriting existing commits. Check out the source branch before composing.`);if(s!==o)throw new n(`Filtering and partial apply require HEAD to be at the source's tip (${o.slice(0,8)}); HEAD is at ${s.slice(0,8)}.`)}function J(e,t){if(t==null)return e;let r=new Set(t),i=e.filter(e=>r.has(e.id)),a=new Set(i.map(e=>e.id)),o=t.filter(e=>!a.has(e));if(o.length>0)throw new n(`applyCommitIds contains commit ids not found in plan.allOrderedCommits: ${o.join(`, `)}`);return i}function Y(e,t){let n=new Set(e.flatMap(e=>e.hunkIndices));return t.filter(e=>n.has(e.index))}function X(e,t){let n=new Set(e.flatMap(e=>e.hunkIndices)),r=t.filter(e=>!n.has(e.index));if(r.length===0)return{leftoverHunks:[],leftoverPatch:void 0};let i=y(r);return{leftoverHunks:r,leftoverPatch:i.length>0?i:void 0}}async function Z(e,t){if(t.type===`branch`){let r=await _(e,t.name),i=(await m(e,{format:`%H`,range:`${await h(e,await _(e,t.mergeTarget??`main`),r)}..${r}`})).trim().split(`
|
|
2
|
-
`).at(-1);if(!i)throw new n(`Cannot recompose branch '${t.name}': no commits between merge-base and branch tip.`);return{type:`rewrite-range`,branch:t.name,rangeFrom:i,rangeTo:r}}return t.type===`commit-range`?{type:`rewrite-range`,branch:t.branch,rangeFrom:t.from,rangeTo:t.to}:{type:`head`}}function
|
|
3
|
-
`).filter(Boolean);return new Set(t)}async function
|
|
1
|
+
import{GitError as e}from"../errors/git.js";import{ComposeWorkflowError as t,ComposeWorkflowInputError as n}from"../errors/workflow.js";import{applyPatchToIndex as r,currentBranch as i,diffTree as a,forEachRef as o,log as s,mergeBase as c,readTree as l,revParse as u,writeTree as d}from"../git/port-dispatch.js";import{validateRewriteRangeSafety as f}from"../git/safety-range.js";import{addUsage as p,emptyUsage as m}from"../ai/types.js";import{memoizeRedactHunkContent as h}from"../tasks/utils/redact.js";import{analyze as g}from"../tasks/analyze/analyze.js";import{composeGroup as _}from"../tasks/compose-group/compose-group.js";import{group as v}from"../tasks/group/group.js";import{order as y}from"../tasks/order/order.js";import{partition as b}from"../tasks/partition/partition.js";import{withDefaultNoiseRedaction as x}from"../tasks/utils/noise.js";import{buildPatchForCommit as S}from"../git/utils/patch-builder.js";import{sha256 as C}from"../git/utils/crypto.js";import{collectFromSource as w}from"../git/diff-collector.js";import{verifySnapshot as T}from"../git/safety.js";import{applyToTarget as E}from"../git/apply-target.js";import{requireExec as D}from"../git/require-exec.js";import{extractConventions as O}from"../git/utils/conventions.js";import{rethrowAsWorkflowError as k}from"../utils/cancellation.js";import{randomUUID as A}from"node:crypto";import{unlinkSync as j}from"node:fs";import{tmpdir as M}from"node:os";import{join as N}from"node:path";import{UndoStore as P,capturePreOperationState as F,generateUndoId as I}from"@gitkraken/shared-tools";const L={collecting:{message:`Collecting changes…`,weight:5},composing:{message:`Composing commits…`,weight:40},"compose-grouping":{message:`Grouping changes into commits…`,weight:40},analyzing:{message:`Analyzing changes…`,weight:20},grouping:{message:`Grouping changes into commits…`,weight:25},ordering:{message:`Ordering commits…`,weight:15},partitioning:{message:`Subdividing branches…`,weight:20},verifying:{message:`Verifying…`,weight:5},applying:{message:`Applying to repository…`,weight:10}};async function R(r){if(!r.applyPlan&&!r.model)throw new n(`Either 'model' or 'applyPlan' must be provided`);if(r.applyPlan)return ne(r);let{git:i,model:a,source:o={type:`workdir`},onProgress:s,onBeforeApply:c,cancellation:l}=r,u=a,d=r.depth??`balanced`,p=r.target??await ae(i,o);oe(o,p),await Y(i,r.applyCommitIds,r.hunkFilter,o,p),await f({git:i,source:o,target:p,overrides:r.overrideSafetyFailures});let m=[];s?.({phase:`collecting`,...L.collecting});let g=Date.now(),_=await w(i,o);if(m.push({name:`collecting`,durationMs:Date.now()-g,attempts:1}),_.hunks.length===0)throw new e(`NO_CHANGES`,Z(o));let{hunks:v,filteredCount:y}=await K(_.hunks,r.hunkFilter);if(v.length===0)throw new e(`NO_CHANGES`,Z(o));let b=W(v);s?.({phase:`collecting`,...L.collecting,stats:b});let S=y!==void 0&&y>0?await q(i,_.snapshot,v):_.snapshot,C=r.conventions??await O({git:i,author:r.conventionAuthor}),A=await se(i),j=p.type===`auto`?`branches`:`commits`,M=r.disableDefaultNoiseReduction?r.redactHunkContent:x(r.redactHunkContent),N=M?h(M):void 0,P=N?v.filter(e=>N(e)).length:void 0,I={hunks:v,collection:_,conventions:C,existingBranchNames:A,groupMode:j,target:p,model:u,instructions:r.instructions,context:r.context,strategy:r.strategy,maxRetries:r.maxRetries,maxTokens:r.maxTokens,onProgress:s,cancellation:l,onBeforeModelCall:r.onBeforeModelCall,onAfterModelCall:r.onAfterModelCall,onBeforePrompt:r.onBeforePrompt,session:r.session,promptOverrides:r.promptOverrides,redactHunkContent:N,inputTokenBudget:r.inputTokenBudget},R;try{R=await ee(d,I)}catch(e){k(e,l)}let z={analysis:R.analysis,grouping:R.grouping,ordering:R.ordering,branches:R.branchPlans,allOrderedCommits:R.allOrderedCommits};if(m.push(...R.phases),c&&!await c(z))return{plan:z,snapshot:S,session:R.session,usage:R.usage,phases:m,source:{type:o.type===`branch`?`branch`:`workdir`,hunks:v,commitMessages:_.commitMessages,branchName:_.branchName,baseBranch:_.baseBranch},diffStats:b,feedbackTargetCallId:G(m),hunksFilteredCount:y,hunksRedactedCount:P,hunksCondensedCount:R.hunksCondensedCount};let te=await F(D(i,`capturing pre-operation state`));s?.({phase:`verifying`,...L.verifying});let B=Date.now(),V=await T(i,_.snapshot,J(o,_));if(!V.valid)throw new t(`SAFETY_CHECK_FAILED`,`Safety check failed: ${V.error}`);m.push({name:`verifying`,durationMs:Date.now()-B,attempts:1});let H=X(R.allOrderedCommits,r.applyCommitIds),{leftoverHunks:U,leftoverPatch:Q}=ie(H,_.hunks),le=U.length>0?await q(i,S,re(H,_.hunks)):S;s?.({phase:`applying`,...L.applying});let ue=Date.now(),$=await E({git:i,source:o,target:p,branchPlans:R.branchPlans,allOrderedCommits:H,hunks:_.hunks,snapshot:le,collection:_,signing:r.signing,authorAttribution:r.authorAttribution,stashLabel:r.stashLabel,leftoverPatch:Q});m.push({name:`applying`,durationMs:Date.now()-ue,attempts:1});let de=await ce(i,te,$.mutations,p,o);return{plan:z,snapshot:le,partitions:$.partitions,partitionGroups:$.partitionGroups,session:R.session,usage:R.usage,phases:m,source:{type:o.type===`branch`?`branch`:`workdir`,hunks:v,commitMessages:_.commitMessages,branchName:_.branchName,baseBranch:_.baseBranch},diffStats:b,commitShas:$.commitShas,undoId:de,feedbackTargetCallId:G(m),hunksFilteredCount:y,hunksRedactedCount:P,hunksCondensedCount:R.hunksCondensedCount,appliedCommitIds:H.map(e=>e.id),unappliedHunkIndices:U.length>0?U.map(e=>e.index):void 0,stashConflict:$.stashConflict}}function ee(e,t){switch(e){case`quick`:return z(t);case`balanced`:return te(t);case`deep`:return V(t)}}async function z(e){return B(e,{lightweight:!0,skipOrder:!0})}async function te(e){return B(e,{includeAnalysis:!0})}async function B(e,t){let{hunks:n,collection:r,conventions:i,existingBranchNames:a,groupMode:o,target:s,model:c,instructions:l,context:u,strategy:d,maxRetries:f,maxTokens:h,onProgress:g,cancellation:v,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C}=e,w=m(),T=[],E=await _({hunks:n,commitMessages:r.commitMessages,commitOrder:r.commitOrder,branchName:r.branchName,baseBranch:r.baseBranch,issueUrl:u?.issueUrl,pullRequestUrl:u?.pullRequestUrl,instructions:l,conventions:i,mode:o,strategy:d?.grouping,existingBranchNames:o===`branches`?a:void 0,includeAnalysis:t.includeAnalysis,lightweight:t.lightweight,condensed:t.condensed,redactHunkContent:e.redactHunkContent,inputTokenBudget:e.inputTokenBudget,model:c,session:e.session,maxRetries:f,maxTokens:h,onProgress:e=>g?.({phase:`compose-grouping`,...L[`compose-grouping`],event:e}),cancellation:v,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.[`compose-group`]});E.usage&&p(w,E.usage),T.push({name:`compose-grouping`,durationMs:E.durationMs,attempts:E.attempts,usage:E.usage,callIds:E.callIds});let D=E.session,O,k;if(t.skipOrder)O=E.result.branches.map(e=>({branchGroup:e,orderedCommitIds:e.commits.map(e=>e.id)})),k={branches:O.map(e=>({branchId:e.branchGroup.id,orderedCommitIds:e.orderedCommitIds})),rationale:``};else{let t=await y({branches:E.result.branches,hunks:n,instructions:l,model:c,session:E.session,maxRetries:f,maxTokens:h,onProgress:e=>g?.({phase:`ordering`,...L.ordering,event:e}),cancellation:v,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.order});t.usage&&p(w,t.usage),T.push({name:`ordering`,durationMs:t.durationMs,attempts:t.attempts,usage:t.usage,callIds:t.callIds}),D=t.session,k=t.result,O=E.result.branches.map(e=>({branchGroup:e,orderedCommitIds:k.branches.find(t=>t.branchId===e.id)?.orderedCommitIds??e.commits.map(e=>e.id)}))}if(s.type===`auto`){let t=new Set(a);for(let e of E.result.branches)e.name&&t.add(e.name);for(let n of O){let r=await b({commits:Q(n.branchGroup,n.orderedCommitIds),orderedCommitIds:n.orderedCommitIds,sourceBranchName:n.branchGroup.name,instructions:l,conventions:i,strategy:d?.partition,existingBranchNames:t,model:c,maxRetries:f,maxTokens:h,onProgress:e=>g?.({phase:`partitioning`,...L.partitioning,branch:n.branchGroup.name,event:e}),cancellation:v,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.partition});r.usage&&p(w,r.usage),T.push({name:`partitioning:${n.branchGroup.name}`,durationMs:r.durationMs,attempts:r.attempts,usage:r.usage,callIds:r.callIds}),n.partitioning=r.result,D=r.session;for(let e of r.result.partitions)t.add(e.branchName)}}let A=O.flatMap(e=>Q(e.branchGroup,e.orderedCommitIds));return{analysis:void 0,grouping:E.result,ordering:k,branchPlans:O,allOrderedCommits:A,session:D,usage:w,phases:T,hunksCondensedCount:E.hunksCondensedCount}}async function V(e){let{hunks:t,collection:n,conventions:r,existingBranchNames:i,groupMode:a,target:o,model:s,instructions:c,context:l,strategy:u,maxRetries:d,maxTokens:f,onProgress:h,cancellation:_,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C}=e,w=m(),T=[],E=await g({hunks:t,commitMessages:n.commitMessages,commitOrder:n.commitOrder,branchName:n.branchName,baseBranch:n.baseBranch,issueUrl:l?.issueUrl,pullRequestUrl:l?.pullRequestUrl,instructions:c,redactHunkContent:e.redactHunkContent,inputTokenBudget:e.inputTokenBudget,model:s,session:e.session,maxRetries:d,maxTokens:f,onProgress:e=>h?.({phase:`analyzing`,...L.analyzing,event:e}),cancellation:_,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.analyze});E.usage&&p(w,E.usage),T.push({name:`analyzing`,durationMs:E.durationMs,attempts:E.attempts,usage:E.usage,callIds:E.callIds});let D=await v({hunks:t,commitMessages:n.commitMessages,instructions:c,conventions:r,mode:a,strategy:u?.grouping,existingBranchNames:a===`branches`?i:void 0,redactHunkContent:e.redactHunkContent,inputTokenBudget:e.inputTokenBudget,model:s,session:E.session,maxRetries:d,maxTokens:f,onProgress:e=>h?.({phase:`grouping`,...L.grouping,event:e}),cancellation:_,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.group});D.usage&&p(w,D.usage),T.push({name:`grouping`,durationMs:D.durationMs,attempts:D.attempts,usage:D.usage,callIds:D.callIds});let O=await y({branches:D.result.branches,hunks:t,instructions:c,model:s,session:D.session,maxRetries:d,maxTokens:f,onProgress:e=>h?.({phase:`ordering`,...L.ordering,event:e}),cancellation:_,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.order});O.usage&&p(w,O.usage),T.push({name:`ordering`,durationMs:O.durationMs,attempts:O.attempts,usage:O.usage,callIds:O.callIds});let k=D.result.branches.map(e=>({branchGroup:e,orderedCommitIds:O.result.branches.find(t=>t.branchId===e.id)?.orderedCommitIds??e.commits.map(e=>e.id)})),A=O.session;if(o.type===`auto`){let t=new Set(i);for(let e of D.result.branches)e.name&&t.add(e.name);for(let n of k){let i=await b({commits:Q(n.branchGroup,n.orderedCommitIds),orderedCommitIds:n.orderedCommitIds,sourceBranchName:n.branchGroup.name,instructions:c,conventions:r,strategy:u?.partition,existingBranchNames:t,model:s,maxRetries:d,maxTokens:f,onProgress:e=>h?.({phase:`partitioning`,...L.partitioning,branch:n.branchGroup.name,event:e}),cancellation:_,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C,promptOverride:e.promptOverrides?.partition});i.usage&&p(w,i.usage),T.push({name:`partitioning:${n.branchGroup.name}`,durationMs:i.durationMs,attempts:i.attempts,usage:i.usage,callIds:i.callIds}),n.partitioning=i.result,A=i.session;for(let e of i.result.partitions)t.add(e.branchName)}}let j=k.flatMap(e=>Q(e.branchGroup,e.orderedCommitIds));return{analysis:E.result,grouping:D.result,ordering:O.result,branchPlans:k,allOrderedCommits:j,session:A,usage:w,phases:T,hunksCondensedCount:E.hunksCondensedCount}}async function H(e){let t=await R({...e,onBeforeApply:async()=>!1});return{plan:t.plan,snapshot:t.snapshot,source:t.source,session:t.session,usage:t.usage,phases:t.phases,feedbackTargetCallId:t.feedbackTargetCallId,hunksFilteredCount:t.hunksFilteredCount,hunksRedactedCount:t.hunksRedactedCount,hunksCondensedCount:t.hunksCondensedCount,diffStats:t.diffStats}}async function U(e){let t=await R({git:e.git,applyPlan:e.applyPlan,source:e.source,target:e.target,onProgress:e.onProgress,signing:e.signing,authorAttribution:e.authorAttribution,applyCommitIds:e.applyCommitIds,stashLabel:e.stashLabel,hunkFilter:e.hunkFilter,overrideSafetyFailures:e.overrideSafetyFailures});return{commitShas:t.commitShas,partitions:t.partitions,partitionGroups:t.partitionGroups,phases:t.phases,undoId:t.undoId,appliedCommitIds:t.appliedCommitIds,unappliedHunkIndices:t.unappliedHunkIndices,stashConflict:t.stashConflict}}async function ne(n){let{git:r,applyPlan:i,onProgress:a}=n,{plan:o,snapshot:s,source:c}=i,l=n.source??c,u=n.target??i?.target??await ae(r,l);oe(l,u),await Y(r,n.applyCommitIds,n.hunkFilter,l,u),await f({git:r,source:l,target:u,overrides:n.overrideSafetyFailures});let d=await F(D(r,`capturing pre-operation state`)),p=[];a?.({phase:`collecting`,...L.collecting});let h=Date.now(),g=await w(r,l);if(p.push({name:`collecting`,durationMs:Date.now()-h,attempts:1}),g.hunks.length===0)throw new e(`NO_CHANGES`,Z(l));let{hunks:_}=await K(g.hunks,n.hunkFilter);if(_.length===0)throw new e(`NO_CHANGES`,Z(l));let v=W(_);a?.({phase:`collecting`,...L.collecting,stats:v});let y=_.length<g.hunks.length?await q(r,g.snapshot,_):g.snapshot,b={...g,hunks:_,snapshot:y};if(b.snapshot.diffHash!==s.diffHash)throw new t(`SAFETY_CHECK_FAILED`,`Working directory has changed since the plan was created. Re-run compose to generate a new plan.`);a?.({phase:`verifying`,...L.verifying});let x=Date.now(),S=await T(r,b.snapshot,J(l,b));if(!S.valid)throw new t(`SAFETY_CHECK_FAILED`,`Safety check failed: ${S.error}`);p.push({name:`verifying`,durationMs:Date.now()-x,attempts:1});let C=X(o.allOrderedCommits,n.applyCommitIds),{leftoverHunks:O,leftoverPatch:k}=ie(C,b.hunks),A=O.length>0?await q(r,b.snapshot,re(C,b.hunks)):b.snapshot;a?.({phase:`applying`,...L.applying});let j=Date.now(),M=await E({git:r,source:l,target:u,branchPlans:o.branches,allOrderedCommits:C,hunks:b.hunks,snapshot:A,collection:b,signing:n.signing,authorAttribution:n.authorAttribution,stashLabel:n.stashLabel,leftoverPatch:k});p.push({name:`applying`,durationMs:Date.now()-j,attempts:1});let N=await ce(r,d,M.mutations,u,l);return{plan:o,snapshot:A,partitions:M.partitions,partitionGroups:M.partitionGroups,session:{messages:[],systemPrompt:``,completedSteps:{}},usage:m(),phases:p,source:{type:l.type===`branch`?`branch`:`workdir`,hunks:b.hunks,commitMessages:b.commitMessages,branchName:b.branchName,baseBranch:b.baseBranch},diffStats:v,commitShas:M.commitShas,undoId:N,appliedCommitIds:C.map(e=>e.id),unappliedHunkIndices:O.length>0?O.map(e=>e.index):void 0,stashConflict:M.stashConflict}}function W(e){let t=new Set,n=0,r=0;for(let i of e)t.add(i.fileName),n+=i.additions,r+=i.deletions;return{fileCount:t.size,hunkCount:e.length,addedLines:n,removedLines:r}}function G(e){for(let t=e.length-1;t>=0;t--){let n=e[t].callIds;if(n&&n.length>0)return n[n.length-1]}}async function K(e,t){if(!t)return{hunks:e,filteredCount:void 0};let n=await t(e);return{hunks:n,filteredCount:e.length-n.length}}async function q(e,t,n){let i=t.baseSha??t.headSha,o=N(M(),`compose-filtered-idx-${A()}`),s={GIT_INDEX_FILE:o};try{await l(e,i,{indexEnv:s});let o=S(n);o.length>0&&await r(e,o,{indexEnv:s});let c=await a(e,i,await d(e,{indexEnv:s}));return{...t,diffHash:C(c)}}finally{try{j(o)}catch{}}}function J(e,t){return e.type===`workdir`?{includeUnstaged:!e.stagedOnly,includeUntracked:t.untrackedIncluded}:e.type===`commit-range`&&e.includeWorkdir?{includeStaged:e.includeWorkdir.includeStaged??!0,includeUnstaged:e.includeWorkdir.includeUnstaged??!0,includeUntracked:e.includeWorkdir.includeUntracked??!0}:{includeUnstaged:!1,includeUntracked:!1}}async function Y(e,t,r,a,o){if(r==null&&t==null||a.type===`workdir`||!(o.type===`rewrite-range`||(o.type===`branch`||o.type===`auto`)&&o.preserveOriginal!==!0))return;let s=a.type===`branch`?await u(e,a.name):await u(e,a.to),c=await u(e,`HEAD`);if(await i(e)==null)throw new n(`Filtering and partial apply require HEAD to be on a branch (non-detached) when rewriting existing commits. Check out the source branch before composing.`);if(c!==s)throw new n(`Filtering and partial apply require HEAD to be at the source's tip (${s.slice(0,8)}); HEAD is at ${c.slice(0,8)}.`)}function X(e,t){if(t==null)return e;let r=new Set(t),i=e.filter(e=>r.has(e.id)),a=new Set(i.map(e=>e.id)),o=t.filter(e=>!a.has(e));if(o.length>0)throw new n(`applyCommitIds contains commit ids not found in plan.allOrderedCommits: ${o.join(`, `)}`);return i}function re(e,t){let n=new Set(e.flatMap(e=>e.hunkIndices));return t.filter(e=>n.has(e.index))}function ie(e,t){let n=new Set(e.flatMap(e=>e.hunkIndices)),r=t.filter(e=>!n.has(e.index));if(r.length===0)return{leftoverHunks:[],leftoverPatch:void 0};let i=S(r);return{leftoverHunks:r,leftoverPatch:i.length>0?i:void 0}}async function ae(e,t){if(t.type===`branch`){let r=await u(e,t.name),i=(await s(e,{format:`%H`,range:`${await c(e,await u(e,t.mergeTarget??`main`),r)}..${r}`})).trim().split(`
|
|
2
|
+
`).at(-1);if(!i)throw new n(`Cannot recompose branch '${t.name}': no commits between merge-base and branch tip.`);return{type:`rewrite-range`,branch:t.name,rangeFrom:i,rangeTo:r}}return t.type===`commit-range`?{type:`rewrite-range`,branch:t.branch,rangeFrom:t.from,rangeTo:t.to}:{type:`head`}}function Z(e){switch(e.type){case`workdir`:return`No changes found in working directory`;case`branch`:return`No changes found on branch '${e.name}'`;case`commit-range`:return`No changes found in range '${e.from}..${e.to}' on branch '${e.branch}'`}}function oe(e,t){if(t.type===`rewrite-range`&&e.type===`workdir`)throw new n(`target 'rewrite-range' is not valid with 'workdir' source`);if(t.type===`head`&&e.type!==`workdir`)throw new n(`target 'head' is not valid with '${e.type}' source — use 'branch', 'auto', or 'rewrite-range'`)}function Q(e,n){return n.map(n=>{let r=e.commits.find(e=>e.id===n);if(!r)throw new t(`INTERNAL`,`Commit ${n} not found in branch '${e.id}'`);return r})}async function se(e){let t=(await o(e,{format:`%(refname:short)`,prefix:`refs/heads/`})).trim().split(`
|
|
3
|
+
`).filter(Boolean);return new Set(t)}async function ce(e,t,n,r,i){let a=I(),o=n.composedBranchCount||1,s=`compose: ${o} branch${o===1?``:`es`} from ${i.type}`,c=[...n.movedRefs];if(r.type===`head`&&t.headBranch){let n=await u(e,`HEAD`);n!==t.headSha&&c.push({kind:`branch`,name:t.headBranch,previousSha:t.headSha,newSha:n})}let l={version:1,id:a,createdAt:new Date().toISOString(),description:s,previousHead:{sha:t.headSha,branch:t.headBranch},workdirClean:t.workdirClean,workdirBackupSha:t.workdirBackupSha,createdRefs:n.createdRefs,deletedRefs:n.deletedRefs,movedRefs:c};return await new P(D(e,`undo manifest persistence`)).save(l),a}export{U as applyComposePlan,R as compose,H as composePlan,L as defaultPhaseMessages};
|
|
@@ -84,5 +84,13 @@ interface RefinePlanResult {
|
|
|
84
84
|
* compose for apply-time validation.
|
|
85
85
|
*/
|
|
86
86
|
declare function refinePlan(input: RefinePlanInput): Promise<RefinePlanResult>;
|
|
87
|
+
/**
|
|
88
|
+
* Partition the prior plan's locked commits into the maximal runs of *consecutive* locked
|
|
89
|
+
* commits (no commit between them) within each branch, in the plan's original order. Each run
|
|
90
|
+
* — singletons included — is one entry; the flattened result is the locked commits' original
|
|
91
|
+
* relative order. Feeds the refine task's post-output cleanup, which restores ordering the AI
|
|
92
|
+
* reshuffled. See {@link import('../tasks/refine/types.js').RefineInput.lockedRuns}.
|
|
93
|
+
*/
|
|
94
|
+
declare function computeLockedRuns(plan: ComposePlan, lockedCommitIds: readonly string[]): string[][];
|
|
87
95
|
//#endregion
|
|
88
|
-
export { RefinePlanInput, RefinePlanResult, RefineProgressEvent, refinePlan };
|
|
96
|
+
export { RefinePlanInput, RefinePlanResult, RefineProgressEvent, computeLockedRuns, refinePlan };
|
package/dist/workflows/refine.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ComposeWorkflowInputError as e}from"../errors/workflow.js";import{emptyUsage as t}from"../ai/types.js";import{rethrowAsWorkflowError as n}from"../utils/cancellation.js";import{refine as r}from"../tasks/refine/refine.js";async function i(i){if(i.session.messages.length===0)throw new e(`refinePlan: session must contain prior messages from a previous compose call.`);if(i.priorPlan.allOrderedCommits.length===0)throw new e(`refinePlan: priorPlan must contain at least one commit.`);let
|
|
1
|
+
import{ComposeWorkflowInputError as e}from"../errors/workflow.js";import{emptyUsage as t}from"../ai/types.js";import{rethrowAsWorkflowError as n}from"../utils/cancellation.js";import{refine as r}from"../tasks/refine/refine.js";async function i(i){if(i.session.messages.length===0)throw new e(`refinePlan: session must contain prior messages from a previous compose call.`);if(i.priorPlan.allOrderedCommits.length===0)throw new e(`refinePlan: priorPlan must contain at least one commit.`);let c=[];if(i.lockedCommits?.length)for(let t of i.lockedCommits){let n=i.priorPlan.allOrderedCommits.find(e=>e.id===t);if(n==null)throw new e(`refinePlan: lockedCommits id '${t}' not found in priorPlan.allOrderedCommits.`);c.push(n)}let l=o(i.priorPlan),u=s(i.priorPlan),d=i.lockedCommits?.length?a(i.priorPlan,i.lockedCommits):void 0,f=Date.now(),p;try{p=await r({session:i.session,lockedCommits:c,lockedRuns:d,instructions:i.instructions,clientContext:i.clientContext,hunkCount:l,mode:u,model:i.model,maxRetries:i.maxRetries,maxTokens:i.maxTokens,cancellation:i.cancellation,onBeforeModelCall:i.onBeforeModelCall,onAfterModelCall:i.onAfterModelCall,onBeforePrompt:i.onBeforePrompt,promptOverride:i.promptOverride,onProgress:i.onProgress?e=>i.onProgress?.({phase:`refining`,message:`Refining commits…`,weight:100,event:e}):void 0})}catch(e){n(e,i.cancellation)}let m=p.result,h=m.branches.map(e=>({branchGroup:e,orderedCommitIds:e.commits.map(e=>e.id)})),g={branches:h.map(e=>({branchId:e.branchGroup.id,orderedCommitIds:e.orderedCommitIds})),rationale:``},_=h.flatMap(e=>e.branchGroup.commits);return{plan:{analysis:i.session.completedSteps.analyze,grouping:m,ordering:g,branches:h,allOrderedCommits:_},session:p.session,usage:p.usage??t(),phases:[{name:`refine`,durationMs:Date.now()-f,attempts:p.attempts,usage:p.usage,callIds:p.callIds}],feedbackTargetCallId:p.callIds.length>0?p.callIds[p.callIds.length-1]:void 0}}function a(e,t){let n=new Set(t);if(n.size===0)return[];let r=[];for(let t of e.branches){let e=[];for(let i of t.branchGroup.commits)n.has(i.id)?e.push(i.id):e.length>0&&(r.push(e),e=[]);e.length>0&&r.push(e)}return r}function o(e){let t=new Set;for(let n of e.allOrderedCommits)for(let e of n.hunkIndices)t.add(e);return t.size}function s(e){return e.branches.length===1&&e.branches[0].branchGroup.id===`default`&&e.branches[0].branchGroup.name===``?`commits`:`branches`}export{a as computeLockedRuns,i as refinePlan};
|
package/dist/workflows/split.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{GitError as e}from"../errors/git.js";import{
|
|
2
|
-
`)),P=j.map(e=>({id:e.sha,message:e.message,explanation:``,hunkIndices:[]})),F;try{F=await
|
|
3
|
-
`).filter(Boolean);return new Set(
|
|
1
|
+
import{GitError as e}from"../errors/git.js";import{forEachRef as t}from"../git/port-dispatch.js";import{addUsage as n,emptyUsage as r}from"../ai/types.js";import{analyze as i}from"../tasks/analyze/analyze.js";import{partition as a}from"../tasks/partition/partition.js";import{createStackedBranches as o}from"../git/apply-branches.js";import{parseUnifiedDiff as s}from"../git/utils/hunk-parser.js";import{collectCommitsForSplit as c}from"../git/diff-collector.js";import{requireExec as l}from"../git/require-exec.js";import{rethrowAsWorkflowError as u}from"../utils/cancellation.js";import{defaultPhaseMessages as d}from"./compose.js";import{UndoStore as f,capturePreOperationState as p,generateUndoId as m}from"@gitkraken/shared-tools";async function h(t){let{git:f,model:m,branchName:h,baseBranch:v=`main`,instructions:y,maxRetries:b,maxTokens:x,onProgress:S,onBeforeApply:C,cancellation:w,onBeforeModelCall:T,onAfterModelCall:E,onBeforePrompt:D}=t,O=r(),k=[];S?.({phase:`collecting`,...d.collecting});let A=Date.now(),{commits:j,branchSha:M}=await c(f,h,v);if(k.push({name:`collecting`,durationMs:Date.now()-A,attempts:1}),j.length===0)throw new e(`NO_CHANGES`,`No commits found between '${v}' and '${h}'`);if(j.length<2)throw new e(`NO_CHANGES`,`Too few commits to split. A branch needs at least 2 commits.`);let N=s(j.map(e=>e.diff).join(`
|
|
2
|
+
`)),P=j.map(e=>({id:e.sha,message:e.message,explanation:``,hunkIndices:[]})),F;try{F=await i({hunks:N,commitMessages:j.map(e=>e.message),commitOrder:j.map(e=>e.sha),branchName:h,baseBranch:v,instructions:y,redactHunkContent:t.redactHunkContent,model:m,maxRetries:b,maxTokens:x,onProgress:e=>S?.({phase:`analyzing`,...d.analyzing,event:e}),cancellation:w,onBeforeModelCall:T,onAfterModelCall:E,onBeforePrompt:D})}catch(e){u(e,w)}F.usage&&n(O,F.usage),k.push({name:`analyzing`,durationMs:F.durationMs,attempts:F.attempts,usage:F.usage,callIds:F.callIds});let I=j.map(e=>e.sha),L=await g(f),R;try{R=await a({commits:P,orderedCommitIds:I,sourceBranchName:h,baseBranch:v,instructions:y,existingBranchNames:L,model:m,session:F.session,maxRetries:b,maxTokens:x,onProgress:e=>S?.({phase:`partitioning`,...d.partitioning,event:e}),cancellation:w,onBeforeModelCall:T,onAfterModelCall:E,onBeforePrompt:D})}catch(e){u(e,w)}R.usage&&n(O,R.usage),k.push({name:`partitioning`,durationMs:R.durationMs,attempts:R.attempts,usage:R.usage,callIds:R.callIds});let z={type:`branch`,hunks:N,commitMessages:j.map(e=>e.message),branchName:h,baseBranch:v},B={analysis:F.result,partitioning:R.result,commits:j};if(R.result.partitions.length<=1||C&&!await C(B))return{plan:B,partitions:R.result.partitions,session:R.session,usage:O,phases:k,source:z};let V=await p(l(f,`capturing pre-operation state`));S?.({phase:`applying`,...d.applying});let H=Date.now();await o({git:f,partitions:R.result.partitions,originalBranchName:h,originalBranchSha:M}),k.push({name:`applying`,durationMs:Date.now()-H,attempts:1});let U=await _(f,V,R.result.partitions,h,M);return{plan:B,partitions:R.result.partitions,session:R.session,usage:O,phases:k,source:z,undoId:U}}async function g(e){let n=(await t(e,{format:`%(refname:short)`,prefix:`refs/heads/`})).trim().split(`
|
|
3
|
+
`).filter(Boolean);return new Set(n)}async function _(e,t,n,r,i){let a=m(),o=n.map(e=>({kind:`branch`,name:e.branchName,sha:e.commitIds[e.commitIds.length-1]})),s={version:1,id:a,createdAt:new Date().toISOString(),description:`split: ${n.length} branches from '${r}'`,previousHead:{sha:t.headSha,branch:t.headBranch},workdirClean:t.workdirClean,workdirBackupSha:t.workdirBackupSha,createdRefs:o,deletedRefs:[{kind:`branch`,name:r,sha:i}],movedRefs:[]};return await new f(l(e,`undo manifest persistence`)).save(s),a}export{h as split};
|
|
@@ -87,5 +87,18 @@ interface ComposeContext {
|
|
|
87
87
|
issueUrl?: string;
|
|
88
88
|
pullRequestUrl?: string;
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Opt-out switches for individual rewrite-range safety checks. Each flag, when `true`, suppresses
|
|
92
|
+
* one specific pre-apply validation; omitting the object (or a flag) keeps that check active.
|
|
93
|
+
*/
|
|
94
|
+
interface OverrideSafetyFailures {
|
|
95
|
+
/**
|
|
96
|
+
* Skip the interior-ref check. When `true`, a rewrite proceeds even if branches or tags point
|
|
97
|
+
* at commits inside the rewritten range. Those refs are left untouched: they stay on the
|
|
98
|
+
* pre-rewrite commits (which remain reachable through them) rather than following the rewrite,
|
|
99
|
+
* so the caller is accepting that they diverge from the recomposed history.
|
|
100
|
+
*/
|
|
101
|
+
interiorRefs?: boolean;
|
|
102
|
+
}
|
|
90
103
|
//#endregion
|
|
91
|
-
export { ComposeContext, ComposeSource, ComposeTarget };
|
|
104
|
+
export { ComposeContext, ComposeSource, ComposeTarget, OverrideSafetyFailures };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitkraken/compose-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "AI-powered organization of git changes into clean, atomic commits and branches.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Axosoft, LLC dba GitKraken",
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"CHANGELOG.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"zod": "^4.3.6",
|
|
24
23
|
"@gitkraken/shared-tools": "^0.2.0"
|
|
25
24
|
},
|
|
26
25
|
"devDependencies": {
|
|
27
26
|
"@types/node": "^22.0.0",
|
|
27
|
+
"json-schema-to-ts": "^3.1.1",
|
|
28
28
|
"typescript": "^6.0.2",
|
|
29
29
|
"vitest": "^4.1.1"
|
|
30
30
|
},
|