@gitkraken/compose-tools 0.5.0 → 0.7.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 +22 -0
- package/dist/errors/task.d.ts +11 -1
- package/dist/errors/task.js +1 -1
- package/dist/git/diff-collector.js +5 -5
- package/dist/git/utils/conventions.d.ts +7 -0
- package/dist/git/utils/conventions.js +8 -9
- package/dist/index.d.ts +5 -2
- 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/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 +36 -0
- package/dist/workflows/compose.js +3 -3
- package/dist/workflows/split.js +2 -2
- package/package.json +2 -2
|
@@ -43,6 +43,10 @@ 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}. */
|
|
@@ -73,6 +77,10 @@ interface ComposePlanResult {
|
|
|
73
77
|
feedbackTargetCallId?: string;
|
|
74
78
|
/** See {@link ComposeResult.hunksFilteredCount}. */
|
|
75
79
|
hunksFilteredCount?: number;
|
|
80
|
+
/** See {@link ComposeResult.hunksRedactedCount}. */
|
|
81
|
+
hunksRedactedCount?: number;
|
|
82
|
+
/** See {@link ComposeResult.hunksCondensedCount}. */
|
|
83
|
+
hunksCondensedCount?: number;
|
|
76
84
|
/** See {@link ComposeResult.diffStats}. */
|
|
77
85
|
diffStats?: ComposeDiffStats;
|
|
78
86
|
}
|
|
@@ -217,8 +225,23 @@ interface ComposeInput {
|
|
|
217
225
|
*
|
|
218
226
|
* Intended for `.aiexclude`/`.aiignore`/`.cursorignore`-style features: the AI assigns
|
|
219
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.
|
|
220
231
|
*/
|
|
221
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;
|
|
222
245
|
/**
|
|
223
246
|
* Per-task prompt overrides, keyed by {@link ComposeTaskName}. Consumers with
|
|
224
247
|
* existing prompt template services (server-driven templates, provider-specific
|
|
@@ -415,6 +438,19 @@ interface ComposeResult {
|
|
|
415
438
|
* Undefined when no filter ran.
|
|
416
439
|
*/
|
|
417
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;
|
|
418
454
|
/** See {@link ApplyComposePlanResult.appliedCommitIds}. */
|
|
419
455
|
appliedCommitIds?: string[];
|
|
420
456
|
/** See {@link ApplyComposePlanResult.unappliedHunkIndices}. */
|
|
@@ -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{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{analyze as h}from"../tasks/analyze/analyze.js";import{composeGroup as g}from"../tasks/compose-group/compose-group.js";import{group as _}from"../tasks/group/group.js";import{order as v}from"../tasks/order/order.js";import{partition as y}from"../tasks/partition/partition.js";import{buildPatchForCommit as b}from"../git/utils/patch-builder.js";import{sha256 as x}from"../git/utils/crypto.js";import{collectFromSource as S}from"../git/diff-collector.js";import{verifySnapshot as C}from"../git/safety.js";import{applyToTarget as w}from"../git/apply-target.js";import{requireExec as T}from"../git/require-exec.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`,p=r.target??await Z(i,o);re(o,p),await q(i,r.applyCommitIds,r.hunkFilter,o,p),await f({git:i,source:o,target:p,overrides:r.overrideSafetyFailures});let m=[];s?.({phase:`collecting`,...F.collecting});let h=Date.now(),g=await S(i,o);if(m.push({name:`collecting`,durationMs:Date.now()-h,attempts:1}),g.hunks.length===0)throw new e(`NO_CHANGES`,Q(o));let{hunks:_,filteredCount:v}=await W(g.hunks,r.hunkFilter);if(_.length===0)throw new e(`NO_CHANGES`,Q(o));let y=H(_);s?.({phase:`collecting`,...F.collecting,stats:y});let b=v!==void 0&&v>0?await G(i,g.snapshot,_):g.snapshot,x={hunks:_,collection:g,conventions:r.conventions??await E({git:i,author:r.conventionAuthor}),existingBranchNames:await ie(i),groupMode:p.type===`auto`?`branches`:`commits`,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:r.redactHunkContent},O;try{O=await ee(d,x)}catch(e){D(e,l)}let k={analysis:O.analysis,grouping:O.grouping,ordering:O.ordering,branches:O.branchPlans,allOrderedCommits:O.allOrderedCommits};if(m.push(...O.phases),c&&!await c(k))return{plan:k,snapshot:b,session:O.session,usage:O.usage,phases:m,source:{type:o.type===`branch`?`branch`:`workdir`,hunks:_,commitMessages:g.commitMessages,branchName:g.branchName,baseBranch:g.baseBranch},diffStats:y,feedbackTargetCallId:U(m),hunksFilteredCount:v};let A=await N(T(i,`capturing pre-operation state`));s?.({phase:`verifying`,...F.verifying});let j=Date.now(),M=await C(i,g.snapshot,K(o,g));if(!M.valid)throw new t(`SAFETY_CHECK_FAILED`,`Safety check failed: ${M.error}`);m.push({name:`verifying`,durationMs:Date.now()-j,attempts:1});let P=J(O.allOrderedCommits,r.applyCommitIds),{leftoverHunks:I,leftoverPatch:L}=X(P,g.hunks),R=I.length>0?await G(i,b,Y(P,g.hunks)):b;s?.({phase:`applying`,...F.applying});let z=Date.now(),B=await w({git:i,source:o,target:p,branchPlans:O.branchPlans,allOrderedCommits:P,hunks:g.hunks,snapshot:R,collection:g,signing:r.signing,authorAttribution:r.authorAttribution,stashLabel:r.stashLabel,leftoverPatch:L});m.push({name:`applying`,durationMs:Date.now()-z,attempts:1});let V=await ae(i,A,B.mutations,p,o);return{plan:k,snapshot:R,partitions:B.partitions,partitionGroups:B.partitionGroups,session:O.session,usage:O.usage,phases:m,source:{type:o.type===`branch`?`branch`:`workdir`,hunks:_,commitMessages:g.commitMessages,branchName:g.branchName,baseBranch:g.baseBranch},diffStats:y,commitShas:B.commitShas,undoId:V,feedbackTargetCallId:U(m),hunksFilteredCount:v,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:r,conventions:i,existingBranchNames:a,groupMode:o,target:s,model:c,instructions:l,context:u,strategy:d,maxRetries:f,maxTokens:h,onProgress:_,cancellation:b,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C}=e,w=m(),T=[],E=await g({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,model:c,session:e.session,maxRetries:f,maxTokens:h,onProgress:e=>_?.({phase:`compose-grouping`,...F[`compose-grouping`],event:e}),cancellation:b,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 v({branches:E.result.branches,hunks:n,instructions:l,model:c,session:E.session,maxRetries:f,maxTokens:h,onProgress:e=>_?.({phase:`ordering`,...F.ordering,event:e}),cancellation:b,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 y({commits:$(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=>_?.({phase:`partitioning`,...F.partitioning,branch:n.branchGroup.name,event:e}),cancellation:b,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=>$(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:r,existingBranchNames:i,groupMode:a,target:o,model:s,instructions:c,context:l,strategy:u,maxRetries:d,maxTokens:f,onProgress:g,cancellation:b,onBeforeModelCall:x,onAfterModelCall:S,onBeforePrompt:C}=e,w=m(),T=[],E=await h({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,model:s,session:e.session,maxRetries:d,maxTokens:f,onProgress:e=>g?.({phase:`analyzing`,...F.analyzing,event:e}),cancellation:b,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 _({hunks:t,commitMessages:n.commitMessages,instructions:c,conventions:r,mode:a,strategy:u?.grouping,existingBranchNames:a===`branches`?i:void 0,redactHunkContent:e.redactHunkContent,model:s,session:E.session,maxRetries:d,maxTokens:f,onProgress:e=>g?.({phase:`grouping`,...F.grouping,event:e}),cancellation:b,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 v({branches:D.result.branches,hunks:t,instructions:c,model:s,session:D.session,maxRetries:d,maxTokens:f,onProgress:e=>g?.({phase:`ordering`,...F.ordering,event:e}),cancellation:b,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 y({commits:$(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=>g?.({phase:`partitioning`,...F.partitioning,branch:n.branchGroup.name,event:e}),cancellation:b,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=>$(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,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 Z(r,l);re(l,u),await q(r,n.applyCommitIds,n.hunkFilter,l,u),await f({git:r,source:l,target:u,overrides:n.overrideSafetyFailures});let d=await N(T(r,`capturing pre-operation state`)),p=[];a?.({phase:`collecting`,...F.collecting});let h=Date.now(),g=await S(r,l);if(p.push({name:`collecting`,durationMs:Date.now()-h,attempts:1}),g.hunks.length===0)throw new e(`NO_CHANGES`,Q(l));let{hunks:_}=await W(g.hunks,n.hunkFilter);if(_.length===0)throw new e(`NO_CHANGES`,Q(l));let v=H(_);a?.({phase:`collecting`,...F.collecting,stats:v});let y=_.length<g.hunks.length?await G(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`,...F.verifying});let x=Date.now(),E=await C(r,b.snapshot,K(l,b));if(!E.valid)throw new t(`SAFETY_CHECK_FAILED`,`Safety check failed: ${E.error}`);p.push({name:`verifying`,durationMs:Date.now()-x,attempts:1});let D=J(o.allOrderedCommits,n.applyCommitIds),{leftoverHunks:O,leftoverPatch:k}=X(D,b.hunks),A=O.length>0?await G(r,b.snapshot,Y(D,b.hunks)):b.snapshot;a?.({phase:`applying`,...F.applying});let j=Date.now(),M=await w({git:r,source:l,target:u,branchPlans:o.branches,allOrderedCommits:D,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 P=await ae(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: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 i=t.baseSha??t.headSha,o=j(A(),`compose-filtered-idx-${O()}`),s={GIT_INDEX_FILE:o};try{await l(e,i,{indexEnv:s});let o=b(n);o.length>0&&await r(e,o,{indexEnv:s});let c=await a(e,i,await d(e,{indexEnv:s}));return{...t,diffHash:x(c)}}finally{try{k(o)}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,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 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=b(r);return{leftoverHunks:r,leftoverPatch:i.length>0?i:void 0}}async function Z(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
|
|
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};
|
package/dist/workflows/split.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{GitError as e}from"../errors/git.js";import{forEachRef as t}from"../git/port-dispatch.js";import{addUsage as
|
|
2
|
-
`)),P=j.map(e=>({id:e.sha,message:e.message,explanation:``,hunkIndices:[]})),F;try{F=await
|
|
1
|
+
import{GitError as e}from"../errors/git.js";import{forEachRef as t}from"../git/port-dispatch.js";import{parseUnifiedDiff as n}from"../git/utils/hunk-parser.js";import{addUsage as r,emptyUsage as i}from"../ai/types.js";import{analyze as a}from"../tasks/analyze/analyze.js";import{partition as o}from"../tasks/partition/partition.js";import{createStackedBranches as s}from"../git/apply-branches.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=i(),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=n(j.map(e=>e.diff).join(`
|
|
2
|
+
`)),P=j.map(e=>({id:e.sha,message:e.message,explanation:``,hunkIndices:[]})),F;try{F=await a({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&&r(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 o({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&&r(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 s({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
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};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitkraken/compose-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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
|
},
|