@gitkraken/compose-tools 0.7.0 → 0.8.1

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +8 -5
  3. package/dist/ai/types.d.ts +46 -1
  4. package/dist/git/apply-target.d.ts +2 -1
  5. package/dist/git/apply-target.js +1 -1
  6. package/dist/git/commit-chain.d.ts +17 -6
  7. package/dist/git/commit-chain.js +7 -6
  8. package/dist/git/diff-collector.d.ts +35 -4
  9. package/dist/git/diff-collector.js +4 -4
  10. package/dist/git/index.js +1 -1
  11. package/dist/git/port-dispatch.js +1 -1
  12. package/dist/git/safety.js +1 -1
  13. package/dist/git/types.d.ts +19 -1
  14. package/dist/index.d.ts +3 -3
  15. package/dist/ports/git.d.ts +8 -1
  16. package/dist/tasks/ai-loop.d.ts +10 -1
  17. package/dist/tasks/ai-loop.js +1 -1
  18. package/dist/tasks/analyze/analyze.js +1 -1
  19. package/dist/tasks/analyze/prompt.js +1 -0
  20. package/dist/tasks/analyze/validator.d.ts +1 -1
  21. package/dist/tasks/analyze/validator.js +1 -1
  22. package/dist/tasks/compose-group/compose-group.js +1 -1
  23. package/dist/tasks/compose-group/prompt.js +6 -6
  24. package/dist/tasks/compose-group/types.d.ts +3 -11
  25. package/dist/tasks/group/group.js +1 -1
  26. package/dist/tasks/group/prompt.js +4 -4
  27. package/dist/tasks/group/types.d.ts +6 -1
  28. package/dist/tasks/group/validator.d.ts +10 -2
  29. package/dist/tasks/group/validator.js +5 -5
  30. package/dist/tasks/quick-compose/prompt.js +1 -1
  31. package/dist/tasks/quick-compose/quick-compose.js +1 -1
  32. package/dist/tasks/quick-compose/validator.d.ts +1 -1
  33. package/dist/tasks/quick-compose/validator.js +2 -2
  34. package/dist/tasks/refine/prompt.d.ts +1 -1
  35. package/dist/tasks/refine/prompt.js +4 -4
  36. package/dist/tasks/refine/refine.js +1 -1
  37. package/dist/tasks/refine/types.d.ts +10 -4
  38. package/dist/tasks/refine/validator.d.ts +1 -1
  39. package/dist/tasks/refine/validator.js +1 -1
  40. package/dist/workflows/compose.d.ts +6 -2
  41. package/dist/workflows/compose.js +3 -3
  42. package/dist/workflows/refine.d.ts +2 -2
  43. package/dist/workflows/refine.js +1 -1
  44. package/dist/workflows/types.d.ts +17 -0
  45. package/package.json +1 -1
@@ -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{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};
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{sha256 as S}from"../git/utils/crypto.js";import{collectFromSource as C,resolveWorkdirLayers as w}from"../git/diff-collector.js";import{buildPatchForCommit as T}from"../git/utils/patch-builder.js";import{verifySnapshot as E}from"../git/safety.js";import{applyToTarget as D}from"../git/apply-target.js";import{requireExec as O}from"../git/require-exec.js";import{extractConventions as k}from"../git/utils/conventions.js";import{rethrowAsWorkflowError as A}from"../utils/cancellation.js";import{randomUUID as j}from"node:crypto";import{unlinkSync as M}from"node:fs";import{tmpdir as N}from"node:os";import{join as P}from"node:path";import{UndoStore as F,capturePreOperationState as I,generateUndoId as L}from"@gitkraken/shared-tools";const R={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 z(r){if(!r.applyPlan&&!r.model)throw new n(`Either 'model' or 'applyPlan' must be provided`);if(r.applyPlan)return te(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 oe(i,o);se(o,p),await ne(i,r.applyCommitIds,r.hunkFilter,o,p),await f({git:i,source:o,target:p,overrides:r.overrideSafetyFailures});let m=[];s?.({phase:`collecting`,...R.collecting});let g=Date.now(),_=await C(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 J(_.hunks,r.hunkFilter);if(v.length===0)throw new e(`NO_CHANGES`,Z(o));let b=K(v);s?.({phase:`collecting`,...R.collecting,stats:b});let S=y!==void 0&&y>0?await Y(i,_.snapshot,v):_.snapshot,w=r.conventions??await k({git:i,author:r.conventionAuthor}),T=await ce(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,F={hunks:v,collection:_,conventions:w,existingBranchNames:T,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},L;try{L=await ee(d,F)}catch(e){A(e,l)}let z={analysis:L.analysis,grouping:L.grouping,ordering:L.ordering,branches:L.branchPlans,allOrderedCommits:L.allOrderedCommits};if(m.push(...L.phases),c&&!await c(z))return{plan:z,snapshot:S,session:L.session,usage:L.usage,phases:m,source:{type:o.type===`branch`?`branch`:`workdir`,hunks:v,commitMessages:_.commitMessages,branchName:_.branchName,baseBranch:_.baseBranch},diffStats:b,feedbackTargetCallId:q(m),hunksFilteredCount:y,hunksRedactedCount:P,hunksCondensedCount:L.hunksCondensedCount};let B=await I(O(i,`capturing pre-operation state`));s?.({phase:`verifying`,...R.verifying});let V=Date.now(),H=await E(i,_.snapshot,X(o,_));if(!H.valid)throw new t(`SAFETY_CHECK_FAILED`,`Safety check failed: ${H.error}`);m.push({name:`verifying`,durationMs:Date.now()-V,attempts:1});let U=re(L.allOrderedCommits,r.applyCommitIds),{leftoverHunks:W,leftoverPatch:G}=ae(U,_.hunks),Q=W.length>0?await Y(i,S,ie(U,v)):S;s?.({phase:`applying`,...R.applying});let ue=Date.now(),$=await D({git:i,source:o,target:p,branchPlans:L.branchPlans,allOrderedCommits:U,hunks:v,snapshot:Q,collection:_,signing:r.signing,authorAttribution:r.authorAttribution,stashLabel:r.stashLabel,leftoverPatch:G});m.push({name:`applying`,durationMs:Date.now()-ue,attempts:1});let de=await le(i,B,$.mutations,p,o);return{plan:z,snapshot:Q,partitions:$.partitions,partitionGroups:$.partitionGroups,session:L.session,usage:L.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:q(m),hunksFilteredCount:y,hunksRedactedCount:P,hunksCondensedCount:L.hunksCondensedCount,appliedCommitIds:U.map(e=>e.id),unappliedHunkIndices:W.length>0?W.map(e=>e.index):void 0,stashConflict:$.stashConflict,stashNotRestored:$.stashNotRestored}}function ee(e,t){switch(e){case`quick`:return B(t);case`balanced`:return V(t);case`deep`:return U(t)}}async function B(e){return H(e,{lightweight:!0,skipOrder:!0})}async function V(e){return H(e,{includeAnalysis:!0})}async function H(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`,...R[`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,repairs:E.repairs});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`,...R.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`,...R.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 U(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`,...R.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`,...R.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,repairs:D.repairs});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`,...R.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`,...R.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 W(e){let t=await z({...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 G(e){let t=await z({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,stashNotRestored:t.stashNotRestored}}async function te(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 oe(r,l);se(l,u),await ne(r,n.applyCommitIds,n.hunkFilter,l,u),await f({git:r,source:l,target:u,overrides:n.overrideSafetyFailures});let d=await I(O(r,`capturing pre-operation state`)),p=[];a?.({phase:`collecting`,...R.collecting});let h=Date.now(),g=await C(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 J(g.hunks,n.hunkFilter);if(_.length===0)throw new e(`NO_CHANGES`,Z(l));let v=K(_);a?.({phase:`collecting`,...R.collecting,stats:v});let y=_.length<g.hunks.length?await Y(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`,...R.verifying});let x=Date.now(),S=await E(r,b.snapshot,X(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 w=re(o.allOrderedCommits,n.applyCommitIds),{leftoverHunks:T,leftoverPatch:k}=ae(w,g.hunks),A=T.length>0?await Y(r,b.snapshot,ie(w,b.hunks)):b.snapshot;a?.({phase:`applying`,...R.applying});let j=Date.now(),M=await D({git:r,source:l,target:u,branchPlans:o.branches,allOrderedCommits:w,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 le(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:w.map(e=>e.id),unappliedHunkIndices:T.length>0?T.map(e=>e.index):void 0,stashConflict:M.stashConflict,stashNotRestored:M.stashNotRestored}}function K(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 q(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 J(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 Y(e,t,n){let i=t.baseSha??t.headSha,o=P(N(),`compose-filtered-idx-${j()}`),s={GIT_INDEX_FILE:o};try{await l(e,i,{indexEnv:s});let o=T(n);o.length>0&&await r(e,o,{indexEnv:s});let c=await a(e,i,await d(e,{indexEnv:s}));return{...t,diffHash:S(c)}}finally{try{M(o)}catch{}}}function X(e,t){return e.type===`workdir`?{includeUnstaged:!e.stagedOnly,includeUntracked:t.untrackedIncluded}:e.type===`commit-range`&&e.includeWorkdir?w(e.includeWorkdir):{includeUnstaged:!1,includeUntracked:!1}}async function ne(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 re(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 ie(e,t){let n=new Set(e.flatMap(e=>e.hunkIndices));return t.filter(e=>n.has(e.index))}function ae(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=T(r);return{leftoverHunks:r,leftoverPatch:i.length>0?i:void 0}}async function oe(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 se(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 ce(e){let t=(await o(e,{format:`%(refname:short)`,prefix:`refs/heads/`})).trim().split(`
3
+ `).filter(Boolean);return new Set(t)}async function le(e,t,n,r,i){let a=L(),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 F(O(e,`undo manifest persistence`)).save(l),a}export{G as applyComposePlan,z as compose,W as composePlan,R as defaultPhaseMessages};
@@ -19,7 +19,7 @@ type RefineProgressEvent = {
19
19
  *
20
20
  * The workflow takes a higher-level shape than the task does — it accepts a full
21
21
  * {@link ComposePlan} and a list of `lockedCommits` *as ids*, materializes those ids into
22
- * `CommitSuggestion` objects from the prior plan, derives the hunk count from the prior
22
+ * `CommitSuggestion` objects from the prior plan, derives the hunk index set from the prior
23
23
  * plan's commits, and lifts the task's `GroupingResult` back into a full `ComposePlan`. The
24
24
  * task itself stays lower-level and reusable.
25
25
  */
@@ -31,7 +31,7 @@ interface RefinePlanInput {
31
31
  */
32
32
  session: ComposeSession;
33
33
  /** The plan to be refined. Used to resolve `lockedCommits` ids to their fixed content
34
- * and to derive total hunk count + output mode (commits vs branches) for validation. */
34
+ * and to derive the hunk index set + output mode (commits vs branches) for validation. */
35
35
  priorPlan: ComposePlan;
36
36
  model: AiModelPort;
37
37
  /** The user's refinement instructions. If absent, the prompt uses a generic "review your
@@ -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 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};
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,hunkIndices: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,repairs:p.repairs}],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}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};
@@ -26,6 +26,23 @@ type ComposeSource = {
26
26
  branch: string; /** First commit in the range to recompose (inclusive). Must be an ancestor of `to`. */
27
27
  from: string; /** Last commit in the range to recompose (inclusive). */
28
28
  to: string;
29
+ /**
30
+ * Fold working-directory changes into the composed input. **Each layer defaults to
31
+ * `true`**, so `{ includeStaged: true }` composes the whole working directory — name a
32
+ * layer with `false` to keep it out.
33
+ *
34
+ * A layer excluded this way is neither composed nor discarded: its content is captured
35
+ * immediately before the apply resets the worktree and re-applied afterward as unstaged
36
+ * changes. This is a different thing from `hunkFilter` or a partial apply, which exclude
37
+ * specific changes the caller picked out of the composed input; an excluded layer was
38
+ * never part of that input. The layer's content survives, but its staged/unstaged
39
+ * position does not — excluded staged changes come back unstaged.
40
+ *
41
+ * One combination is rejected: `includeStaged: false` with `includeUnstaged: true` while
42
+ * staged changes exist. Unstaged changes are diffed against the index, so they already
43
+ * presuppose the staged content — there is no coherent way to replay the excluded staged
44
+ * layer over a result composed from unstaged changes.
45
+ */
29
46
  includeWorkdir?: {
30
47
  includeStaged?: boolean;
31
48
  includeUnstaged?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitkraken/compose-tools",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
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",