@gitkraken/compose-tools 0.1.0 → 0.2.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +32 -1
  2. package/README.md +80 -14
  3. package/dist/errors/workflow.d.ts +16 -6
  4. package/dist/errors/workflow.js +1 -1
  5. package/dist/git/apply-branches.d.ts +0 -2
  6. package/dist/git/apply-branches.js +1 -1
  7. package/dist/git/apply-target.d.ts +13 -2
  8. package/dist/git/apply-target.js +1 -1
  9. package/dist/git/commit-chain.d.ts +27 -7
  10. package/dist/git/commit-chain.js +7 -5
  11. package/dist/git/diff-collector.d.ts +37 -10
  12. package/dist/git/diff-collector.js +7 -7
  13. package/dist/git/port-dispatch.d.ts +9 -3
  14. package/dist/git/port-dispatch.js +3 -3
  15. package/dist/git/require-exec.js +1 -1
  16. package/dist/git/safety.d.ts +3 -3
  17. package/dist/git/safety.js +1 -1
  18. package/dist/git/types.d.ts +6 -1
  19. package/dist/git/utils/conventions.d.ts +0 -1
  20. package/dist/git/utils/conventions.js +4 -4
  21. package/dist/git/utils/temp-index.d.ts +1 -1
  22. package/dist/git/utils/temp-index.js +1 -1
  23. package/dist/index.d.ts +5 -3
  24. package/dist/index.js +1 -1
  25. package/dist/ports/git.d.ts +27 -7
  26. package/dist/tasks/ai-loop.d.ts +9 -15
  27. package/dist/tasks/ai-loop.js +2 -2
  28. package/dist/tasks/analyze/analyze.js +1 -1
  29. package/dist/tasks/analyze/types.d.ts +3 -1
  30. package/dist/tasks/analyze/validator.js +1 -1
  31. package/dist/tasks/compose-group/compose-group.js +1 -1
  32. package/dist/tasks/compose-group/prompt.js +27 -21
  33. package/dist/tasks/compose-group/types.d.ts +3 -1
  34. package/dist/tasks/group/group.js +1 -1
  35. package/dist/tasks/group/prompt.js +27 -21
  36. package/dist/tasks/group/types.d.ts +3 -1
  37. package/dist/tasks/group/validator.js +9 -4
  38. package/dist/tasks/order/order.js +1 -1
  39. package/dist/tasks/order/types.d.ts +3 -1
  40. package/dist/tasks/order/validator.js +1 -1
  41. package/dist/tasks/partition/partition.js +1 -1
  42. package/dist/tasks/partition/types.d.ts +3 -1
  43. package/dist/tasks/partition/validator.js +1 -1
  44. package/dist/tasks/quick-compose/prompt.js +14 -5
  45. package/dist/tasks/quick-compose/quick-compose.js +1 -1
  46. package/dist/tasks/quick-compose/types.d.ts +3 -1
  47. package/dist/tasks/quick-compose/validator.js +10 -2
  48. package/dist/utils/cancellation.d.ts +26 -0
  49. package/dist/utils/cancellation.js +1 -0
  50. package/dist/workflows/compose.d.ts +78 -10
  51. package/dist/workflows/compose.js +2 -2
  52. package/dist/workflows/split.d.ts +11 -3
  53. package/dist/workflows/split.js +3 -3
  54. package/dist/workflows/types.d.ts +5 -0
  55. package/dist/workflows/undo.d.ts +18 -11
  56. package/dist/workflows/undo.js +1 -1
  57. package/package.json +2 -2
  58. package/dist/cancellation.d.ts +0 -20
  59. package/dist/cancellation.js +0 -1
@@ -1,8 +1,8 @@
1
- import{GitError as e}from"../errors/git.js";import{createBranch as t,diffTree as n,gitDir as r,log as i,mergeBase as a,revParse as o,revParseVerify as s,stageAll as c,writeTree as l}from"./port-dispatch.js";import{sha256 as u}from"./utils/crypto.js";import{parseUnifiedDiff as d}from"./utils/hunk-parser.js";import{withUntrackedInTempIndex as f}from"./utils/temp-index.js";import{randomUUID as p}from"node:crypto";import{copyFileSync as m,unlinkSync as h}from"node:fs";import{tmpdir as g}from"node:os";import{join as _}from"node:path";async function v(e,t,n){let{signal:r}=n??{};if(r?.throwIfAborted(),t.type===`workdir`){let n=!t.stagedOnly&&!t.noUntracked;return{...await y(e,{includeUnstaged:!t.stagedOnly,includeUntracked:n},r),untrackedIncluded:n}}if(t.type===`commit-range`){let n=await T(e,t.branch,t.from,t.to,r);return{hunks:n.hunks,snapshot:n.snapshot,combinedDiff:n.combinedDiff,commitMessages:n.commits.map(e=>e.message),commitOrder:n.commits.map(e=>e.sha),branchName:t.branch,baseBranch:void 0,branchSha:n.snapshot.branchSha,mergeBaseSha:n.snapshot.baseSha,untrackedIncluded:!1}}let i=t.mergeTarget??`main`,a=await w(e,t.name,i,r);return{hunks:a.hunks,snapshot:a.snapshot,combinedDiff:a.combinedDiff,commitMessages:a.commits.map(e=>e.message),commitOrder:a.commits.map(e=>e.sha),branchName:t.name,baseBranch:i,branchSha:a.snapshot.branchSha,mergeBaseSha:a.snapshot.baseSha,untrackedIncluded:!1}}async function y(e,t,n){let r=t.includeUnstaged||t.includeUntracked;return t.includeUntracked?S(e,r,n):b(e,r,n)}async function b(e,t,n){let r=await o(e,`HEAD`,{signal:n}),i=await x(e,r,t,n);return{hunks:d(i,`staged`),snapshot:k(i,r,void 0,void 0,await A(e,{includeUnstaged:t,includeUntracked:!1},n)),combinedDiff:i}}async function x(e,t,i,a){if(!i)return n(e,t,await l(e,{signal:a}),{signal:a});let o=_(await r(e,{signal:a}),`index`),s=_(g(),`compose-snapshot-idx-${p()}`);try{m(o,s);let r={GIT_INDEX_FILE:s};return await c(e,{trackedOnly:!0,indexEnv:r,signal:a}),n(e,t,await l(e,{indexEnv:r,signal:a}),{signal:a})}finally{try{h(s)}catch{}}}async function S(e,t,r){return f(e,async i=>{await C(e,i,t,r);let a=await l(e,{indexEnv:i,signal:r}),s=await o(e,`HEAD`,{signal:r}),c=await n(e,s,a,{signal:r});return{hunks:d(c,`staged`),snapshot:k(c,s,void 0,void 0,await A(e,{includeUnstaged:t,includeUntracked:!0},r)),combinedDiff:c}},r)}async function C(e,t,n,r){n&&await c(e,{trackedOnly:!0,indexEnv:t,signal:r}),await c(e,{indexEnv:t,signal:r})}async function w(e,t,r,s){let c=await O(e,t,s),l=await a(e,await O(e,r,s),c,{signal:s}),u=await o(e,c,{signal:s}),f=await n(e,l,c,{signal:s});return{hunks:d(f,`branch`),commits:j(await i(e,{format:`%H%n%s%n---END---`,range:`${l}..${c}`,signal:s})),snapshot:k(f,await o(e,`HEAD`,{signal:s}),u,l),combinedDiff:f}}async function T(t,r,a,s,c){await O(t,r,c);let l=await o(t,a,{signal:c}),u=await o(t,s,{signal:c}),f=E(await i(t,{format:`%H%n%an%n%ae%n%aI%n%B%n---COMMIT-END---`,range:`${l}..${u}`,reverse:!0,signal:c}));if(f.length===0)throw new e(`NO_CHANGES`,`No commits in range '${a}..${s}' on branch '${r}'.`);let p=[],m=0;for(let e of f){c?.throwIfAborted();let r=d(await n(t,`${e.sha}^`,e.sha,{signal:c}),`branch`,m);for(let t of r)t.sourceCommitSha=e.sha,t.author={name:e.authorName,email:e.authorEmail,date:e.authorDate};p.push(...r),m+=r.length}let h=await n(t,l,u,{signal:c}),g=k(h,await o(t,`HEAD`,{signal:c}),u,l);return{hunks:p,commits:f.map(e=>({sha:e.sha,message:e.message})),snapshot:g,combinedDiff:h}}function E(e){let t=[],n=e.split(`---COMMIT-END---`).filter(e=>e.trim());for(let e of n){let n=e.replace(/^\n+/,``).split(`
1
+ import{GitError as e}from"../errors/git.js";import{ComposeWorkflowInputError as t}from"../errors/workflow.js";import{applyPatchToIndex as n,createBranch as r,diffTree as i,gitDir as a,listUntracked as o,log as s,mergeBase as c,readTree as l,revParse as u,revParseVerify as d,stageAll as f,writeTree as p}from"./port-dispatch.js";import{sha256 as m}from"./utils/crypto.js";import{parseUnifiedDiff as h}from"./utils/hunk-parser.js";import{withUntrackedInTempIndex as g}from"./utils/temp-index.js";import{randomUUID as _}from"node:crypto";import{copyFileSync as v,unlinkSync as y}from"node:fs";import{tmpdir as b}from"node:os";import{join as x}from"node:path";async function S(e,t){if(t.type===`workdir`){let n=!t.stagedOnly&&!t.noUntracked;return{...await C(e,{includeUnstaged:!t.stagedOnly,includeUntracked:n}),untrackedIncluded:n}}if(t.type===`commit-range`){if(t.includeWorkdir){let n=t.includeWorkdir.includeStaged??!0,r=t.includeWorkdir.includeUnstaged??!0,i=t.includeWorkdir.includeUntracked??!0,a=await j(e,t.branch,t.from,t.to,{includeStaged:n,includeUnstaged:r,includeUntracked:i});return{hunks:a.hunks,snapshot:a.snapshot,combinedDiff:a.combinedDiff,commitMessages:a.commits.map(e=>e.message),commitOrder:a.commits.map(e=>e.sha),branchName:t.branch,baseBranch:void 0,branchSha:a.snapshot.branchSha,mergeBaseSha:a.snapshot.baseSha,untrackedIncluded:i}}let n=await k(e,t.branch,t.from,t.to);return{hunks:n.hunks,snapshot:n.snapshot,combinedDiff:n.combinedDiff,commitMessages:n.commits.map(e=>e.message),commitOrder:n.commits.map(e=>e.sha),branchName:t.branch,baseBranch:void 0,branchSha:n.snapshot.branchSha,mergeBaseSha:n.snapshot.baseSha,untrackedIncluded:!1}}let n=t.mergeTarget??`main`,r=await O(e,t.name,n);return{hunks:r.hunks,snapshot:r.snapshot,combinedDiff:r.combinedDiff,commitMessages:r.commits.map(e=>e.message),commitOrder:r.commits.map(e=>e.sha),branchName:t.name,baseBranch:n,branchSha:r.snapshot.branchSha,mergeBaseSha:r.snapshot.baseSha,untrackedIncluded:!1}}async function C(e,t){let n=t.includeUnstaged||t.includeUntracked;return t.includeUntracked?E(e,n):w(e,n)}async function w(e,t){let n=await u(e,`HEAD`),r=await T(e,n,t);return{hunks:h(r,`staged`),snapshot:F(r,n,void 0,void 0,await I(e,{includeUnstaged:t,includeUntracked:!1})),combinedDiff:r}}async function T(e,t,n){if(!n)return i(e,t,await p(e));let r=x(await a(e),`index`),o=x(b(),`compose-snapshot-idx-${_()}`);try{v(r,o);let n={GIT_INDEX_FILE:o};return await f(e,{trackedOnly:!0,indexEnv:n}),i(e,t,await p(e,{indexEnv:n}))}finally{try{y(o)}catch{}}}async function E(e,t){return g(e,async n=>{await D(e,n,t);let r=await p(e,{indexEnv:n}),a=await u(e,`HEAD`),o=await i(e,a,r);return{hunks:h(o,`staged`),snapshot:F(o,a,void 0,void 0,await I(e,{includeUnstaged:t,includeUntracked:!0})),combinedDiff:o}})}async function D(e,t,n){n&&await f(e,{trackedOnly:!0,indexEnv:t}),await f(e,{indexEnv:t})}async function O(e,t,n){let r=await P(e,t),a=await c(e,await P(e,n),r),o=await u(e,r),l=await i(e,a,r);return{hunks:h(l,`branch`),commits:L(await s(e,{format:`%H%n%s%n---END---`,range:`${a}..${r}`})),snapshot:F(l,await u(e,`HEAD`),o,a),combinedDiff:l}}async function k(t,n,r,a){await P(t,n);let o=await u(t,r),c=await u(t,a),l=A(await s(t,{format:`%H%n%an%n%ae%n%aI%n%B%n---COMMIT-END---`,range:`${o}..${c}`,reverse:!0}));if(l.length===0)throw new e(`NO_CHANGES`,`No commits in range '${r}..${a}' on branch '${n}'.`);let d=[],f=0;for(let e of l){let n=h(await i(t,`${e.sha}^`,e.sha),`branch`,f);for(let t of n)t.sourceCommitSha=e.sha,t.author={name:e.authorName,email:e.authorEmail,date:e.authorDate};d.push(...n),f+=n.length}let p=await i(t,o,c),m=F(p,await u(t,`HEAD`),c,o);return{hunks:d,commits:l.map(e=>({sha:e.sha,message:e.message})),snapshot:m,combinedDiff:p}}function A(e){let t=[],n=e.split(`---COMMIT-END---`).filter(e=>e.trim());for(let e of n){let n=e.replace(/^\n+/,``).split(`
2
2
  `);if(n.length<5)continue;let r=n[0],i=n[1],a=n[2],o=n[3],s=n.slice(4).join(`
3
- `).trimEnd(),c=o?new Date(o):void 0,l=c&&!Number.isNaN(c.getTime())?c:void 0;t.push({sha:r,authorName:i,authorEmail:a,authorDate:l,message:s})}return t}async function D(e,t,r,s){let c=await O(e,t,s),l=await a(e,await O(e,r,s),c,{signal:s}),u=await o(e,c,{signal:s}),d=(await i(e,{format:`%H`,range:`${l}..${c}`,reverse:!0,signal:s})).trim().split(`
4
- `).filter(Boolean),f=[];for(let t of d){s?.throwIfAborted();let[r,a,o]=await Promise.all([i(e,{format:`%B`,sha:t,signal:s}).then(e=>e.trim()),n(e,`${t}^`,t,{signal:s}),n(e,`${t}^`,t,{binary:!1,nameOnly:!0,signal:s})]),c=o.trim().split(`
5
- `).filter(Boolean);f.push({sha:t,message:r,diff:a,filesChanged:c})}return{commits:f,mergeBaseSha:l,branchSha:u}}async function O(n,r,i){if(await s(n,`refs/heads/${r}`,{signal:i})||await s(n,r,{signal:i}))return r;if(await s(n,`origin/${r}`,{signal:i}))return await t(n,r,`origin/${r}`,{signal:i}),r;throw new e(`BRANCH_NOT_FOUND`,`Branch '${r}' not found locally or on origin. Run 'git fetch' if the branch exists on the remote.`)}function k(e,t,n,r,i){return{diffHash:u(e),headSha:t,branchSha:n,baseSha:r,sourceHashes:i}}async function A(e,t,n){if(!e.exec)return;let r={};try{r.staged=u(await e.exec([`diff`,`--cached`,`--binary`,`HEAD`],{signal:n}))}catch{}if(t.includeUnstaged)try{r.unstaged=u(await e.exec([`diff`,`--binary`],{signal:n}))}catch{}if(t.includeUntracked)try{let t=(await e.exec([`ls-files`,`--others`,`--exclude-standard`],{signal:n})).trim().split(`
6
- `).filter(Boolean);if(t.length>0){let i=[];for(let r of t){n?.throwIfAborted();try{let t=await e.exec([`hash-object`,`--`,r],{signal:n});i.push(`${r}:${t.trim()}`)}catch{}}r.untracked=u(i.join(`
7
- `))}else r.untracked=u(``)}catch{}return Object.keys(r).length>0?r:void 0}function j(e){let t=[],n=e.split(`---END---`).filter(e=>e.trim());for(let e of n){let n=e.trim().split(`
8
- `);n.length>=2&&t.push({sha:n[0],message:n[1]})}return t}export{w as collectBranchDiffs,T as collectCommitRangeDiffs,D as collectCommitsForSplit,v as collectFromSource,A as collectSourceHashes,y as collectWorkingDiffs};
3
+ `).trimEnd(),c=o?new Date(o):void 0,l=c&&!Number.isNaN(c.getTime())?c:void 0;t.push({sha:r,authorName:i,authorEmail:a,authorDate:l,message:s})}return t}async function j(e,n,r,a,o){let s=await u(e,await P(e,n)),c=await u(e,r),l=await u(e,a),d=await u(e,`HEAD`);if(l!==s)throw new t(`commit-range with includeWorkdir requires 'to' to equal branch tip. Branch '${n}' tip is ${s}; got 'to'=${l}.`);if(d!==l)throw new t(`commit-range with includeWorkdir requires HEAD to equal 'to'. HEAD=${d}, 'to'=${l}.`);let f=await k(e,n,r,a);if(!(o.includeStaged||o.includeUnstaged||o.includeUntracked))return f;let{workdirHunks:p,finalTreeSha:m}=await M(e,d,o),h=p.map((e,t)=>({...e,index:f.hunks.length+t})),g=await i(e,c,m),_=F(g,d,l,c,await I(e,o));return{hunks:[...f.hunks,...h],commits:f.commits,snapshot:_,combinedDiff:g}}async function M(e,t,r){let s=x(b(),`compose-workdir-${_()}`),c={GIT_INDEX_FILE:s};try{if(await l(e,t,{indexEnv:c}),r.includeStaged){let r=await i(e,t,await p(e));r.trim()&&await n(e,r,{indexEnv:c})}if(r.includeUnstaged){let t=await p(e),r=x(b(),`compose-workdir-u-${_()}`),o={GIT_INDEX_FILE:r};try{v(x(await a(e),`index`),r),await f(e,{trackedOnly:!0,indexEnv:o});let s=await i(e,t,await p(e,{indexEnv:o}));s.trim()&&await n(e,s,{indexEnv:c})}finally{try{y(r)}catch{}}}if(r.includeUntracked){let t=await o(e);t.length>0&&(await f(e,{intentToAdd:!0,paths:t,indexEnv:c}),await f(e,{paths:t,indexEnv:c}))}let s=await p(e,{indexEnv:c});return{workdirHunks:h(await i(e,t,s),`staged`,0),finalTreeSha:s}}finally{try{y(s)}catch{}}}async function N(e,t,n){let r=await P(e,t),a=await c(e,await P(e,n),r),o=await u(e,r),l=(await s(e,{format:`%H`,range:`${a}..${r}`,reverse:!0})).trim().split(`
4
+ `).filter(Boolean),d=[];for(let t of l){let[n,r,a]=await Promise.all([s(e,{format:`%B`,sha:t}).then(e=>e.trim()),i(e,`${t}^`,t),i(e,`${t}^`,t,{binary:!1,nameOnly:!0})]),o=a.trim().split(`
5
+ `).filter(Boolean);d.push({sha:t,message:n,diff:r,filesChanged:o})}return{commits:d,mergeBaseSha:a,branchSha:o}}async function P(t,n){if(await d(t,`refs/heads/${n}`)||await d(t,n))return n;if(await d(t,`origin/${n}`))return await r(t,n,`origin/${n}`),n;throw new e(`BRANCH_NOT_FOUND`,`Branch '${n}' not found locally or on origin. Run 'git fetch' if the branch exists on the remote.`)}function F(e,t,n,r,i){return{diffHash:m(e),headSha:t,branchSha:n,baseSha:r,sourceHashes:i}}async function I(e,t){if(!e.exec)return;let n=t.includeStaged??!0,r={};if(n)try{r.staged=m(await e.exec([`diff`,`--cached`,`--binary`,`HEAD`]))}catch{}if(t.includeUnstaged)try{r.unstaged=m(await e.exec([`diff`,`--binary`]))}catch{}if(t.includeUntracked)try{let t=(await e.exec([`ls-files`,`--others`,`--exclude-standard`])).trim().split(`
6
+ `).filter(Boolean);if(t.length>0){let n=[];for(let r of t)try{let t=await e.exec([`hash-object`,`--`,r]);n.push(`${r}:${t.trim()}`)}catch{}r.untracked=m(n.join(`
7
+ `))}else r.untracked=m(``)}catch{}return Object.keys(r).length>0?r:void 0}function L(e){let t=[],n=e.split(`---END---`).filter(e=>e.trim());for(let e of n){let n=e.trim().split(`
8
+ `);n.length>=2&&t.push({sha:n[0],message:n[1]})}return t}export{O as collectBranchDiffs,k as collectCommitRangeDiffs,j as collectCommitRangeWithWorkdir,N as collectCommitsForSplit,S as collectFromSource,I as collectSourceHashes,M as collectWorkdirPortionForRange,C as collectWorkingDiffs};
@@ -1,4 +1,4 @@
1
- import { ApplyPatchOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitPort, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, IndexScopedOptions, LogOptions, OpOptions, StageAllOptions, StashPushOptions, UpdateRefOptions } from "../ports/git.js";
1
+ import { ApplyPatchOptions, ApplyPatchToWorkdirOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitPort, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, IndexScopedOptions, LogOptions, OpOptions, StageAllOptions, StashPopOptions, StashPushOptions, UpdateRefOptions } from "../ports/git.js";
2
2
 
3
3
  //#region src/git/port-dispatch.d.ts
4
4
  declare function revParse(git: ComposeGitPort, ref: string, opts?: OpOptions): Promise<string>;
@@ -18,6 +18,7 @@ declare function showFile(git: ComposeGitPort, ref: string, path: string, opts?:
18
18
  declare function getConfig(git: ComposeGitPort, key: string, opts?: OpOptions): Promise<string | null>;
19
19
  declare function commitTree(git: ComposeGitPort, tree: string, parents: string[], message: string, opts?: CommitTreeOptions): Promise<string>;
20
20
  declare function applyPatchToIndex(git: ComposeGitPort, patch: string, opts?: ApplyPatchOptions): Promise<void>;
21
+ declare function applyPatchToWorkdir(git: ComposeGitPort, patch: string, opts?: ApplyPatchToWorkdirOptions): Promise<void>;
21
22
  declare function mergeTree(git: ComposeGitPort, a: string, b: string, opts?: OpOptions): Promise<string>;
22
23
  declare function createBranch(git: ComposeGitPort, name: string, startPoint: string, opts?: OpOptions): Promise<void>;
23
24
  declare function deleteBranch(git: ComposeGitPort, name: string, opts?: DeleteBranchOptions): Promise<void>;
@@ -27,8 +28,13 @@ declare function reset(git: ComposeGitPort, ref: string, mode: 'soft' | 'mixed'
27
28
  declare function cleanWorkdir(git: ComposeGitPort, opts?: CleanOptions): Promise<void>;
28
29
  declare function discardWorkdirChanges(git: ComposeGitPort, paths?: string[], opts?: OpOptions): Promise<void>;
29
30
  declare function stashPush(git: ComposeGitPort, opts?: StashPushOptions): Promise<void>;
30
- declare function stashPop(git: ComposeGitPort, opts?: OpOptions): Promise<void>;
31
+ declare const localeNeutralEnv: {
32
+ LANG: string;
33
+ LC_ALL: string;
34
+ };
35
+ declare function stashPop(git: ComposeGitPort, opts?: StashPopOptions): Promise<void>;
36
+ declare function stashDrop(git: ComposeGitPort, stashRef: string, opts?: OpOptions): Promise<void>;
31
37
  declare function cherryPick(git: ComposeGitPort, sha: string, opts?: OpOptions): Promise<void>;
32
38
  declare function cherryPickAbort(git: ComposeGitPort, opts?: OpOptions): Promise<void>;
33
39
  //#endregion
34
- export { applyPatchToIndex, checkout, cherryPick, cherryPickAbort, cleanWorkdir, commitTree, createBranch, currentBranch, deleteBranch, diffTree, discardWorkdirChanges, forEachRef, getConfig, gitDir, listUntracked, log, mergeBase, mergeTree, readTree, reset, revParse, revParseVerify, showFile, stageAll, stashPop, stashPush, statusPorcelain, updateRef, writeTree };
40
+ export { applyPatchToIndex, applyPatchToWorkdir, checkout, cherryPick, cherryPickAbort, cleanWorkdir, commitTree, createBranch, currentBranch, deleteBranch, diffTree, discardWorkdirChanges, forEachRef, getConfig, gitDir, listUntracked, localeNeutralEnv, log, mergeBase, mergeTree, readTree, reset, revParse, revParseVerify, showFile, stageAll, stashDrop, stashPop, stashPush, statusPorcelain, updateRef, writeTree };
@@ -1,3 +1,3 @@
1
- import{ComposeGitPortMissingOpError as e}from"../ports/git.js";function t(t,n){throw new e(t,n)}async function n(e,n,r){return e.revParse?e.revParse(n,r):e.exec?(await e.exec([`rev-parse`,n],{signal:r?.signal})).trim():t(`revParse`)}async function r(e,n,r){if(e.revParseVerify)return e.revParseVerify(n,r);if(e.exec)try{return(await e.exec([`rev-parse`,`--verify`,n],{signal:r?.signal})).trim()}catch{return null}return t(`revParseVerify`)}async function i(e,n){return e.gitDir?e.gitDir(n):e.exec?(await e.exec([`rev-parse`,`--absolute-git-dir`],{signal:n?.signal})).trim():t(`gitDir`)}async function a(e,n){if(e.currentBranch)return e.currentBranch(n);if(e.exec)try{return(await e.exec([`symbolic-ref`,`--short`,`HEAD`],{signal:n?.signal})).trim()||null}catch{return null}return t(`currentBranch`)}async function o(e,n,r,i){return e.mergeBase?e.mergeBase(n,r,i):e.exec?(await e.exec([`merge-base`,n,r],{signal:i?.signal})).trim():t(`mergeBase`)}async function s(e,n,r,i){if(e.diffTree)return e.diffTree(n,r,i);if(e.exec){let t=[`diff`];return i?.binary!==!1&&t.push(`--binary`),i?.nameOnly&&t.push(`--name-only`),t.push(n,r),e.exec(t,{signal:i?.signal})}return t(`diffTree`)}async function c(e,n){if(e.log)return e.log(n);if(e.exec){let t=[`log`,`--format=${n.format}`];return n.noMerges&&t.push(`--no-merges`),n.reverse&&t.push(`--reverse`),n.limit!=null&&t.push(`-n`,String(n.limit)),n.author&&t.push(`--author=${n.author}`),n.sha?t.push(`-1`,n.sha):n.range&&t.push(n.range),e.exec(t,{signal:n.signal})}return t(`log`)}async function l(e,n){if(e.forEachRef)return e.forEachRef(n);if(e.exec){let t=[`for-each-ref`,`--format=${n.format}`];return n.sort&&t.push(`--sort=${n.sort}`),n.prefix&&t.push(n.prefix),n.limit!=null&&t.push(`--count=${n.limit}`),e.exec(t,{signal:n.signal})}return t(`forEachRef`)}async function u(e,n){return e.writeTree?e.writeTree(n):e.exec?(await e.exec([`write-tree`],{env:n?.indexEnv,signal:n?.signal})).trim():t(`writeTree`)}async function d(e,n,r){if(e.readTree)return e.readTree(n,r);if(e.exec){await e.exec([`read-tree`,n],{env:r?.indexEnv,signal:r?.signal});return}return t(`readTree`)}async function f(e,n){if(e.stageAll)return e.stageAll(n);if(e.exec){let t=[`add`];n?.intentToAdd?t.push(`-N`):n?.trackedOnly?t.push(`-u`):n?.paths||t.push(`-A`),n?.paths&&t.push(`--`,...n.paths),await e.exec(t,{env:n?.indexEnv,signal:n?.signal});return}return t(`stageAll`)}async function p(e,n){return e.statusPorcelain?e.statusPorcelain(n):e.exec?e.exec([`status`,`--porcelain`],{signal:n?.signal}):t(`statusPorcelain`)}async function m(e,n){return e.listUntracked?e.listUntracked(n):e.exec?(await e.exec([`ls-files`,`--others`,`--exclude-standard`],{signal:n?.signal})).trim().split(`
2
- `).filter(Boolean):t(`listUntracked`)}async function h(e,n,r,i){return e.showFile?e.showFile(n,r,i):e.exec?e.exec([`show`,`${n}:${r}`],{signal:i?.signal}):t(`showFile`)}async function g(e,n,r){if(e.getConfig)return e.getConfig(n,r);if(e.exec)try{return(await e.exec([`config`,n],{signal:r?.signal})).trim()||null}catch{return null}return t(`getConfig`)}async function _(e,n,r,i,a){if(e.commitTree)return e.commitTree(n,r,i,a);if(e.exec){let t=[`commit-tree`,n];for(let e of r)t.push(`-p`,e);t.push(`-m`,i),a?.signing?.enabled&&t.push(a.signing.signingKey?`-S${a.signing.signingKey}`:`-S`);let o={};return a?.author&&(o.GIT_AUTHOR_NAME=a.author.name,o.GIT_AUTHOR_EMAIL=a.author.email,o.GIT_COMMITTER_NAME=a.author.name,o.GIT_COMMITTER_EMAIL=a.author.email,a.author.date&&(o.GIT_AUTHOR_DATE=a.author.date.toISOString())),a?.signing?.gpgProgram&&(o.GIT_GPG_PROGRAM=a.signing.gpgProgram),(await e.exec(t,{env:Object.keys(o).length?o:void 0,signal:a?.signal})).trim()}return t(`commitTree`)}async function v(e,n,r){if(e.applyPatchToIndex)return e.applyPatchToIndex(n,r);if(e.exec){let t=[`apply`,`--cached`];r?.allowBinaryReplacement!==!1&&t.push(`--allow-binary-replacement`),await e.exec(t,{env:r?.indexEnv,stdin:n,signal:r?.signal});return}return t(`applyPatchToIndex`)}async function y(e,n,r,i){return e.mergeTree?e.mergeTree(n,r,i):e.exec?(await e.exec([`merge-tree`,`--write-tree`,n,r],{signal:i?.signal})).trim().split(`
3
- `)[0]:t(`mergeTree`)}async function b(e,n,r,i){if(e.createBranch)return e.createBranch(n,r,i);if(e.exec){await e.exec([`branch`,n,r],{signal:i?.signal});return}return t(`createBranch`)}async function x(e,n,r){if(e.deleteBranch)return e.deleteBranch(n,r);if(e.exec){await e.exec([`branch`,r?.force?`-D`:`-d`,n],{signal:r?.signal});return}return t(`deleteBranch`)}async function S(e,n,r,i){if(e.updateRef)return e.updateRef(n,r,i);if(e.exec){if(i?.remove){await e.exec([`update-ref`,`-d`,n],{signal:i?.signal});return}let t=[`update-ref`,n,r];i?.previousSha&&t.push(i.previousSha),await e.exec(t,{signal:i?.signal});return}return t(`updateRef`)}async function C(e,n,r){if(e.checkout)return e.checkout(n,r);if(e.exec){let t=[`checkout`];r?.createBranch&&t.push(`-b`),r?.detach&&t.push(`--detach`),t.push(n),r?.createBranch&&r?.startPoint&&t.push(r.startPoint),await e.exec(t,{signal:r?.signal});return}return t(`checkout`)}async function w(e,n,r,i){if(e.reset)return e.reset(n,r,i);if(e.exec){await e.exec([`reset`,`--${r}`,n],{signal:i?.signal});return}return t(`reset`)}async function T(e,n){if(e.cleanWorkdir)return e.cleanWorkdir(n);if(e.exec){let t=`-`;n?.force!==!1&&(t+=`f`),n?.directories!==!1&&(t+=`d`),t===`-`&&(t=`-fd`),await e.exec([`clean`,t],{signal:n?.signal});return}return t(`cleanWorkdir`)}async function E(e,n,r){if(e.discardWorkdirChanges)return e.discardWorkdirChanges(n,r);if(e.exec){let t=[`checkout`,`--`,...n?.length?n:[`.`]];await e.exec(t,{signal:r?.signal});return}return t(`discardWorkdirChanges`)}async function D(e,n){if(e.stashPush)return e.stashPush(n);if(e.exec){let t=[`stash`,`push`];n?.includeUntracked&&t.push(`--include-untracked`),n?.message&&t.push(`-m`,n.message),await e.exec(t,{signal:n?.signal});return}return t(`stashPush`)}async function O(e,n){if(e.stashPop)return e.stashPop(n);if(e.exec){await e.exec([`stash`,`pop`],{signal:n?.signal});return}return t(`stashPop`)}async function k(e,n,r){if(e.cherryPick)return e.cherryPick(n,r);if(e.exec){await e.exec([`cherry-pick`,n],{signal:r?.signal});return}return t(`cherryPick`)}async function A(e,n){if(e.cherryPickAbort)return e.cherryPickAbort(n);if(e.exec){await e.exec([`cherry-pick`,`--abort`],{signal:n?.signal});return}return t(`cherryPickAbort`)}export{v as applyPatchToIndex,C as checkout,k as cherryPick,A as cherryPickAbort,T as cleanWorkdir,_ as commitTree,b as createBranch,a as currentBranch,x as deleteBranch,s as diffTree,E as discardWorkdirChanges,l as forEachRef,g as getConfig,i as gitDir,m as listUntracked,c as log,o as mergeBase,y as mergeTree,d as readTree,w as reset,n as revParse,r as revParseVerify,h as showFile,f as stageAll,O as stashPop,D as stashPush,p as statusPorcelain,S as updateRef,u as writeTree};
1
+ import{ComposeGitPortMissingOpError as e}from"../ports/git.js";function t(t,n){throw new e(t,n)}async function n(e,n,r){return e.revParse?e.revParse(n,r):e.exec?(await e.exec([`rev-parse`,n])).trim():t(`revParse`)}async function r(e,n,r){if(e.revParseVerify)return e.revParseVerify(n,r);if(e.exec)try{return(await e.exec([`rev-parse`,`--verify`,n])).trim()}catch{return null}return t(`revParseVerify`)}async function i(e,n){return e.gitDir?e.gitDir(n):e.exec?(await e.exec([`rev-parse`,`--absolute-git-dir`])).trim():t(`gitDir`)}async function a(e,n){if(e.currentBranch)return e.currentBranch(n);if(e.exec)try{return(await e.exec([`symbolic-ref`,`--short`,`HEAD`])).trim()||null}catch{return null}return t(`currentBranch`)}async function o(e,n,r,i){return e.mergeBase?e.mergeBase(n,r,i):e.exec?(await e.exec([`merge-base`,n,r])).trim():t(`mergeBase`)}async function s(e,n,r,i){if(e.diffTree)return e.diffTree(n,r,i);if(e.exec){let t=[`diff`];return i?.binary!==!1&&t.push(`--binary`),i?.nameOnly&&t.push(`--name-only`),t.push(n,r),e.exec(t)}return t(`diffTree`)}async function c(e,n){if(e.log)return e.log(n);if(e.exec){let t=[`log`,`--format=${n.format}`];return n.noMerges&&t.push(`--no-merges`),n.reverse&&t.push(`--reverse`),n.limit!=null&&t.push(`-n`,String(n.limit)),n.author&&t.push(`--author=${n.author}`),n.sha?t.push(`-1`,n.sha):n.range&&t.push(n.range),e.exec(t)}return t(`log`)}async function l(e,n){if(e.forEachRef)return e.forEachRef(n);if(e.exec){let t=[`for-each-ref`,`--format=${n.format}`];return n.sort&&t.push(`--sort=${n.sort}`),n.prefix&&t.push(n.prefix),n.limit!=null&&t.push(`--count=${n.limit}`),e.exec(t)}return t(`forEachRef`)}async function u(e,n){return e.writeTree?e.writeTree(n):e.exec?(await e.exec([`write-tree`],{env:n?.indexEnv})).trim():t(`writeTree`)}async function d(e,n,r){if(e.readTree)return e.readTree(n,r);if(e.exec){await e.exec([`read-tree`,n],{env:r?.indexEnv});return}return t(`readTree`)}async function f(e,n){if(e.stageAll)return e.stageAll(n);if(e.exec){let t=[`add`];n?.intentToAdd?t.push(`-N`):n?.trackedOnly?t.push(`-u`):n?.paths||t.push(`-A`),n?.paths&&t.push(`--`,...n.paths),await e.exec(t,{env:n?.indexEnv});return}return t(`stageAll`)}async function p(e,n){return e.statusPorcelain?e.statusPorcelain(n):e.exec?e.exec([`status`,`--porcelain`]):t(`statusPorcelain`)}async function m(e,n){return e.listUntracked?e.listUntracked(n):e.exec?(await e.exec([`ls-files`,`--others`,`--exclude-standard`])).trim().split(`
2
+ `).filter(Boolean):t(`listUntracked`)}async function h(e,n,r,i){return e.showFile?e.showFile(n,r,i):e.exec?e.exec([`show`,`${n}:${r}`]):t(`showFile`)}async function g(e,n,r){if(e.getConfig)return e.getConfig(n,r);if(e.exec)try{return(await e.exec([`config`,n])).trim()||null}catch{return null}return t(`getConfig`)}async function _(e,n,r,i,a){if(e.commitTree)return e.commitTree(n,r,i,a);if(e.exec){let t=[`commit-tree`,n];for(let e of r)t.push(`-p`,e);t.push(`-m`,i),a?.signing?.enabled&&t.push(a.signing.signingKey?`-S${a.signing.signingKey}`:`-S`);let o={};return a?.author&&(o.GIT_AUTHOR_NAME=a.author.name,o.GIT_AUTHOR_EMAIL=a.author.email,o.GIT_COMMITTER_NAME=a.author.name,o.GIT_COMMITTER_EMAIL=a.author.email,a.author.date&&(o.GIT_AUTHOR_DATE=a.author.date.toISOString())),a?.signing?.gpgProgram&&(o.GIT_GPG_PROGRAM=a.signing.gpgProgram),(await e.exec(t,{env:Object.keys(o).length?o:void 0})).trim()}return t(`commitTree`)}async function v(e,n,r){if(e.applyPatchToIndex)return e.applyPatchToIndex(n,r);if(e.exec){let t=[`apply`,`--cached`];r?.allowBinaryReplacement!==!1&&t.push(`--allow-binary-replacement`),await e.exec(t,{env:r?.indexEnv,stdin:n});return}return t(`applyPatchToIndex`)}async function y(e,n,r){if(e.applyPatchToWorkdir)return e.applyPatchToWorkdir(n,r);if(e.exec){let t=[`apply`];r?.allowBinaryReplacement!==!1&&t.push(`--allow-binary-replacement`),await e.exec(t,{stdin:n});return}return t(`applyPatchToWorkdir`)}async function b(e,n,r,i){return e.mergeTree?e.mergeTree(n,r,i):e.exec?(await e.exec([`merge-tree`,`--write-tree`,n,r])).trim().split(`
3
+ `)[0]:t(`mergeTree`)}async function x(e,n,r,i){if(e.createBranch)return e.createBranch(n,r,i);if(e.exec){await e.exec([`branch`,n,r]);return}return t(`createBranch`)}async function S(e,n,r){if(e.deleteBranch)return e.deleteBranch(n,r);if(e.exec){await e.exec([`branch`,r?.force?`-D`:`-d`,n]);return}return t(`deleteBranch`)}async function C(e,n,r,i){if(e.updateRef)return e.updateRef(n,r,i);if(e.exec){if(i?.remove){await e.exec([`update-ref`,`-d`,n]);return}let t=[`update-ref`,n,r];i?.previousSha&&t.push(i.previousSha),await e.exec(t);return}return t(`updateRef`)}async function w(e,n,r){if(e.checkout)return e.checkout(n,r);if(e.exec){let t=[`checkout`];r?.createBranch&&t.push(`-b`),r?.detach&&t.push(`--detach`),t.push(n),r?.createBranch&&r?.startPoint&&t.push(r.startPoint),await e.exec(t);return}return t(`checkout`)}async function T(e,n,r,i){if(e.reset)return e.reset(n,r,i);if(e.exec){await e.exec([`reset`,`--${r}`,n]);return}return t(`reset`)}async function E(e,n){if(e.cleanWorkdir)return e.cleanWorkdir(n);if(e.exec){let t=`-`;n?.force!==!1&&(t+=`f`),n?.directories!==!1&&(t+=`d`),t===`-`&&(t=`-fd`),await e.exec([`clean`,t]);return}return t(`cleanWorkdir`)}async function D(e,n,r){if(e.discardWorkdirChanges)return e.discardWorkdirChanges(n,r);if(e.exec){let t=[`checkout`,`--`,...n?.length?n:[`.`]];await e.exec(t);return}return t(`discardWorkdirChanges`)}async function O(e,n){if(e.stashPush)return e.stashPush(n);if(e.exec){let t=[`stash`,`push`];n?.includeUntracked&&t.push(`--include-untracked`),n?.message&&t.push(`-m`,n.message),await e.exec(t);return}return t(`stashPush`)}const k={LANG:`C`,LC_ALL:`C`};async function A(e,n){if(e.stashPop)return e.stashPop(n);if(e.exec){let t=[`stash`,`pop`];n?.withIndex&&t.push(`--index`),n?.ref&&t.push(n.ref),await e.exec(t,{env:k});return}return t(`stashPop`)}async function j(e,n,r){if(e.stashDrop)return e.stashDrop(n,r);if(e.exec){await e.exec([`stash`,`drop`,n]);return}return t(`stashDrop`)}async function M(e,n,r){if(e.cherryPick)return e.cherryPick(n,r);if(e.exec){await e.exec([`cherry-pick`,n]);return}return t(`cherryPick`)}async function N(e,n){if(e.cherryPickAbort)return e.cherryPickAbort(n);if(e.exec){await e.exec([`cherry-pick`,`--abort`]);return}return t(`cherryPickAbort`)}export{v as applyPatchToIndex,y as applyPatchToWorkdir,w as checkout,M as cherryPick,N as cherryPickAbort,E as cleanWorkdir,_ as commitTree,x as createBranch,a as currentBranch,S as deleteBranch,s as diffTree,D as discardWorkdirChanges,l as forEachRef,g as getConfig,i as gitDir,m as listUntracked,k as localeNeutralEnv,c as log,o as mergeBase,b as mergeTree,d as readTree,T as reset,n as revParse,r as revParseVerify,h as showFile,f as stageAll,j as stashDrop,A as stashPop,O as stashPush,p as statusPorcelain,C as updateRef,u as writeTree};
@@ -1 +1 @@
1
- import{WorkflowError as e}from"../errors/workflow.js";function t(t,n){if(!t.exec)throw new e(`INTERNAL`,`${n} requires 'exec' on the ComposeGitPort. Provide an exec implementation, or upgrade to a library version that routes this via high-level ops.`);return{exec:t.exec}}export{t as requireExec};
1
+ import{ComposeWorkflowError as e}from"../errors/workflow.js";function t(t,n){if(!t.exec)throw new e(`INTERNAL`,`${n} requires 'exec' on the ComposeGitPort. Provide an exec implementation, or upgrade to a library version that routes this via high-level ops.`);return{exec:t.exec}}export{t as requireExec};
@@ -3,18 +3,18 @@ import { SafetySnapshot } from "./types.js";
3
3
 
4
4
  //#region src/git/safety.d.ts
5
5
  declare function verifySnapshot(git: ComposeGitPort, snapshot: SafetySnapshot, options: {
6
+ includeStaged?: boolean;
6
7
  includeUnstaged: boolean;
7
8
  includeUntracked: boolean;
8
- signal?: AbortSignal;
9
9
  }): Promise<{
10
10
  valid: boolean;
11
11
  error?: string;
12
12
  }>;
13
- declare function verifyResultingDiff(git: ComposeGitPort, baseSha: string, newHeadSha: string, snapshot: SafetySnapshot, signal?: AbortSignal): Promise<{
13
+ declare function verifyResultingDiff(git: ComposeGitPort, baseSha: string, newHeadSha: string, snapshot: SafetySnapshot): Promise<{
14
14
  valid: boolean;
15
15
  error?: string;
16
16
  }>;
17
- declare function verifyMultiBranchDiff(git: ComposeGitPort, baseSha: string, branchHeadShas: string[], snapshot: SafetySnapshot, signal?: AbortSignal): Promise<{
17
+ declare function verifyMultiBranchDiff(git: ComposeGitPort, baseSha: string, branchHeadShas: string[], snapshot: SafetySnapshot): Promise<{
18
18
  valid: boolean;
19
19
  error?: string;
20
20
  }>;
@@ -1 +1 @@
1
- import{commitTree as e,diffTree as t,gitDir as n,mergeTree as r,revParse as i,stageAll as a,writeTree as o}from"./port-dispatch.js";import{sha256 as s}from"./utils/crypto.js";import{withUntrackedInTempIndex as c}from"./utils/temp-index.js";import{collectSourceHashes as l}from"./diff-collector.js";import{randomUUID as u}from"node:crypto";import{copyFileSync as d,unlinkSync as f}from"node:fs";import{tmpdir as p}from"node:os";import{join as m}from"node:path";async function h(e,t,n){let{signal:r}=n;return r?.throwIfAborted(),t.branchSha&&t.baseSha?y(e,t,r):g(e,t,n)}async function g(e,n,r){let{signal:l}=r,u=await i(e,`HEAD`,{signal:l});if(u!==n.headSha)return{valid:!1,error:`HEAD changed from ${n.headSha} to ${u}`};let d;if(d=r.includeUntracked?await c(e,async n=>(r.includeUnstaged&&await a(e,{trackedOnly:!0,indexEnv:n,signal:l}),await a(e,{indexEnv:n,signal:l}),t(e,u,await o(e,{indexEnv:n,signal:l}),{signal:l})),l):r.includeUnstaged?await v(e,u,l):await t(e,u,await o(e,{signal:l}),{signal:l}),s(d)!==n.diffHash){let t=await _(e,n,r);return t.length>0?{valid:!1,error:`Working directory ${t.join(` and `)} ${t.length===1?`has`:`have`} changed since the AI analysis`}:{valid:!1,error:`Working directory changes have been modified since the AI analysis`}}return{valid:!0}}async function _(e,t,n){if(!t.sourceHashes)return[];let r=await l(e,{includeUnstaged:n.includeUnstaged,includeUntracked:n.includeUntracked},n.signal);if(!r)return[];let i=[];return t.sourceHashes.staged!=null&&r.staged!==t.sourceHashes.staged&&i.push(`staged changes`),t.sourceHashes.unstaged!=null&&r.unstaged!==t.sourceHashes.unstaged&&i.push(`unstaged changes`),t.sourceHashes.untracked!=null&&r.untracked!==t.sourceHashes.untracked&&i.push(`untracked files`),i}async function v(e,r,i){let s=m(await n(e,{signal:i}),`index`),c=m(p(),`compose-verify-idx-${u()}`);try{d(s,c);let n={GIT_INDEX_FILE:c};return await a(e,{trackedOnly:!0,indexEnv:n,signal:i}),t(e,r,await o(e,{indexEnv:n,signal:i}),{signal:i})}finally{try{f(c)}catch{}}}async function y(e,n,r){return await i(e,n.branchSha,{signal:r})===n.branchSha?s(await t(e,n.baseSha,n.branchSha,{signal:r}))===n.diffHash?{valid:!0}:{valid:!1,error:`Branch diff has changed since the AI analysis`}:{valid:!1,error:`Branch has been modified since the AI analysis`}}async function b(e,n,r,i,a){return s(await t(e,n,r,{signal:a}))===i.diffHash?{valid:!0}:{valid:!1,error:`Output diff does not match input — the commit chain does not reproduce the original changes`}}async function x(n,i,a,o,c){if(a.length===1)return b(n,i,a[0],o,c);let l=a[0];for(let t=1;t<a.length;t++)c?.throwIfAborted(),l=await e(n,await r(n,l,a[t],{signal:c}),[l,a[t]],`verify merge`,{signal:c});return s(await t(n,i,l,{signal:c}))===o.diffHash?{valid:!0}:{valid:!1,error:`Combined diff of all branches does not match the original changes`}}export{x as verifyMultiBranchDiff,b as verifyResultingDiff,h as verifySnapshot};
1
+ import{commitTree as e,diffTree as t,gitDir as n,mergeTree as r,revParse as i,stageAll as a,writeTree as o}from"./port-dispatch.js";import{sha256 as s}from"./utils/crypto.js";import{withUntrackedInTempIndex as c}from"./utils/temp-index.js";import{collectSourceHashes as l,collectWorkdirPortionForRange as u}from"./diff-collector.js";import{randomUUID as d}from"node:crypto";import{copyFileSync as f,unlinkSync as p}from"node:fs";import{tmpdir as m}from"node:os";import{join as h}from"node:path";async function g(e,t,n){return t.branchSha&&t.baseSha?(n.includeStaged??!1)||n.includeUnstaged||n.includeUntracked?v(e,t,n):x(e,t):_(e,t,n)}async function _(e,n,r){let l=await i(e,`HEAD`);if(l!==n.headSha)return{valid:!1,error:`HEAD changed from ${n.headSha} to ${l}`};let u;if(u=r.includeUntracked?await c(e,async n=>(r.includeUnstaged&&await a(e,{trackedOnly:!0,indexEnv:n}),await a(e,{indexEnv:n}),t(e,l,await o(e,{indexEnv:n})))):r.includeUnstaged?await b(e,l):await t(e,l,await o(e)),s(u)!==n.diffHash){let t=await y(e,n,r);return t.length>0?{valid:!1,error:`Working directory ${t.join(` and `)} ${t.length===1?`has`:`have`} changed since the AI analysis`}:n.sourceHashes==null?{valid:!1,error:`Working directory changes have been modified since the AI analysis`}:{valid:!0}}return{valid:!0}}async function v(e,n,r){if(await i(e,n.branchSha)!==n.branchSha)return{valid:!1,error:`Branch has been modified since the AI analysis`};let a=await i(e,`HEAD`);if(a!==n.headSha)return{valid:!1,error:`HEAD changed from ${n.headSha} to ${a}`};let{finalTreeSha:o}=await u(e,a,{includeStaged:r.includeStaged??!0,includeUnstaged:r.includeUnstaged,includeUntracked:r.includeUntracked});if(s(await t(e,n.baseSha,o))!==n.diffHash){let t=await y(e,n,r);return t.length>0?{valid:!1,error:`Working directory ${t.join(` and `)} ${t.length===1?`has`:`have`} changed since the AI analysis`}:n.sourceHashes==null?{valid:!1,error:`Working directory or branch state has drifted since the AI analysis`}:{valid:!0}}return{valid:!0}}async function y(e,t,n){if(!t.sourceHashes)return[];let r=await l(e,{includeStaged:n.includeStaged,includeUnstaged:n.includeUnstaged,includeUntracked:n.includeUntracked});if(!r)return[];let i=[];return t.sourceHashes.staged!=null&&r.staged!==t.sourceHashes.staged&&i.push(`staged changes`),t.sourceHashes.unstaged!=null&&r.unstaged!==t.sourceHashes.unstaged&&i.push(`unstaged changes`),t.sourceHashes.untracked!=null&&r.untracked!==t.sourceHashes.untracked&&i.push(`untracked files`),i}async function b(e,r){let i=h(await n(e),`index`),s=h(m(),`compose-verify-idx-${d()}`);try{f(i,s);let n={GIT_INDEX_FILE:s};return await a(e,{trackedOnly:!0,indexEnv:n}),t(e,r,await o(e,{indexEnv:n}))}finally{try{p(s)}catch{}}}async function x(e,n){return await i(e,n.branchSha)===n.branchSha?s(await t(e,n.baseSha,n.branchSha))===n.diffHash?{valid:!0}:{valid:!1,error:`Branch diff has changed since the AI analysis`}:{valid:!1,error:`Branch has been modified since the AI analysis`}}async function S(e,n,r,i){return s(await t(e,n,r))===i.diffHash?{valid:!0}:{valid:!1,error:`Output diff does not match input — the commit chain does not reproduce the original changes`}}async function C(n,i,a,o){if(a.length===1)return S(n,i,a[0],o);let c=a[0];for(let t=1;t<a.length;t++)c=await e(n,await r(n,c,a[t]),[c,a[t]],`verify merge`);return s(await t(n,i,c))===o.diffHash?{valid:!0}:{valid:!1,error:`Combined diff of all branches does not match the original changes`}}export{C as verifyMultiBranchDiff,S as verifyResultingDiff,g as verifySnapshot};
@@ -39,5 +39,10 @@ interface ApplyMutations {
39
39
  newSha: string;
40
40
  }[];
41
41
  }
42
+ /** Reported when the apply path's stash pop hit conflicts. The apply itself succeeded. */
43
+ interface StashConflict {
44
+ stashLabel: string;
45
+ conflictedFiles: string[];
46
+ }
42
47
  //#endregion
43
- export { ApplyMutations, SafetySnapshot };
48
+ export { ApplyMutations, SafetySnapshot, StashConflict };
@@ -26,7 +26,6 @@ interface ExtractConventionsOptions {
26
26
  author?: string;
27
27
  commitLimit?: number;
28
28
  branchLimit?: number;
29
- signal?: AbortSignal;
30
29
  }
31
30
  declare function extractConventions(options: ExtractConventionsOptions): Promise<ConventionDescriptor>;
32
31
  //#endregion
@@ -1,10 +1,10 @@
1
- import{forEachRef as e,getConfig as t,log as n,showFile as r}from"../port-dispatch.js";const i=/^(feat|fix|refactor|chore|docs|style|test|perf|ci|build|revert)(\(.+?\))?!?:\s/,a=[{re:/\(#\d+\)/,label:`(#NNN)`},{re:/#\d+/,label:`#NNN`},{re:/[A-Z]{2,}-\d+/,label:`PROJ-NNN`}],o=/^(feat|fix|chore|feature|hotfix|bugfix|refactor|release|support)\//,s=/^(\d+)-|^([A-Z]{2,}-\d+)-/,c=new Set([`main`,`master`,`develop`,`development`,`staging`,`production`]);async function l(e){let{git:t,commitLimit:n=30,branchLimit:r=15,signal:i}=e;i?.throwIfAborted();let a=[],o=e.author??await T(t,i),[s,c,l]=await Promise.all([u(t,a,i),f(t,n,o,i),p(t,r,i)]),d=m(c,s),g=h(l);return{commits:d,branches:g,sources:a,promptFragment:b(d,g)}}async function u(e,t,n){let r={hasConventionalCommitsDep:!1,hasCommitizen:!1},i=await C(e,`package.json`,n);if(i)try{let e=JSON.parse(i),n={...e.dependencies??{},...e.devDependencies??{}};n[`@commitlint/config-conventional`]&&(r.hasConventionalCommitsDep=!0,t.push(`package.json (@commitlint/config-conventional)`)),(e.config?.commitizen||n.commitizen)&&(r.hasCommitizen=!0,t.push(`package.json (commitizen)`))}catch{}for(let i of[`.commitlintrc.json`,`.commitlintrc.yml`,`.commitlintrc.yaml`]){let a=await C(e,i,n);if(a){t.push(i);let e=d(a);e.length>0&&(r.commitlintTypes=e);break}}for(let i of[`commitlint.config.js`,`commitlint.config.cjs`,`commitlint.config.mjs`])if(await w(e,i,n)){t.push(i),r.commitlintTypes||(r.hasConventionalCommitsDep=!0);break}let a=await C(e,`.gitmessage`,n);return a&&(r.gitMessageTemplate=a.trim(),t.push(`.gitmessage`)),r}function d(e){try{let t=JSON.parse(e)?.rules?.[`type-enum`];if(Array.isArray(t)){let e=t.find(e=>Array.isArray(e));if(Array.isArray(e))return e.filter(e=>typeof e==`string`)}}catch{let t=e.match(/type-enum[^[]*\[([^\]]+)\]/);if(t)return t[1].split(`,`).map(e=>e.trim().replace(/['"]/g,``)).filter(Boolean)}return[]}async function f(e,t,r,i){try{let a=(await n(e,{format:`%s`,noMerges:!0,limit:t,author:r,signal:i})).trim().split(`
2
- `).filter(Boolean);return r&&a.length<5?(await n(e,{format:`%s`,noMerges:!0,limit:t,signal:i})).trim().split(`
3
- `).filter(Boolean):a}catch{return[]}}async function p(t,n,r){try{return(await e(t,{format:`%(refname:short)`,prefix:`refs/heads/`,sort:`-committerdate`,limit:n+5,signal:r})).trim().split(`
1
+ import{forEachRef as e,getConfig as t,log as n,showFile as r}from"../port-dispatch.js";const i=/^(feat|fix|refactor|chore|docs|style|test|perf|ci|build|revert)(\(.+?\))?!?:\s/,a=[{re:/\(#\d+\)/,label:`(#NNN)`},{re:/#\d+/,label:`#NNN`},{re:/[A-Z]{2,}-\d+/,label:`PROJ-NNN`}],o=/^(feat|fix|chore|feature|hotfix|bugfix|refactor|release|support)\//,s=/^(\d+)-|^([A-Z]{2,}-\d+)-/,c=new Set([`main`,`master`,`develop`,`development`,`staging`,`production`]);async function l(e){let{git:t,commitLimit:n=30,branchLimit:r=15}=e,i=[],a=e.author??await T(t),[o,s,c]=await Promise.all([u(t,i),f(t,n,a),p(t,r)]),l=m(s,o),d=h(c);return{commits:l,branches:d,sources:i,promptFragment:b(l,d)}}async function u(e,t){let n={hasConventionalCommitsDep:!1,hasCommitizen:!1},r=await C(e,`package.json`);if(r)try{let e=JSON.parse(r),i={...e.dependencies??{},...e.devDependencies??{}};i[`@commitlint/config-conventional`]&&(n.hasConventionalCommitsDep=!0,t.push(`package.json (@commitlint/config-conventional)`)),(e.config?.commitizen||i.commitizen)&&(n.hasCommitizen=!0,t.push(`package.json (commitizen)`))}catch{}for(let r of[`.commitlintrc.json`,`.commitlintrc.yml`,`.commitlintrc.yaml`]){let i=await C(e,r);if(i){t.push(r);let e=d(i);e.length>0&&(n.commitlintTypes=e);break}}for(let r of[`commitlint.config.js`,`commitlint.config.cjs`,`commitlint.config.mjs`])if(await w(e,r)){t.push(r),n.commitlintTypes||(n.hasConventionalCommitsDep=!0);break}let i=await C(e,`.gitmessage`);return i&&(n.gitMessageTemplate=i.trim(),t.push(`.gitmessage`)),n}function d(e){try{let t=JSON.parse(e)?.rules?.[`type-enum`];if(Array.isArray(t)){let e=t.find(e=>Array.isArray(e));if(Array.isArray(e))return e.filter(e=>typeof e==`string`)}}catch{let t=e.match(/type-enum[^[]*\[([^\]]+)\]/);if(t)return t[1].split(`,`).map(e=>e.trim().replace(/['"]/g,``)).filter(Boolean)}return[]}async function f(e,t,r){try{let i=(await n(e,{format:`%s`,noMerges:!0,limit:t,author:r})).trim().split(`
2
+ `).filter(Boolean);return r&&i.length<5?(await n(e,{format:`%s`,noMerges:!0,limit:t})).trim().split(`
3
+ `).filter(Boolean):i}catch{return[]}}async function p(t,n){try{return(await e(t,{format:`%(refname:short)`,prefix:`refs/heads/`,sort:`-committerdate`,limit:n+5})).trim().split(`
4
4
  `).filter(Boolean).filter(e=>!c.has(e)&&!e.startsWith(`release/`)).slice(0,n)}catch{return[]}}function m(e,t){if(e.length===0)return{format:t.hasConventionalCommitsDep||t.hasCommitizen?`conventional-commits`:`unknown`,prefixes:t.commitlintTypes,samples:[]};let n=e.filter(e=>i.test(e)),r=n.length/e.length>.6||t.hasConventionalCommitsDep||t.hasCommitizen,a,o;if(r){let e=new Set,r=0;for(let t of n){let n=t.match(i);n&&(e.add(n[1]),n[2]&&r++)}a=t.commitlintTypes??[...e].sort(),o=r/Math.max(n.length,1)>.3?`parenthesized`:`none`}let s=g(e,r),c=_(e),l=v(e,r?a:void 0);return{format:r?`conventional-commits`:`plain`,prefixes:a,scopeStyle:o,casing:s,issueRefPattern:c,samples:l}}function h(e){if(e.length===0)return{format:`unknown`,samples:[]};let t=e.filter(e=>o.test(e)),n=e.filter(e=>s.test(e));if(t.length/e.length>.5){let n=new Set;for(let e of t){let t=e.match(o);t&&n.add(t[1])}return{format:`prefixed`,prefixes:[...n].sort(),separator:`/`,samples:y(e,[...n])}}return n.length/e.length>.5?{format:`issue-number`,separator:`-`,samples:e.slice(0,5)}:{format:e.length>=3?`plain`:`unknown`,samples:e.slice(0,5)}}function g(e,t){let n=0,r=0;for(let a of e){let e=(t?a.replace(i,``):a).trim()[0];e&&(e===e.toUpperCase()&&e!==e.toLowerCase()?r++:n++)}if(!(n+r<3)){if(n/(n+r)>.7)return`lowercase`;if(r/(n+r)>.7)return`capitalized`}}function _(e){for(let{re:t,label:n}of a)if(e.filter(e=>t.test(e)).length/e.length>.2)return n}function v(e,t){if(!t||t.length===0)return e.slice(0,5);let n=[],r=new Set;for(let i of t){let t=e.find(e=>e.startsWith(`${i}(`)||e.startsWith(`${i}:`));if(t&&!r.has(t)&&(n.push(t),r.add(t),n.length>=5))break}for(let t of e){if(n.length>=5)break;r.has(t)||(n.push(t),r.add(t))}return n}function y(e,t){let n=[],r=new Set;for(let i of t){let t=e.find(e=>e.startsWith(`${i}/`));if(t&&!r.has(t)&&(n.push(t),r.add(t),n.length>=5))break}for(let t of e){if(n.length>=5)break;r.has(t)||(n.push(t),r.add(t))}return n}function b(e,t){let n=[`## Repository conventions
5
5
  `];return n.push(x(e)),n.push(S(t)),n.push(`Match these conventions in your output.`),n.join(`
6
6
  `)}function x(e){if(e.format===`unknown`&&e.samples.length===0)return`Commit messages: No clear convention detected. Use clear, concise messages.
7
7
  `;let t=[];if(e.format===`conventional-commits`){let n=e.scopeStyle===`parenthesized`?` with scope in parentheses`:``;t.push(`Commit messages follow Conventional Commits format${n}: type${e.scopeStyle===`parenthesized`?`(scope)`:``}: description`),e.prefixes?.length&&t.push(`Allowed types: ${e.prefixes.join(`, `)}`)}else t.push(`Commit messages use plain English format.`);if(e.casing&&t.push(`${e.casing===`capitalized`?`Capitalized`:`Lowercase`} first word after any prefix.`),e.issueRefPattern&&t.push(`Issues often referenced as ${e.issueRefPattern}.`),e.samples.length>0){t.push(`Examples:`);for(let n of e.samples)t.push(`- ${n}`)}return`${t.join(`
8
8
  `)}\n`}function S(e){if(e.format===`unknown`&&e.samples.length===0)return`Branch names: No clear convention detected. Use descriptive kebab-case names.
9
9
  `;let t=[];if(e.format===`prefixed`){let n=e.separator??`/`;t.push(`Branch names use type${n}kebab-case-slug format.`),e.prefixes?.length&&t.push(`Common prefixes: ${e.prefixes.join(`, `)}`)}else e.format===`issue-number`?t.push(`Branch names start with an issue number followed by a kebab-case description.`):t.push(`Branch names use descriptive kebab-case format.`);if(e.samples.length>0){t.push(`Examples:`);for(let n of e.samples)t.push(`- ${n}`)}return`${t.join(`
10
- `)}\n`}async function C(e,t,n){try{return await r(e,`HEAD`,t,{signal:n})}catch{return null}}async function w(e,t,n){try{return await r(e,`HEAD`,t,{signal:n}),!0}catch{return!1}}async function T(e,n){let r=await t(e,`user.email`,{signal:n});if(r)return r;let i=await t(e,`user.name`,{signal:n});if(i)return i}export{l as extractConventions};
10
+ `)}\n`}async function C(e,t){try{return await r(e,`HEAD`,t)}catch{return null}}async function w(e,t){try{return await r(e,`HEAD`,t),!0}catch{return!1}}async function T(e){let n=await t(e,`user.email`);if(n)return n;let r=await t(e,`user.name`);if(r)return r}export{l as extractConventions};
@@ -1,6 +1,6 @@
1
1
  import { ComposeGitPort } from "../../ports/git.js";
2
2
 
3
3
  //#region src/git/utils/temp-index.d.ts
4
- declare function withUntrackedInTempIndex<T>(git: ComposeGitPort, fn: (indexEnv: Record<string, string>) => Promise<T>, signal?: AbortSignal): Promise<T>;
4
+ declare function withUntrackedInTempIndex<T>(git: ComposeGitPort, fn: (indexEnv: Record<string, string>) => Promise<T>): Promise<T>;
5
5
  //#endregion
6
6
  export { withUntrackedInTempIndex };
@@ -1 +1 @@
1
- import{ComposeGitPortMissingOpError as e}from"../../ports/git.js";import{gitDir as t,listUntracked as n,stageAll as r}from"../port-dispatch.js";import{randomUUID as i}from"node:crypto";import*as a from"node:fs";import*as o from"node:os";import*as s from"node:path";async function c(e,t,c){let u=await l(e,c),d=s.join(o.tmpdir(),`compose-tmp-idx-${i()}`);try{a.copyFileSync(u,d);let i={GIT_INDEX_FILE:d},o=await n(e,{signal:c});return o.length>0&&await r(e,{intentToAdd:!0,paths:o,indexEnv:i,signal:c}),await t(i)}finally{try{a.unlinkSync(d)}catch{}}}async function l(n,r){let i=await t(n,{signal:r});if(!i)throw new e(`gitDir`,`The temp-index helper needs to resolve the real .git/index path. Provide 'gitDir' (or 'exec').`);return s.join(i,`index`)}export{c as withUntrackedInTempIndex};
1
+ import{ComposeGitPortMissingOpError as e}from"../../ports/git.js";import{gitDir as t,listUntracked as n,stageAll as r}from"../port-dispatch.js";import{randomUUID as i}from"node:crypto";import*as a from"node:fs";import*as o from"node:os";import*as s from"node:path";async function c(e,t){let c=await l(e),u=s.join(o.tmpdir(),`compose-tmp-idx-${i()}`);try{a.copyFileSync(c,u);let i={GIT_INDEX_FILE:u},o=await n(e);return o.length>0&&await r(e,{intentToAdd:!0,paths:o,indexEnv:i}),await t(i)}finally{try{a.unlinkSync(u)}catch{}}}async function l(n){let r=await t(n);if(!r)throw new e(`gitDir`,`The temp-index helper needs to resolve the real .git/index path. Provide 'gitDir' (or 'exec').`);return s.join(r,`index`)}export{c as withUntrackedInTempIndex};
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { PhaseDetail, ProgressEvent, TokenUsage } from "./ai/types.js";
2
2
  import { AIError, AIErrorCode } from "./errors/ai.js";
3
3
  import { GitError, GitErrorCode } from "./errors/git.js";
4
4
  import { TaskInputError } from "./errors/task.js";
5
- import { WorkflowError, WorkflowErrorCode, WorkflowInputError } from "./errors/workflow.js";
5
+ import { ComposeWorkflowError, ComposeWorkflowErrorCode, ComposeWorkflowInputError } from "./errors/workflow.js";
6
6
  import { ApplyPatchOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitOps, ComposeGitPort, ComposeGitPortMissingOpError, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, GitCommitIdentity, IndexScopedOptions, LogOptions, OpOptions, SigningConfig, StageAllOptions, StashPushOptions, UpdateRefOptions } from "./ports/git.js";
7
7
  import { BranchGroup, BranchOrdering, BranchPartition, CommitDetail, CommitSuggestion, ComposeHunk, ComposeTaskName, PromptOverride } from "./tasks/types.js";
8
8
  import { ConventionDescriptor } from "./git/utils/conventions.js";
@@ -14,6 +14,7 @@ import { QuickComposeInput, QuickComposeOutput, QuickComposeResult } from "./tas
14
14
  import { ComposeSession } from "./tasks/session.js";
15
15
  import { PartitionInput, PartitionOutput, PartitionResult, PartitionStrategy } from "./tasks/partition/types.js";
16
16
  import { ComposeContext, ComposeSource, ComposeTarget } from "./workflows/types.js";
17
+ import { StashConflict } from "./git/types.js";
17
18
  import { ComposeBranchPlan } from "./git/apply-target.js";
18
19
  import { analyze } from "./tasks/analyze/analyze.js";
19
20
  import { ComposeGroupInput, ComposeGroupOutput } from "./tasks/compose-group/types.js";
@@ -22,7 +23,8 @@ import { group } from "./tasks/group/group.js";
22
23
  import { order } from "./tasks/order/order.js";
23
24
  import { partition } from "./tasks/partition/partition.js";
24
25
  import { quickCompose } from "./tasks/quick-compose/quick-compose.js";
25
- import { ApplyComposePlanInput, ApplyComposePlanResult, ComposeApplyPlan, ComposeDepth, ComposeInput, ComposePlan, ComposePlanInput, ComposePlanResult, ComposeProgressEvent, ComposeResult, ComposeStrategy, applyComposePlan, compose, composePlan, defaultPhaseMessages } from "./workflows/compose.js";
26
+ import { ApplyComposePlanInput, ApplyComposePlanResult, ApplyUpTo, ComposeApplyPlan, ComposeDepth, ComposeInput, ComposePlan, ComposePlanInput, ComposePlanResult, ComposeProgressEvent, ComposeResult, ComposeStrategy, applyComposePlan, compose, composePlan, defaultPhaseMessages } from "./workflows/compose.js";
26
27
  import { SplitInput, SplitPlan, SplitProgressEvent, SplitResult, split } from "./workflows/split.js";
27
28
  import { UndoComposeInput, ValidateUndoComposeInput, listUndoIds, undoCompose, validateUndoCompose } from "./workflows/undo.js";
28
- export { AIError, type AIErrorCode, type AfterModelCallInfo, type AnalysisResult, type AnalyzeInput, type AnalyzeOutput, type ApplyComposePlanInput, type ApplyComposePlanResult, type ApplyPatchOptions, type BeforeModelCallInfo, type BeforePromptInfo, type BranchGroup, type BranchOrdering, type BranchPartition, type CheckoutOptions, type CleanOptions, type CommitDetail, type CommitSuggestion, type CommitTreeOptions, type ComposeApplyPlan, type ComposeBranchPlan, type ComposeContext, type ComposeDepth, type ComposeGitOps, type ComposeGitPort, ComposeGitPortMissingOpError, type ComposeGroupInput, type ComposeGroupOutput, type ComposeHunk, type ComposeInput, type ComposePlan, type ComposePlanInput, type ComposePlanResult, type ComposeProgressEvent, type ComposeResult, type ComposeSession, type ComposeSource, type ComposeStrategy, type ComposeTarget, type ComposeTaskName, type Concern, type ConcernDependency, type ConventionDescriptor, type DeleteBranchOptions, type DiffTreeOptions, type ForEachRefOptions, type GitCommitIdentity, GitError, type GitErrorCode, type GroupInput, type GroupOutput, type GroupingResult, type GroupingStrategy, type HunkAnnotation, type HunkOrderingConstraint, type IndexScopedOptions, type LogOptions, type OnAfterModelCall, type OnBeforeModelCall, type OnBeforePrompt, type OpOptions, type OrderInput, type OrderOutput, type OrderingResult, type PartitionInput, type PartitionOutput, type PartitionResult, type PartitionStrategy, type PhaseDetail, type ProgressEvent, type PromptOverride, type QuickComposeInput, type QuickComposeOutput, type QuickComposeResult, type SigningConfig, type SplitInput, type SplitPlan, type SplitProgressEvent, type SplitResult, type StageAllOptions, type StashPushOptions, TaskInputError, type TokenUsage, type UndoComposeInput, type UpdateRefOptions, type ValidateUndoComposeInput, WorkflowError, type WorkflowErrorCode, WorkflowInputError, analyze, applyComposePlan, compose, composeGroup, composePlan, defaultPhaseMessages, estimatedCharactersPerToken, group, listUndoIds, order, partition, quickCompose, split, undoCompose, validateUndoCompose };
29
+ import { UndoForceOptions } from "@gitkraken/shared-tools";
30
+ export { AIError, type AIErrorCode, type AfterModelCallInfo, type AnalysisResult, type AnalyzeInput, type AnalyzeOutput, type ApplyComposePlanInput, type ApplyComposePlanResult, type ApplyPatchOptions, type ApplyUpTo, type BeforeModelCallInfo, type BeforePromptInfo, type BranchGroup, type BranchOrdering, type BranchPartition, type CheckoutOptions, type CleanOptions, type CommitDetail, type CommitSuggestion, type CommitTreeOptions, type ComposeApplyPlan, type ComposeBranchPlan, type ComposeContext, type ComposeDepth, type ComposeGitOps, type ComposeGitPort, ComposeGitPortMissingOpError, type ComposeGroupInput, type ComposeGroupOutput, type ComposeHunk, type ComposeInput, type ComposePlan, type ComposePlanInput, type ComposePlanResult, type ComposeProgressEvent, type ComposeResult, type ComposeSession, type ComposeSource, type ComposeStrategy, type ComposeTarget, type ComposeTaskName, ComposeWorkflowError, type ComposeWorkflowErrorCode, ComposeWorkflowInputError, type Concern, type ConcernDependency, type ConventionDescriptor, type DeleteBranchOptions, type DiffTreeOptions, type ForEachRefOptions, type GitCommitIdentity, GitError, type GitErrorCode, type GroupInput, type GroupOutput, type GroupingResult, type GroupingStrategy, type HunkAnnotation, type HunkOrderingConstraint, type IndexScopedOptions, type LogOptions, type OnAfterModelCall, type OnBeforeModelCall, type OnBeforePrompt, type OpOptions, type OrderInput, type OrderOutput, type OrderingResult, type PartitionInput, type PartitionOutput, type PartitionResult, type PartitionStrategy, type PhaseDetail, type ProgressEvent, type PromptOverride, type QuickComposeInput, type QuickComposeOutput, type QuickComposeResult, type SigningConfig, type SplitInput, type SplitPlan, type SplitProgressEvent, type SplitResult, type StageAllOptions, type StashConflict, type StashPushOptions, TaskInputError, type TokenUsage, type UndoComposeInput, type UndoForceOptions, type UpdateRefOptions, type ValidateUndoComposeInput, analyze, applyComposePlan, compose, composeGroup, composePlan, defaultPhaseMessages, estimatedCharactersPerToken, group, listUndoIds, order, partition, quickCompose, split, undoCompose, validateUndoCompose };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{WorkflowError as e,WorkflowInputError as t}from"./errors/workflow.js";import{AIError as n}from"./errors/ai.js";import{GitError as r}from"./errors/git.js";import{TaskInputError as i}from"./errors/task.js";import{ComposeGitPortMissingOpError as a}from"./ports/git.js";import{estimatedCharactersPerToken as o}from"./tasks/ai-loop.js";import{analyze as s}from"./tasks/analyze/analyze.js";import{composeGroup as c}from"./tasks/compose-group/compose-group.js";import{group as l}from"./tasks/group/group.js";import{order as u}from"./tasks/order/order.js";import{partition as d}from"./tasks/partition/partition.js";import{quickCompose as f}from"./tasks/quick-compose/quick-compose.js";import{applyComposePlan as p,compose as m,composePlan as h,defaultPhaseMessages as g}from"./workflows/compose.js";import{split as _}from"./workflows/split.js";import{listUndoIds as v,undoCompose as y,validateUndoCompose as b}from"./workflows/undo.js";export{n as AIError,a as ComposeGitPortMissingOpError,r as GitError,i as TaskInputError,e as WorkflowError,t as WorkflowInputError,s as analyze,p as applyComposePlan,m as compose,c as composeGroup,h as composePlan,g as defaultPhaseMessages,o as estimatedCharactersPerToken,l as group,v as listUndoIds,u as order,d as partition,f as quickCompose,_ as split,y as undoCompose,b as validateUndoCompose};
1
+ import{AIError as e}from"./errors/ai.js";import{GitError as t}from"./errors/git.js";import{TaskInputError as n}from"./errors/task.js";import{ComposeWorkflowError as r,ComposeWorkflowInputError as i}from"./errors/workflow.js";import{ComposeGitPortMissingOpError as a}from"./ports/git.js";import{estimatedCharactersPerToken as o}from"./tasks/ai-loop.js";import{analyze as s}from"./tasks/analyze/analyze.js";import{composeGroup as c}from"./tasks/compose-group/compose-group.js";import{group as l}from"./tasks/group/group.js";import{order as u}from"./tasks/order/order.js";import{partition as d}from"./tasks/partition/partition.js";import{quickCompose as f}from"./tasks/quick-compose/quick-compose.js";import{applyComposePlan as p,compose as m,composePlan as h,defaultPhaseMessages as g}from"./workflows/compose.js";import{split as _}from"./workflows/split.js";import{listUndoIds as v,undoCompose as y,validateUndoCompose as b}from"./workflows/undo.js";export{e as AIError,a as ComposeGitPortMissingOpError,r as ComposeWorkflowError,i as ComposeWorkflowInputError,t as GitError,n as TaskInputError,s as analyze,p as applyComposePlan,m as compose,c as composeGroup,h as composePlan,g as defaultPhaseMessages,o as estimatedCharactersPerToken,l as group,v as listUndoIds,u as order,d as partition,f as quickCompose,_ as split,y as undoCompose,b as validateUndoCompose};
@@ -52,6 +52,8 @@ interface ComposeGitOps {
52
52
  commitTree?: (tree: string, parents: string[], message: string, opts?: CommitTreeOptions) => Promise<string>;
53
53
  /** Apply a unified diff patch to the current (or temp) index. Covers `apply --cached --allow-binary-replacement`. */
54
54
  applyPatchToIndex?: (patch: string, opts?: ApplyPatchOptions) => Promise<void>;
55
+ /** Apply a unified diff patch to the working tree (no staging). Covers `apply --allow-binary-replacement`. */
56
+ applyPatchToWorkdir?: (patch: string, opts?: ApplyPatchToWorkdirOptions) => Promise<void>;
55
57
  /** 3-way merge producing a tree SHA. Covers `merge-tree --write-tree a b`. */
56
58
  mergeTree?: (a: string, b: string, opts?: OpOptions) => Promise<string>;
57
59
  /** Create a branch at `startPoint`. Covers `branch name start`. */
@@ -70,8 +72,10 @@ interface ComposeGitOps {
70
72
  discardWorkdirChanges?: (paths?: string[], opts?: OpOptions) => Promise<void>;
71
73
  /** Push current workdir state to stash. Covers `stash push --include-untracked -m msg`. */
72
74
  stashPush?: (opts?: StashPushOptions) => Promise<void>;
73
- /** Pop the top stash entry. Covers `stash pop`. */
74
- stashPop?: (opts?: OpOptions) => Promise<void>;
75
+ /** Pop the top stash entry. `withIndex: true` maps to `--index`. Covers `stash pop` and `stash pop --index`. */
76
+ stashPop?: (opts?: StashPopOptions) => Promise<void>;
77
+ /** Drop a specific stash entry. Covers `stash drop stash@{N}`. */
78
+ stashDrop?: (stashRef: string, opts?: OpOptions) => Promise<void>;
75
79
  /**
76
80
  * Apply an existing commit onto the current HEAD, producing a new commit.
77
81
  * Covers `git cherry-pick <sha>`. Throws when conflicts make the pick fail —
@@ -84,10 +88,16 @@ interface ComposeGitOps {
84
88
  /** Abort an in-progress cherry-pick. Covers `git cherry-pick --abort`. */
85
89
  cherryPickAbort?: (opts?: OpOptions) => Promise<void>;
86
90
  }
87
- /** Options passed to every op — all ops can be cancelled via signal. */
88
- interface OpOptions {
89
- signal?: AbortSignal;
90
- }
91
+ /**
92
+ * Options passed to every op. Currently empty — git operations are not
93
+ * cancellable in this library; they run to completion to avoid leaving the
94
+ * repo in a half-mutated state. Domain workflows route cancellation only
95
+ * into the AI loop.
96
+ *
97
+ * Kept as an interface so individual ops that need extra options (e.g.
98
+ * {@link CheckoutOptions}, {@link UpdateRefOptions}) can extend it.
99
+ */
100
+ interface OpOptions {}
91
101
  interface DiffTreeOptions extends OpOptions {
92
102
  /** Include binary hunks as base85. Default true. */
93
103
  binary?: boolean;
@@ -154,6 +164,10 @@ interface ApplyPatchOptions extends IndexScopedOptions {
154
164
  /** Allow binary hunks in the patch. Default true. */
155
165
  allowBinaryReplacement?: boolean;
156
166
  }
167
+ interface ApplyPatchToWorkdirOptions extends OpOptions {
168
+ /** Allow binary hunks in the patch. Default true. */
169
+ allowBinaryReplacement?: boolean;
170
+ }
157
171
  interface DeleteBranchOptions extends OpOptions {
158
172
  force?: boolean;
159
173
  }
@@ -179,6 +193,12 @@ interface StashPushOptions extends OpOptions {
179
193
  message?: string;
180
194
  includeUntracked?: boolean;
181
195
  }
196
+ interface StashPopOptions extends OpOptions {
197
+ /** Restore staged-vs-unstaged split where possible. Maps to `--index`. */
198
+ withIndex?: boolean;
199
+ /** Specific stash entry to pop (e.g. `stash@{2}`). Defaults to the topmost. */
200
+ ref?: string;
201
+ }
182
202
  /**
183
203
  * The full port shape the library accepts. Exec is optional — if absent,
184
204
  * every flow must be fully served by high-level ops.
@@ -196,4 +216,4 @@ declare class ComposeGitPortMissingOpError extends Error {
196
216
  constructor(op: string, hint?: string);
197
217
  }
198
218
  //#endregion
199
- export { ApplyPatchOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitOps, ComposeGitPort, ComposeGitPortMissingOpError, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, GitCommitIdentity, IndexScopedOptions, LogOptions, OpOptions, SigningConfig, StageAllOptions, StashPushOptions, UpdateRefOptions };
219
+ export { ApplyPatchOptions, ApplyPatchToWorkdirOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitOps, ComposeGitPort, ComposeGitPortMissingOpError, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, GitCommitIdentity, IndexScopedOptions, LogOptions, OpOptions, SigningConfig, StageAllOptions, StashPopOptions, StashPushOptions, UpdateRefOptions };
@@ -34,15 +34,14 @@ interface AfterModelCallInfo {
34
34
  type OnBeforeModelCall = (info: BeforeModelCallInfo) => void;
35
35
  type OnAfterModelCall = (info: AfterModelCallInfo) => void;
36
36
  /**
37
- * Gating hook fired immediately before each model.generate call.
37
+ * Gating hook fired immediately before each `model.generate` call. Returning
38
+ * `false` aborts the loop with a native `AbortError` (DOMException with
39
+ * `name === 'AbortError'`). Intended for cost warnings, large-prompt
40
+ * confirmations, policy checks, and similar caller-side UX.
38
41
  *
39
- * Returning `false` aborts the workflow; the library throws the resulting
40
- * rejection as a `WorkflowError('CANCELLED')`. Intended for cost warnings,
41
- * large-prompt confirmations, policy checks, and similar consumer-side UX.
42
- *
43
- * The library provides `tokenEstimate` as a rough `charCount / 2.8`
44
- * approximation — reasonable for code-heavy prompts but not authoritative.
45
- * Consumers can ignore it and estimate their own way.
42
+ * `tokenEstimate` is a rough `charCount / 2.8` approximation — reasonable for
43
+ * code-heavy prompts but not authoritative. Callers can ignore it and
44
+ * estimate their own way.
46
45
  */
47
46
  interface BeforePromptInfo {
48
47
  phase: string;
@@ -65,16 +64,11 @@ interface AiLoopParams<T> {
65
64
  onProgress?: (event: ProgressEvent) => void;
66
65
  validate: (responseText: string) => ValidationResult<T>;
67
66
  taskName: string;
68
- signal?: AbortSignal;
69
- /** Phase label stamped on Before/After model-call hook events. */
67
+ cancellation?: AbortSignal;
68
+ /** Phase label stamped on Before/After model-call hook events. Defaults to `taskName`. */
70
69
  phase?: string;
71
70
  onBeforeModelCall?: OnBeforeModelCall;
72
71
  onAfterModelCall?: OnAfterModelCall;
73
- /**
74
- * Gating hook. Called before each model.generate. If it returns `false`, the
75
- * loop aborts with an AbortError (caught by the workflow and surfaced as a
76
- * `WorkflowError('CANCELLED')`).
77
- */
78
72
  onBeforePrompt?: OnBeforePrompt;
79
73
  promptVersion?: string;
80
74
  }
@@ -1,2 +1,2 @@
1
- import{WorkflowError as e}from"../errors/workflow.js";import{AIError as t}from"../errors/ai.js";import{TaskInputError as n}from"../errors/task.js";import{appendAssistantMessage as r,appendUserMessage as i,createAiSession as a}from"../ai/session.js";import{addUsage as o}from"../ai/types.js";import{randomUUID as s}from"node:crypto";const c=2.8;async function l(o){let{model:c,userPrompt:l,maxRetries:f=3,maxTokens:p=16384,onProgress:m,validate:h,taskName:g,signal:_,phase:v=g,onBeforeModelCall:y,onAfterModelCall:b,promptVersion:x}=o;if(!o.session&&!o.systemPrompt)throw new n(`${g}: either 'session' or 'systemPrompt' must be provided`);let S=Date.now(),C;o.session?C=i(o.session,l):(C=a(o.systemPrompt),C=i(C,l));let w,T=[],E=f+1,D=[];for(let t=0;t<=f;t++){_?.throwIfAborted();let n=t+1;if(m?.({type:`attempt_start`,attempt:n,maxAttempts:E}),o.onBeforePrompt){let t=(C.systemPrompt?.length??0)+C.messages.reduce((e,t)=>e+t.content.length,0);if(!await o.onBeforePrompt({phase:v,attempt:n,maxAttempts:E,charCount:t,messageCount:C.messages.length,tokenEstimate:Math.ceil(t/2.8)}))throw new e(`CANCELLED`,`Prompt gate declined by consumer (onBeforePrompt returned false)`)}let a=s();D.push(a);let l=Date.now();y?.({phase:v,attempt:n,maxAttempts:E,callId:a});let g;try{g=await c.generate({system:C.systemPrompt,messages:[...C.messages],maxTokens:p,temperature:0,signal:_})}catch(t){let r=t instanceof Error?t.message:String(t);throw b?.({phase:v,attempt:n,callId:a,success:!1,durationMs:Date.now()-l,error:{code:t?.code??`UNKNOWN`,message:r},promptVersion:x}),u(t)||_?.aborted?new e(`CANCELLED`,r,{cause:t}):t}b?.({phase:v,attempt:n,callId:a,success:!0,durationMs:Date.now()-l,usage:g.usage,promptVersion:x}),w=d(w,g.usage);let O=h(g.text);if(O.valid&&O.result!==void 0)return C=r(C,g.text),{result:O.result,session:C,usage:w,attempts:n,durationMs:Date.now()-S,callIds:D};T=O.errors??[`Unknown validation error`],m?.({type:`validation_failed`,attempt:n,maxAttempts:E,errors:T}),t<f&&O.retryFeedback&&(C=r(C,g.text),C=i(C,O.retryFeedback))}throw new t(`VALIDATION_EXHAUSTED`,`Failed to get valid ${g} after ${E} attempts. Last errors:\n${T.join(`
2
- `)}`)}function u(e){return e instanceof Error&&(e.name===`AbortError`||e.name===`DOMException`)}function d(e,t){if(!e)return t;if(!t)return e;let n={...e};return o(n,t),n}function f(e){let t=e.match(/<output>([\s\S]*?)(?:<\/output>|$)/)?.[1]?.trim()??null;return t?t.match(/^```(?:\w*)\n?([\s\S]*?)```$/)?.[1]?.trim()??t:null}export{c as estimatedCharactersPerToken,f as extractOutputTag,l as runAiLoop};
1
+ import{AIError as e}from"../errors/ai.js";import{TaskInputError as t}from"../errors/task.js";import{appendAssistantMessage as n,appendUserMessage as r,createAiSession as i}from"../ai/session.js";import{addUsage as a}from"../ai/types.js";import{randomUUID as o}from"node:crypto";const s=2.8;async function c(a){let{model:s,userPrompt:c,maxRetries:d=3,maxTokens:f=16384,onProgress:p,validate:m,taskName:h,cancellation:g,phase:_=h,onBeforeModelCall:v,onAfterModelCall:y,promptVersion:b}=a;if(!a.session&&!a.systemPrompt)throw new t(`${h}: either 'session' or 'systemPrompt' must be provided`);let x=Date.now(),S;a.session?S=r(a.session,c):(S=i(a.systemPrompt),S=r(S,c));let C,w=[],T=d+1,E=[];for(let e=0;e<=d;e++){g?.throwIfAborted();let t=e+1;if(p?.({type:`attempt_start`,attempt:t,maxAttempts:T}),a.onBeforePrompt){let e=(S.systemPrompt?.length??0)+S.messages.reduce((e,t)=>e+t.content.length,0);if(!await a.onBeforePrompt({phase:_,attempt:t,maxAttempts:T,charCount:e,messageCount:S.messages.length,tokenEstimate:Math.ceil(e/2.8)}))throw new DOMException(`Prompt gate declined by caller (onBeforePrompt returned false)`,`AbortError`)}let i=o();E.push(i);let c=Date.now();v?.({phase:_,attempt:t,maxAttempts:T,callId:i});let h;try{h=await s.generate({system:S.systemPrompt,messages:[...S.messages],maxTokens:f,temperature:0,signal:g})}catch(e){let n=e instanceof Error?e.message:String(e);throw y?.({phase:_,attempt:t,callId:i,success:!1,durationMs:Date.now()-c,error:{code:e?.code??`UNKNOWN`,message:n},promptVersion:b}),l(e)||g?.aborted?new DOMException(n,`AbortError`):e}y?.({phase:_,attempt:t,callId:i,success:!0,durationMs:Date.now()-c,usage:h.usage,promptVersion:b}),C=u(C,h.usage);let D=m(h.text);if(D.valid&&D.result!==void 0)return S=n(S,h.text),{result:D.result,session:S,usage:C,attempts:t,durationMs:Date.now()-x,callIds:E};w=D.errors??[`Unknown validation error`],p?.({type:`validation_failed`,attempt:t,maxAttempts:T,errors:w}),e<d&&D.retryFeedback&&(S=n(S,h.text),S=r(S,D.retryFeedback))}throw new e(`VALIDATION_EXHAUSTED`,`Failed to get valid ${h} after ${T} attempts. Last errors:\n${w.join(`
2
+ `)}`)}function l(e){return e instanceof Error&&(e.name===`AbortError`||e.name===`DOMException`)}function u(e,t){if(!e)return t;if(!t)return e;let n={...e};return a(n,t),n}function d(e){let t=e.match(/<output>([\s\S]*?)(?:<\/output>|$)/)?.[1]?.trim()??null;return t?t.match(/^```(?:\w*)\n?([\s\S]*?)```$/)?.[1]?.trim()??t:null}export{s as estimatedCharactersPerToken,d as extractOutputTag,c as runAiLoop};
@@ -1 +1 @@
1
- import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{buildAnalyzeSystemPrompt as r,buildAnalyzeUserPrompt as i}from"./prompt.js";import{validateAnalyzeResponse as a}from"./validator.js";async function o(o){let s=o.hunks;if(!s||s.length===0)throw new e(`No hunks provided: supply 'hunks'`);if(o.session?.completedSteps.analyze)return{result:o.session.completedSteps.analyze,session:o.session,attempts:0,durationMs:0,callIds:[]};let c=n(i({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions}),o.promptOverride),l=s.length,u=await t({model:o.model,systemPrompt:o.session?void 0:o.promptOverride?.system??r(),userPrompt:c,session:o.session,maxRetries:o.maxRetries,onProgress:o.onProgress,signal:o.signal,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>a(e,l),taskName:`analyze`}),d=o.session?.completedSteps??{},f={...u.session,completedSteps:{...d,analyze:u.result}};return{result:u.result,session:f,usage:u.usage,attempts:u.attempts,durationMs:u.durationMs,callIds:u.callIds}}export{o as analyze};
1
+ import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{buildAnalyzeSystemPrompt as r,buildAnalyzeUserPrompt as i}from"./prompt.js";import{validateAnalyzeResponse as a}from"./validator.js";async function o(o){let s=o.hunks;if(!s||s.length===0)throw new e(`No hunks provided: supply 'hunks'`);if(o.session?.completedSteps.analyze)return{result:o.session.completedSteps.analyze,session:o.session,attempts:0,durationMs:0,callIds:[]};let c=n(i({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions}),o.promptOverride),l=s.length,u=await t({model:o.model,systemPrompt:o.session?void 0:o.promptOverride?.system??r(),userPrompt:c,session:o.session,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>a(e,l),taskName:`analyze`}),d=o.session?.completedSteps??{},f={...u.session,completedSteps:{...d,analyze:u.result}};return{result:u.result,session:f,usage:u.usage,attempts:u.attempts,durationMs:u.durationMs,callIds:u.callIds}}export{o as analyze};
@@ -17,8 +17,10 @@ interface AnalyzeInput {
17
17
  model: AiModelPort;
18
18
  session?: ComposeSession;
19
19
  maxRetries?: number;
20
+ /** Output-token cap forwarded to `runAiLoop`. Default 16384; bump for analyze on very large diffs where the structured output (per-hunk annotations) overflows the cap mid-JSON. */
21
+ maxTokens?: number;
20
22
  onProgress?: (event: ProgressEvent) => void;
21
- signal?: AbortSignal;
23
+ cancellation?: AbortSignal;
22
24
  onBeforeModelCall?: OnBeforeModelCall;
23
25
  onAfterModelCall?: OnAfterModelCall;
24
26
  onBeforePrompt?: OnBeforePrompt;
@@ -1,4 +1,4 @@
1
- import{extractOutputTag as e}from"../ai-loop.js";import{analysisResultSchema as t}from"./schema.js";function n(n,a){try{let o=e(n);if(!o)return r([`No <output> tag found in response`],i());let s=JSON.parse(o),c=t.safeParse(s);if(!c.success)return r(c.error.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`),i());let l=c.data,u=new Set(l.hunkAnnotations.map(e=>e.hunkIndex)),d=[...new Set(Array.from({length:a},(e,t)=>t))].filter(e=>!u.has(e));return d.length>0?r([`Missing annotations for hunks: ${d.join(`, `)}`],`Your response is missing annotations for some hunks. Every hunk must be annotated.\n\nMissing: ${d.join(`, `)}\n\nPlease provide a corrected response.`):{valid:!0,result:{title:l.title,summary:l.summary,concerns:l.concerns,dependencies:l.dependencies,hunkAnnotations:l.hunkAnnotations,orderingConstraints:l.orderingConstraints}}}catch{return r([`Failed to parse response as valid JSON`],i())}}function r(e,t){return{valid:!1,errors:e,retryFeedback:t}}function i(){return`Your response has an invalid structure. Please return JSON inside <output> tags with this structure:
1
+ import{extractOutputTag as e}from"../ai-loop.js";import{analysisResultSchema as t}from"./schema.js";function n(n,a){let o=null;try{if(o=e(n),!o)return r([`No <output> tag found in response`],i());let s=JSON.parse(o),c=t.safeParse(s);if(!c.success)return r(c.error.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`),i());let l=c.data,u=new Set(l.hunkAnnotations.map(e=>e.hunkIndex)),d=[...new Set(Array.from({length:a},(e,t)=>t))].filter(e=>!u.has(e));return d.length>0?r([`Missing annotations for hunks: ${d.join(`, `)}`],`Your response is missing annotations for some hunks. Every hunk must be annotated.\n\nMissing: ${d.join(`, `)}\n\nPlease provide a corrected response.`):{valid:!0,result:{title:l.title,summary:l.summary,concerns:l.concerns,dependencies:l.dependencies,hunkAnnotations:l.hunkAnnotations,orderingConstraints:l.orderingConstraints}}}catch(e){let t=e instanceof Error?e.message:String(e),a=(o??n).slice(-300);return r([`Failed to parse response as valid JSON: ${t}`,`Response tail: ${a}`],i())}}function r(e,t){return{valid:!1,errors:e,retryFeedback:t}}function i(){return`Your response has an invalid structure. Please return JSON inside <output> tags with this structure:
2
2
  <output>
3
3
  {
4
4
  "title": "Short PR-style title (under 72 chars)",
@@ -1 +1 @@
1
- import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{validateGroupResponse as r}from"../group/validator.js";import{buildComposeGroupSystemPrompt as i,buildComposeGroupUserPrompt as a}from"./prompt.js";async function o(o){let s=o.hunks,c=o.mode??`branches`;if(!s||s.length===0)throw new e(`No hunks provided for compose-group`);if(o.session?.completedSteps.composeGroup)return{result:o.session.completedSteps.composeGroup,session:o.session,attempts:0,durationMs:0,callIds:[]};let l=o.promptOverride?.system??i(o.conventions,c,o.strategy,o.includeAnalysis,o.lightweight),u=n(a({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions,condensed:o.condensed}),o.promptOverride),d=await t({model:o.model,systemPrompt:l,userPrompt:u,maxRetries:o.maxRetries,onProgress:o.onProgress,signal:o.signal,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>r(e,s.length,c,o.existingBranchNames),taskName:`compose-group`}),f=o.session?.completedSteps??{},p={...d.session,completedSteps:{...f,composeGroup:d.result}};return{result:d.result,session:p,usage:d.usage,attempts:d.attempts,durationMs:d.durationMs,callIds:d.callIds}}export{o as composeGroup};
1
+ import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{validateGroupResponse as r}from"../group/validator.js";import{buildComposeGroupSystemPrompt as i,buildComposeGroupUserPrompt as a}from"./prompt.js";async function o(o){let s=o.hunks,c=o.mode??`branches`;if(!s||s.length===0)throw new e(`No hunks provided for compose-group`);if(o.session?.completedSteps.composeGroup)return{result:o.session.completedSteps.composeGroup,session:o.session,attempts:0,durationMs:0,callIds:[]};let l=o.promptOverride?.system??i(o.conventions,c,o.strategy,o.includeAnalysis,o.lightweight),u=n(a({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions,condensed:o.condensed}),o.promptOverride),d=await t({model:o.model,systemPrompt:l,userPrompt:u,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>r(e,s.length,c,o.existingBranchNames),taskName:`compose-group`}),f=o.session?.completedSteps??{},p={...d.session,completedSteps:{...f,composeGroup:d.result}};return{result:d.result,session:p,usage:d.usage,attempts:d.attempts,durationMs:d.durationMs,callIds:d.callIds}}export{o as composeGroup};
@@ -10,15 +10,25 @@ In addition to the \`branches\` field, include an \`analysis\` field at the top
10
10
  "branches": [ ... ]
11
11
  }
12
12
  \`\`\`
13
- `,i=`5. Write a commit message for each commit:
14
- - First line: concise summary, 50 chars max, third-person singular present tense ("Fixes", "Adds", "Improves")
15
- - Then a blank line followed by details focusing on "why" not "what"
16
- - Informal yet professional tone`;function a(e,t=`branches`,n,r,i){return i?t===`commits`?u(e,n):d(e,n):t===`commits`?o(e,n,r):s(e,n,r)}function o(e,t,a){let o=e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
13
+ `,i=`5. Write a full commit message for each commit containing both a summary and a description:
14
+ - **Summary** (first line): concise, 50 chars max, third-person singular present tense ("Fixes", "Adds", "Improves"). This line stands alone as the commit subject.
15
+ - **Blank line**: mandatory separator between summary and description.
16
+ - **Description** (body): one or more lines explaining *why* this change was made, not *what* it does. Focus on motivation, trade-offs, or context a reviewer needs. For non-trivial changes, always include a description — don't leave it at just the summary.
17
+ - Informal yet professional tone.`;function a(e,t=`branches`,n,r,i){return i?t===`commits`?u(e,n):d(e,n):t===`commits`?o(e,n,r):s(e,n,r)}function o(e,t,a){let o=e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
17
18
  6. Write a detailed explanation for each commit as if explaining to a reviewer.
18
19
 
19
20
  ${e.promptFragment}`:`${i}
20
21
  6. Write a detailed explanation for each commit as if explaining to a reviewer`,s=l(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits.
21
22
 
23
+ ## Critical constraints (output is rejected if violated)
24
+
25
+ 1. **Each hunk index appears exactly once across the entire response.** Not zero. Not two. Exactly one occurrence — meaning it appears in exactly one commit's \`hunks\` array, and only once within that array. Listing the same index twice inside one commit's \`hunks\`, OR listing it in two different commits, both count as duplicates and both fail validation. This is the most common failure mode when two hunks look similar (same file, same kind of change).
26
+ 2. **All hunk indices 0..N-1 must appear** across the union of every commit's \`hunks\` array.
27
+ 3. Each commit must contain at least one hunk. No empty commits.
28
+ 4. Each commit's \`id\` must be unique (e.g. \`commit-1\`, \`commit-2\`).
29
+
30
+ Before producing the \`<output>\` tag, do a self-check: count the total number of hunk indices across every commit's \`hunks\` array. That count MUST equal the input hunk count. If it does not match, you have either duplicated or skipped a hunk — find and fix it before outputting.
31
+
22
32
  ${a?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
23
33
 
24
34
  Before grouping, analyze the changes to understand:
@@ -31,10 +41,7 @@ ${a?"Output this analysis as a JSON field named `analysis` alongside your `branc
31
41
  ## Step 2: Group hunks into commits
32
42
 
33
43
  1. Group hunks into logical units that make sense together and can be applied atomically
34
- 2. Use each hunk only once. Ensure all hunks are assigned to exactly one commit
35
- 3. Ensure each commit is self-contained and atomic
36
- 4. Each commit must have a unique ID (e.g., "commit-1", "commit-2")
37
- 5. Every commit must contain at least one hunk. Do not produce empty commits — if you want to split a theme across multiple commits, each resulting commit needs its own hunks
44
+ 2. Ensure each commit is self-contained and atomic
38
45
  ${o}
39
46
  ${s}
40
47
 
@@ -48,16 +55,21 @@ ${n}
48
55
  ${a?r:``}
49
56
  Wrap all commits in a single branch object with id "default", name "", title "", and description "".
50
57
 
51
- Remember:
52
- - Every hunk must be assigned to exactly one commit
53
- - Every commit must contain at least one hunk (no empty commits)
54
- - Use exactly one branch with id "default", name "", title "", and description ""
55
- - Return only the <output> tag and no other text.`}function s(e,t,a){let o=e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
58
+ Return only the <output> tag and no other text.`}function s(e,t,a){let o=e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
56
59
  6. Write a detailed explanation for each commit as if explaining to a reviewer.
57
60
 
58
61
  ${e.promptFragment}`:`${i}
59
62
  6. Write a detailed explanation for each commit as if explaining to a reviewer`,s=c(t?.branchSplitting,e),u=l(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits and branches.
60
63
 
64
+ ## Critical constraints (output is rejected if violated)
65
+
66
+ 1. **Each hunk index appears exactly once across the entire response.** Not zero. Not two. Exactly one occurrence — meaning it appears in exactly one branch, in exactly one commit within that branch, and only once within that commit's \`hunks\` array. Listing the same index twice inside one commit's \`hunks\`, OR listing it in two different commits, OR listing it in two different branches, all count as duplicates and all fail validation. This is the most common failure mode when two hunks look similar (same file, same kind of change).
67
+ 2. **All hunk indices 0..N-1 must appear** across the union of every commit's \`hunks\` array across every branch.
68
+ 3. Each commit must contain at least one hunk. No empty commits.
69
+ 4. Each commit \`id\` must be unique across the whole response.
70
+
71
+ Before producing the \`<output>\` tag, do a self-check: count the total number of hunk indices across every commit's \`hunks\` array (across every branch). That count MUST equal the input hunk count. If it does not match, you have either duplicated or skipped a hunk — find and fix it before outputting.
72
+
61
73
  ${a?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
62
74
 
63
75
  Before grouping, analyze the changes to understand:
@@ -78,10 +90,7 @@ Your job has two levels:
78
90
  ### Commit guidelines
79
91
 
80
92
  1. Group hunks into logical units that make sense together and can be applied atomically
81
- 2. Use each hunk only once. Ensure all hunks are assigned to exactly one commit
82
- 3. Ensure each commit is self-contained and atomic
83
- 4. Each commit must have a unique ID (e.g., "commit-1", "commit-2")
84
- 5. Every commit must contain at least one hunk. Do not produce empty commits — if you want to split a theme across multiple commits, each resulting commit needs its own hunks
93
+ 2. Ensure each commit is self-contained and atomic
85
94
  ${o}
86
95
  ${u}
87
96
 
@@ -97,10 +106,7 @@ Output your organization as JSON inside a <output> tag matching this schema:
97
106
  ${n}
98
107
  \`\`\`
99
108
  ${a?r:``}
100
- Remember:
101
- - Every hunk must be assigned to exactly one commit in exactly one branch
102
- - Every commit must contain at least one hunk (no empty commits)
103
- - Return only the <output> tag and no other text.`}function c(e,t){let n=`Each branch must have a descriptive name, a short PR-style title (under 72 characters, imperative mood), and at least one commit.`,r=`- Each commit belongs to exactly one branch.
109
+ Return only the <output> tag and no other text.`}function c(e,t){let n=`Each branch must have a descriptive name, a short PR-style title (under 72 characters, imperative mood), and at least one commit.`,r=`- Each commit belongs to exactly one branch.
104
110
  ${t?.promptFragment?`- ${n} Follow the repository's branch naming conventions (see above).`:`- ${n}`}
105
111
  - Changes addressing the same pattern across different files belong in ONE branch.`,i=`- Consider merge-order preference, not just code dependencies. Even if two concerns compile independently, they may need to be in the same branch if merging one without the other would be confusing or incomplete.`;return e===`conservative`?`${r}\n- Default to a single branch. Only create multiple branches when concerns are completely unrelated.\n${i}\n- When in doubt, use fewer branches.`:e===`aggressive`?`${r}\n- Create separate branches for each distinct concern.\n${i}\n- Prefer more branches over fewer.`:`${r}\n- Create separate branches when concerns are genuinely independent — meaning a reviewer would gain nothing from seeing them together.\n- Keep changes in the same branch when they form a coherent story.\n${i}\n- When in doubt: "Would a reviewer want to see these together?"`}function l(e){return e===`consolidated`?`
106
112
  Prefer fewer, larger commits. Combine related changes into a single commit when they serve the same purpose.`:e===`granular`?`