@gitkraken/compose-tools 0.3.1 → 0.3.2

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 CHANGED
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/) and this
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.2] - 2026-06-02
10
+
11
+ ### Fixed
12
+
13
+ - `defaultTargetForSource` computed the wrong `rangeFrom` when defaulting a branch source to a rewrite range. `git log --reverse -n 1` returns the branch tip, not the oldest commit in `mergeBase..tip`, so the rewrite started from the wrong commit. Now reads the full range and takes the last entry.
14
+
9
15
  ## [0.3.1] - 2026-05-14
10
16
 
11
17
  ### Fixed
@@ -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 re(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 X(i,o);Q(o,f),await K(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`,Z(o));let{hunks:g,filteredCount:_}=await U(h.hunks,r.hunkFilter);if(g.length===0)throw new e(`NO_CHANGES`,Z(o));let v=_!==void 0&&_>0?await W(i,h.snapshot,g):h.snapshot,y={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},b;try{b=await ee(d,y)}catch(e){D(e,l)}let O={analysis:b.analysis,grouping:b.grouping,ordering:b.ordering,branches:b.branchPlans,allOrderedCommits:b.allOrderedCommits};if(p.push(...b.phases),c&&!await c(O))return{plan:O,snapshot:v,session:b.session,usage:b.usage,phases:p,source:{type:o.type===`branch`?`branch`:`workdir`,hunks:g,commitMessages:h.commitMessages,branchName:h.branchName,baseBranch:h.baseBranch},diffStats:V(g),feedbackTargetCallId:H(p),hunksFilteredCount:_};let k=await N(w(i,`capturing pre-operation state`));s?.({phase:`verifying`,...F.verifying});let A=Date.now(),j=await S(i,h.snapshot,G(o,h));if(!j.valid)throw new t(`SAFETY_CHECK_FAILED`,`Safety check failed: ${j.error}`);p.push({name:`verifying`,durationMs:Date.now()-A,attempts:1});let M=q(b.allOrderedCommits,r.applyCommitIds),{leftoverHunks:P,leftoverPatch:I}=Y(M,h.hunks),L=P.length>0?await W(i,v,J(M,h.hunks)):v;s?.({phase:`applying`,...F.applying});let R=Date.now(),z=await C({git:i,source:o,target:f,branchPlans:b.branchPlans,allOrderedCommits:M,hunks:h.hunks,snapshot:L,collection:h,signing:r.signing,authorAttribution:r.authorAttribution,stashLabel:r.stashLabel,leftoverPatch:I});p.push({name:`applying`,durationMs:Date.now()-R,attempts:1});let B=await ae(i,k,z.mutations,f,o);return{plan:O,snapshot:L,partitions:z.partitions,partitionGroups:z.partitionGroups,session:b.session,usage:b.usage,phases:p,source:{type:o.type===`branch`?`branch`:`workdir`,hunks:g,commitMessages:h.commitMessages,branchName:h.branchName,baseBranch:h.baseBranch},diffStats:V(g),commitShas:z.commitShas,undoId:B,feedbackTargetCallId:H(p),hunksFilteredCount:_,appliedCommitIds:M.map(e=>e.id),unappliedHunkIndices:P.length>0?P.map(e=>e.index):void 0,stashConflict:z.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 te(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}}async function ne(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 re(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 X(r,u);Q(u,d),await K(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`,Z(u));let{hunks:g}=await U(h.hunks,n.hunkFilter);if(g.length===0)throw new e(`NO_CHANGES`,Z(u));let _=g.length<h.hunks.length?await W(r,h.snapshot,g):h.snapshot,v={...h,hunks:g,snapshot:_};if(v.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 y=Date.now(),b=await S(r,v.snapshot,G(u,v));if(!b.valid)throw new t(`SAFETY_CHECK_FAILED`,`Safety check failed: ${b.error}`);p.push({name:`verifying`,durationMs:Date.now()-y,attempts:1});let E=q(s.allOrderedCommits,n.applyCommitIds),{leftoverHunks:D,leftoverPatch:O}=Y(E,v.hunks),k=D.length>0?await W(r,v.snapshot,J(E,v.hunks)):v.snapshot;o?.({phase:`applying`,...F.applying});let A=Date.now(),j=await C({git:r,source:u,target:d,branchPlans:s.branches,allOrderedCommits:E,hunks:v.hunks,snapshot:k,collection:v,signing:n.signing,authorAttribution:n.authorAttribution,stashLabel:n.stashLabel,leftoverPatch:O});p.push({name:`applying`,durationMs:Date.now()-A,attempts:1});let M=await ae(r,f,j.mutations,d,u);return{plan:s,snapshot:k,partitions:j.partitions,partitionGroups:j.partitionGroups,session:{messages:[],systemPrompt:``,completedSteps:{}},usage:i(),phases:p,source:{type:u.type===`branch`?`branch`:`workdir`,hunks:v.hunks,commitMessages:v.commitMessages,branchName:v.branchName,baseBranch:v.baseBranch},diffStats:V(v.hunks),commitShas:j.commitShas,undoId:M,appliedCommitIds:E.map(e=>e.id),unappliedHunkIndices:D.length>0?D.map(e=>e.index):void 0,stashConflict:j.stashConflict}}function V(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 H(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 U(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 W(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 G(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 K(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 q(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 J(e,t){let n=new Set(e.flatMap(e=>e.hunkIndices));return t.filter(e=>n.has(e.index))}function Y(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 X(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}`,reverse:!0,limit:1})).trim().split(`
2
- `)[0];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 Q(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 $(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 ie(e){let t=(await p(e,{format:`%(refname:short)`,prefix:`refs/heads/`})).trim().split(`
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 re(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 X(i,o);Q(o,f),await K(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`,Z(o));let{hunks:g,filteredCount:_}=await U(h.hunks,r.hunkFilter);if(g.length===0)throw new e(`NO_CHANGES`,Z(o));let v=_!==void 0&&_>0?await W(i,h.snapshot,g):h.snapshot,y={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},b;try{b=await ee(d,y)}catch(e){D(e,l)}let O={analysis:b.analysis,grouping:b.grouping,ordering:b.ordering,branches:b.branchPlans,allOrderedCommits:b.allOrderedCommits};if(p.push(...b.phases),c&&!await c(O))return{plan:O,snapshot:v,session:b.session,usage:b.usage,phases:p,source:{type:o.type===`branch`?`branch`:`workdir`,hunks:g,commitMessages:h.commitMessages,branchName:h.branchName,baseBranch:h.baseBranch},diffStats:V(g),feedbackTargetCallId:H(p),hunksFilteredCount:_};let k=await N(w(i,`capturing pre-operation state`));s?.({phase:`verifying`,...F.verifying});let A=Date.now(),j=await S(i,h.snapshot,G(o,h));if(!j.valid)throw new t(`SAFETY_CHECK_FAILED`,`Safety check failed: ${j.error}`);p.push({name:`verifying`,durationMs:Date.now()-A,attempts:1});let M=q(b.allOrderedCommits,r.applyCommitIds),{leftoverHunks:P,leftoverPatch:I}=Y(M,h.hunks),L=P.length>0?await W(i,v,J(M,h.hunks)):v;s?.({phase:`applying`,...F.applying});let R=Date.now(),z=await C({git:i,source:o,target:f,branchPlans:b.branchPlans,allOrderedCommits:M,hunks:h.hunks,snapshot:L,collection:h,signing:r.signing,authorAttribution:r.authorAttribution,stashLabel:r.stashLabel,leftoverPatch:I});p.push({name:`applying`,durationMs:Date.now()-R,attempts:1});let B=await ae(i,k,z.mutations,f,o);return{plan:O,snapshot:L,partitions:z.partitions,partitionGroups:z.partitionGroups,session:b.session,usage:b.usage,phases:p,source:{type:o.type===`branch`?`branch`:`workdir`,hunks:g,commitMessages:h.commitMessages,branchName:h.branchName,baseBranch:h.baseBranch},diffStats:V(g),commitShas:z.commitShas,undoId:B,feedbackTargetCallId:H(p),hunksFilteredCount:_,appliedCommitIds:M.map(e=>e.id),unappliedHunkIndices:P.length>0?P.map(e=>e.index):void 0,stashConflict:z.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 te(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}}async function ne(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 re(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 X(r,u);Q(u,d),await K(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`,Z(u));let{hunks:g}=await U(h.hunks,n.hunkFilter);if(g.length===0)throw new e(`NO_CHANGES`,Z(u));let _=g.length<h.hunks.length?await W(r,h.snapshot,g):h.snapshot,v={...h,hunks:g,snapshot:_};if(v.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 y=Date.now(),b=await S(r,v.snapshot,G(u,v));if(!b.valid)throw new t(`SAFETY_CHECK_FAILED`,`Safety check failed: ${b.error}`);p.push({name:`verifying`,durationMs:Date.now()-y,attempts:1});let E=q(s.allOrderedCommits,n.applyCommitIds),{leftoverHunks:D,leftoverPatch:O}=Y(E,v.hunks),k=D.length>0?await W(r,v.snapshot,J(E,v.hunks)):v.snapshot;o?.({phase:`applying`,...F.applying});let A=Date.now(),j=await C({git:r,source:u,target:d,branchPlans:s.branches,allOrderedCommits:E,hunks:v.hunks,snapshot:k,collection:v,signing:n.signing,authorAttribution:n.authorAttribution,stashLabel:n.stashLabel,leftoverPatch:O});p.push({name:`applying`,durationMs:Date.now()-A,attempts:1});let M=await ae(r,f,j.mutations,d,u);return{plan:s,snapshot:k,partitions:j.partitions,partitionGroups:j.partitionGroups,session:{messages:[],systemPrompt:``,completedSteps:{}},usage:i(),phases:p,source:{type:u.type===`branch`?`branch`:`workdir`,hunks:v.hunks,commitMessages:v.commitMessages,branchName:v.branchName,baseBranch:v.baseBranch},diffStats:V(v.hunks),commitShas:j.commitShas,undoId:M,appliedCommitIds:E.map(e=>e.id),unappliedHunkIndices:D.length>0?D.map(e=>e.index):void 0,stashConflict:j.stashConflict}}function V(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 H(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 U(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 W(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 G(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 K(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 q(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 J(e,t){let n=new Set(e.flatMap(e=>e.hunkIndices));return t.filter(e=>n.has(e.index))}function Y(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 X(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 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 Q(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 $(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 ie(e){let t=(await p(e,{format:`%(refname:short)`,prefix:`refs/heads/`})).trim().split(`
3
3
  `).filter(Boolean);return new Set(t)}async function ae(e,t,n,r,i){let a=P(),o=n.createdRefs.filter(e=>e.kind===`branch`).length||1,s=`compose: ${o} branch${o===1?``:`es`} from ${i.type}`,c=[...n.movedRefs];if(r.type===`head`&&t.headBranch){let n=await _(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 M(w(e,`undo manifest persistence`)).save(l),a}export{ne as applyComposePlan,I as compose,te as composePlan,F as defaultPhaseMessages};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitkraken/compose-tools",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
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",