@gitkraken/compose-tools 0.1.0 → 0.3.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 (77) hide show
  1. package/CHANGELOG.md +72 -1
  2. package/README.md +109 -22
  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 +10 -3
  8. package/dist/git/apply-target.js +1 -1
  9. package/dist/git/commit-chain.d.ts +47 -33
  10. package/dist/git/commit-chain.js +7 -5
  11. package/dist/git/diff-collector.d.ts +69 -19
  12. package/dist/git/diff-collector.js +6 -7
  13. package/dist/git/index.d.ts +2 -2
  14. package/dist/git/index.js +1 -1
  15. package/dist/git/port-dispatch.d.ts +11 -3
  16. package/dist/git/port-dispatch.js +4 -3
  17. package/dist/git/reparent.d.ts +39 -0
  18. package/dist/git/reparent.js +5 -0
  19. package/dist/git/require-exec.js +1 -1
  20. package/dist/git/safety-range.d.ts +33 -0
  21. package/dist/git/safety-range.js +5 -0
  22. package/dist/git/safety.d.ts +3 -3
  23. package/dist/git/safety.js +1 -1
  24. package/dist/git/types.d.ts +16 -15
  25. package/dist/git/utils/authorship.d.ts +22 -0
  26. package/dist/git/utils/authorship.js +1 -0
  27. package/dist/git/utils/conventions.d.ts +0 -1
  28. package/dist/git/utils/conventions.js +4 -4
  29. package/dist/git/utils/hunk-parser.d.ts +1 -1
  30. package/dist/git/utils/hunk-parser.js +1 -1
  31. package/dist/git/utils/temp-index.d.ts +1 -1
  32. package/dist/git/utils/temp-index.js +1 -1
  33. package/dist/index.d.ts +5 -2
  34. package/dist/index.js +1 -1
  35. package/dist/ports/git.d.ts +48 -7
  36. package/dist/tasks/ai-loop.d.ts +9 -15
  37. package/dist/tasks/ai-loop.js +2 -2
  38. package/dist/tasks/analyze/analyze.js +1 -1
  39. package/dist/tasks/analyze/prompt.d.ts +2 -0
  40. package/dist/tasks/analyze/prompt.js +5 -5
  41. package/dist/tasks/analyze/types.d.ts +6 -1
  42. package/dist/tasks/analyze/validator.js +1 -1
  43. package/dist/tasks/compose-group/compose-group.js +1 -1
  44. package/dist/tasks/compose-group/prompt.d.ts +2 -0
  45. package/dist/tasks/compose-group/prompt.js +52 -46
  46. package/dist/tasks/compose-group/types.d.ts +6 -1
  47. package/dist/tasks/group/group.js +1 -1
  48. package/dist/tasks/group/prompt.d.ts +2 -0
  49. package/dist/tasks/group/prompt.js +45 -39
  50. package/dist/tasks/group/types.d.ts +6 -1
  51. package/dist/tasks/group/validator.js +9 -4
  52. package/dist/tasks/order/order.js +1 -1
  53. package/dist/tasks/order/types.d.ts +3 -1
  54. package/dist/tasks/order/validator.js +1 -1
  55. package/dist/tasks/partition/partition.js +1 -1
  56. package/dist/tasks/partition/types.d.ts +3 -1
  57. package/dist/tasks/partition/validator.js +1 -1
  58. package/dist/tasks/quick-compose/prompt.d.ts +2 -0
  59. package/dist/tasks/quick-compose/prompt.js +24 -15
  60. package/dist/tasks/quick-compose/quick-compose.js +1 -1
  61. package/dist/tasks/quick-compose/types.d.ts +6 -1
  62. package/dist/tasks/quick-compose/validator.js +10 -2
  63. package/dist/tasks/types.d.ts +10 -9
  64. package/dist/tasks/utils/redact.d.ts +14 -0
  65. package/dist/tasks/utils/redact.js +1 -0
  66. package/dist/utils/cancellation.d.ts +26 -0
  67. package/dist/utils/cancellation.js +1 -0
  68. package/dist/workflows/compose.d.ts +89 -10
  69. package/dist/workflows/compose.js +3 -2
  70. package/dist/workflows/split.d.ts +14 -3
  71. package/dist/workflows/split.js +3 -3
  72. package/dist/workflows/types.d.ts +46 -18
  73. package/dist/workflows/undo.d.ts +18 -11
  74. package/dist/workflows/undo.js +1 -1
  75. package/package.json +2 -2
  76. package/dist/cancellation.d.ts +0 -20
  77. package/dist/cancellation.js +0 -1
@@ -1,6 +1,8 @@
1
- import{GitError as e}from"../errors/git.js";import{applyPatchToIndex as t,checkout as n,cherryPick as r,cherryPickAbort as i,commitTree as a,currentBranch as o,listUntracked as s,log as c,readTree as l,reset as u,revParse as d,stageAll as f,stashPop as p,stashPush as m,statusPorcelain as h,updateRef as g,writeTree as _}from"./port-dispatch.js";import{buildPatchForCommit as v}from"./utils/patch-builder.js";import{randomUUID as y}from"node:crypto";import*as b from"node:fs";import*as x from"node:os";import*as S from"node:path";async function C(n){let{git:r,baseSha:i,commits:o,hunks:s,signal:c,signing:u,authorAttribution:d=`plurality`}=n;c?.throwIfAborted();let f=S.join(x.tmpdir(),`compose-idx-${y()}`),p=i,m=new Map,h=new Set;try{for(let n of o){c?.throwIfAborted();let i=n.hunkIndices.map(t=>{let n=s.find(e=>e.index===t);if(!n)throw new e(`OPERATION_FAILED`,`Hunk index ${t} not found`);return n}),o=v(i.map(e=>h.has(e.diffHeader)?{...e,diffHeader:P(e.diffHeader,e.fileName)}:e));if(!o.trim())throw new e(`OPERATION_FAILED`,`Empty patch for commit: ${n.message}`);let g={GIT_INDEX_FILE:f};await l(r,p,{indexEnv:g,signal:c});try{await t(r,o,{indexEnv:g,signal:c})}catch(t){let r=t instanceof Error?t.message:String(t);throw new e(`OPERATION_FAILED`,`git apply failed for commit '${n.message.split(`
2
- `)[0]}': ${r}`,{cause:t})}for(let e of i)e.originalFileName&&e.diffHeader.includes(`rename from`)&&h.add(e.diffHeader);let y=await _(r,{indexEnv:g,signal:c}),b=A(i,d),x=M(n.message,b.coAuthors),S=await a(r,y,[p],x,{signal:c,signing:u,author:b.author});m.set(n.id,S),p=S}return{headSha:p,shaMap:m}}finally{N(f)}}async function w(e){let{git:t,newHeadSha:r,branchName:i,untrackedIncluded:a,signal:o}=e;o?.throwIfAborted(),a&&await T(t,o);let s=await O(t,o);try{i?await n(t,i,{createBranch:!0,startPoint:r,signal:o}):await u(t,r,`hard`,{signal:o})}catch(e){throw s&&await k(t,o),e}s&&await k(t,o)}async function T(e,t){let n=await s(e,{signal:t});n.length>0&&await f(e,{paths:n,signal:t})}async function E(e){let{git:t,branchName:n,newHeadSha:r,signal:i}=e;if(i?.throwIfAborted(),await g(t,`refs/heads/${n}`,r,{signal:i}),await o(t,{signal:i})===n){let e=await O(t,i);try{await u(t,r,`hard`,{signal:i})}catch(n){throw e&&await k(t,i),n}e&&await k(t,i)}}async function D(t){let{git:a,branch:s,rangeTo:l,newHeadSha:f,originalBranchSha:p,preserveSuffix:m=!0,signal:h}=t;h?.throwIfAborted();let _=await o(a,{signal:h})===s,v=[],y=f;if(m&&l!==p){let t=(await c(a,{format:`%H`,range:`${l}..${p}`,reverse:!0,signal:h})).trim().split(`
3
- `).filter(Boolean);if(t.length>0){let o=await O(a,h);try{await n(a,f,{detach:!0,signal:h});for(let n of t){h?.throwIfAborted();try{await r(a,n,{signal:h}),v.push(n)}catch(t){throw await i(a,{signal:h}).catch(()=>{}),new e(`CHERRY_PICK_CONFLICT`,`Could not preserve commit ${n.slice(0,8)} after rewriting the range. Cherry-pick aborted; no changes were made to the branch.`,{cause:t,detail:{conflictingCommit:n,successfullyCherryPicked:[...v]}})}}y=await d(a,`HEAD`,{signal:h})}finally{o&&await k(a,h)}}}if(await g(a,`refs/heads/${s}`,y,{signal:h}),_){let e=await O(a,h);try{await n(a,s,{signal:h}),await u(a,y,`hard`,{signal:h})}finally{e&&await k(a,h)}}return{finalTipSha:y,preservedSuffixShas:v}}async function O(e,t){return(await h(e,{signal:t})).trim()?(await m(e,{message:`compose backup`,includeUntracked:!0,signal:t}),!0):!1}async function k(e,t){try{await p(e,{signal:t})}catch{}}function A(e,t){let n=e.filter(e=>e.author!=null);if(n.length===0)return{author:void 0,coAuthors:[]};let r;if(typeof t==`function`)r=t(e);else if(t===`first-hunk`)r=n[0].author;else{let e=new Map;for(let t of n){let n=j(t.author),r=e.get(n);r?r.count++:e.set(n,{identity:t.author,count:1})}let t=0;for(let{identity:n,count:i}of e.values())i>t&&(r=n,t=i)}let i=new Map;for(let t of e){t.author&&i.set(j(t.author),t.author);for(let e of t.coAuthors??[])i.set(j(e),e)}let a=r?j(r):null,o=[];for(let[e,t]of i)e!==a&&o.push(t);return{author:r,coAuthors:o}}function j(e){return`${e.name.toLowerCase()}\0${e.email.toLowerCase()}`}function M(e,t){if(t.length===0)return e;let n=t.map(e=>`Co-authored-by: ${e.name} <${e.email}>`).join(`
4
- `);return`${e.trimEnd()}\n\n${n}\n`}function N(e){try{b.unlinkSync(e)}catch{}}function P(e,t){let n=e.split(`
1
+ import{GitError as e}from"../errors/git.js";import{applyPatchToIndex as t,applyPatchToWorkdir as n,checkout as r,commitTree as i,currentBranch as a,forEachRef as o,listUntracked as s,localeNeutralEnv as c,readTree as l,reset as u,revParse as d,stageAll as f,stashDrop as p,stashPop as m,stashPush as h,statusPorcelain as g,updateRef as _,writeTree as v}from"./port-dispatch.js";import{reparentDescendants as y}from"./reparent.js";import{buildPatchForCommit as b}from"./utils/patch-builder.js";import{randomUUID as x}from"node:crypto";import*as S from"node:fs";import*as C from"node:os";import*as w from"node:path";async function T(n){let{git:r,baseSha:a,commits:o,hunks:s,signing:c,authorAttribution:u=`plurality`}=n,d=w.join(C.tmpdir(),`compose-idx-${x()}`),f=a,p=new Map,m=new Set;try{for(let n of o){let a=n.hunkIndices.map(t=>{let n=s.find(e=>e.index===t);if(!n)throw new e(`OPERATION_FAILED`,`Hunk index ${t} not found`);return n}),o=b(a.map(e=>m.has(e.diffHeader)?{...e,diffHeader:V(e.diffHeader,e.fileName)}:e));if(!o.trim())throw new e(`OPERATION_FAILED`,`Empty patch for commit: ${n.message}`);let h={GIT_INDEX_FILE:d};await l(r,f,{indexEnv:h});try{await t(r,o,{indexEnv:h})}catch(t){let r=t instanceof Error?t.message:String(t);throw new e(`OPERATION_FAILED`,`git apply failed for commit '${n.message.split(`
2
+ `)[0]}': ${r}`,{cause:t})}for(let e of a)e.originalFileName&&e.diffHeader.includes(`rename from`)&&m.add(e.diffHeader);let g=await v(r,{indexEnv:h}),_=L(a,u),y=z(n.message,_.coAuthors),x=await i(r,g,[f],y,{signing:c,author:_.author});p.set(n.id,x),f=x}return{headSha:f,shaMap:p}}finally{B(d)}}async function E(e){let{git:t,newHeadSha:i,branchName:a,untrackedIncluded:o,stashLabel:s,leftoverPatch:c}=e;o&&await D(t);let l=s??A,d=await j(t,l);try{a?await r(t,a,{createBranch:!0,startPoint:i}):await u(t,i,`hard`),c&&await n(t,c)}catch(e){throw d!=null&&await N(t,d,l),e}if(d!=null){let e=await N(t,d,l);if(e.conflicted)return{stashConflict:{stashLabel:l,conflictedFiles:e.conflictedFiles}}}return{}}async function D(e){let t=await s(e);t.length>0&&await f(e,{paths:t})}async function O(e){let{git:t,branch:i,rangeTo:o,newHeadSha:s,originalBranchSha:c,stashLabel:l,leftoverPatch:d,includeWorkdirActive:f}=e,p=await a(t)===i,m=l??A,h=p&&!f?await j(t,m):null,g,v=s,b=[];try{let e=await y({git:t,oldToSha:o,newTopSha:s,sourceBranchName:i});for(let t of e.reparentedRefs)b.push({refname:t.refname,previousSha:t.oldSha,newSha:t.newSha});e.sourceBranchNewSha==null?(await _(t,`refs/heads/${i}`,s),b.push({refname:`refs/heads/${i}`,previousSha:c,newSha:s}),v=s):v=e.sourceBranchNewSha;let a=await k(t,o,s,`refs/heads/${i}`);b.push(...a),p&&(await r(t,i),await u(t,v,`hard`),h==null&&d&&await n(t,d))}catch(e){throw h!=null&&await N(t,h,m),e}if(h!=null){let e=await N(t,h,m);e.conflicted&&(g={stashLabel:m,conflictedFiles:e.conflictedFiles})}return{finalTipSha:v,refMovements:b,stashConflict:g}}async function k(e,t,n,r){let i=await o(e,{format:`%(refname) %(objectname)`,pointsAt:t}),a=[];for(let o of i.split(`
3
+ `)){let i=o.trim();if(!i)continue;let s=i.indexOf(` `);if(s<0)continue;let c=i.slice(0,s),l=i.slice(s+1).trim();c!==r&&(!c.startsWith(`refs/heads/`)&&!c.startsWith(`refs/tags/`)||l===t&&(await _(e,c,n),a.push({refname:c,previousSha:t,newSha:n})))}return a}const A=`compose backup`;async function j(e,t){if(!(await g(e)).trim())return null;await h(e,{message:t,includeUntracked:!0});try{let t=(await d(e,`stash@{0}`)).trim();return t.length>0?t:null}catch{return null}}async function M(e,t){if(!e.exec)return null;let n;try{n=await e.exec([`stash`,`list`,`--format=%H %gd`],{env:c})}catch{return null}for(let e of n.split(`
4
+ `)){let n=e.indexOf(` `);if(!(n<0)&&e.slice(0,n)===t){let t=e.slice(n+1).trim();return t.length>0?t:null}}return null}async function N(e,t,n){let r=await M(e,t);if(r==null)return{conflicted:!0,conflictedFiles:[]};let i;try{return await m(e,{withIndex:!0,ref:r}),{conflicted:!1,conflictedFiles:[]}}catch(e){i=e}let a=await I(e);if(a.length>0)return{conflicted:!0,conflictedFiles:a};if(P(i)){let n=await M(e,t);if(n!=null)try{await p(e,n)}catch{}return{conflicted:!1,conflictedFiles:[]}}return{conflicted:!0,conflictedFiles:[]}}function P(e){let t=F(e);return t.includes(`already exists, no checkout`)||t.includes(`could not restore untracked files from stash`)}function F(e){if(e==null)return``;if(e instanceof Error){let t=e.stderr;return t!=null&&t.length>0?`${e.message}\n${t}`:e.message}return String(e)}async function I(e){try{let t=await g(e),n=[];for(let e of t.split(`
5
+ `))/^(UU|AA|DD|AU|UA|UD|DU) /.test(e)&&n.push(e.slice(3).trim());return n}catch{return[]}}function L(e,t){let n=e.filter(e=>e.author!=null);if(n.length===0)return{author:void 0,coAuthors:[]};let r;if(typeof t==`function`)r=t(e);else if(t===`first-hunk`)r=n[0].author;else{let e=new Map;for(let t of n){let n=R(t.author),r=e.get(n);r?r.count++:e.set(n,{identity:t.author,count:1})}let t=0;for(let{identity:n,count:i}of e.values())i>t&&(r=n,t=i)}let i=new Map;for(let t of e){t.author&&i.set(R(t.author),t.author);for(let e of t.coAuthors??[])i.set(R(e),e)}let a=r?R(r):null,o=[];for(let[e,t]of i)e!==a&&o.push(t);return{author:r,coAuthors:o}}function R(e){return`${e.name.toLowerCase()}\0${e.email.toLowerCase()}`}function z(e,t){if(t.length===0)return e;let n=t.map(e=>`Co-authored-by: ${e.name} <${e.email}>`).join(`
6
+ `);return`${e.trimEnd()}\n\n${n}\n`}function B(e){try{S.unlinkSync(e)}catch{}}function V(e,t){let n=e.split(`
5
7
  `),r=[];for(let e of n)e.startsWith(`diff --git `)?r.push(`diff --git a/${t} b/${t}`):e.startsWith(`similarity index`)||e.startsWith(`rename from`)||e.startsWith(`rename to`)||(e.startsWith(`--- a/`)?r.push(`--- a/${t}`):r.push(e));return r.join(`
6
- `)}export{w as applyCommitChain,D as applyRangeRewrite,E as applyRecompose,C as buildCommitChain};
8
+ `)}export{E as applyCommitChain,O as applyRangeRewrite,T as buildCommitChain,k as moveRefsAtSha};
@@ -33,34 +33,83 @@ interface CollectionResult {
33
33
  branchName?: string;
34
34
  baseBranch?: string;
35
35
  branchSha?: string;
36
+ /**
37
+ * Base SHA for the recomposed chain — `mergeBase(mergeTarget, branch)` for `branch`
38
+ * sources, `parent(from)` for `commit-range` sources. Named `mergeBaseSha` for
39
+ * historical reasons but really means "parent of the first rewritten commit".
40
+ */
36
41
  mergeBaseSha?: string;
37
42
  untrackedIncluded: boolean;
38
43
  }
39
- interface CollectOptions {
40
- signal?: AbortSignal;
41
- }
42
- declare function collectFromSource(git: ComposeGitPort, source: ComposeSource, options?: CollectOptions): Promise<CollectionResult>;
44
+ declare function collectFromSource(git: ComposeGitPort, source: ComposeSource): Promise<CollectionResult>;
43
45
  interface WorkingDiffOptions {
44
46
  includeUnstaged: boolean;
45
47
  includeUntracked: boolean;
46
48
  }
47
- declare function collectWorkingDiffs(git: ComposeGitPort, options: WorkingDiffOptions, signal?: AbortSignal): Promise<WorkingDiffResult>;
48
- declare function collectBranchDiffs(git: ComposeGitPort, branchName: string, baseBranch: string, signal?: AbortSignal): Promise<BranchDiffResult>;
49
+ declare function collectWorkingDiffs(git: ComposeGitPort, options: WorkingDiffOptions): Promise<WorkingDiffResult>;
50
+ /**
51
+ * Branch source — thin adapter over {@link collectCombinedDiffRange}. The merge-base with
52
+ * `baseBranch` plays the role of `parent(from)`.
53
+ */
54
+ declare function collectBranchDiffs(git: ComposeGitPort, branchName: string, baseBranch: string): Promise<BranchDiffResult>;
49
55
  /**
50
- * Collect hunks from a contiguous range of commits on a branch, tagging each
51
- * hunk with the author of the commit that introduced it.
56
+ * Collect hunks from a contiguous range of commits on a branch via a single
57
+ * combined diff. Both `from` and `to` are **inclusive** — they name the first
58
+ * and last commits being rewritten. For a single-commit recompose:
59
+ * `from = sha, to = sha`. The library computes `parent(from)` internally and
60
+ * delegates to {@link collectCombinedDiffRange}.
52
61
  *
53
- * `from` is exclusive, `to` is inclusive — so single-commit recompose is
54
- * `from = parent(sha), to = sha`. The returned `combinedDiff` is the
55
- * tree-to-tree diff `from..to`; per-commit patches are re-parsed to get author
56
- * attribution, then the hunks from those per-commit parses are what's returned
57
- * (ordered by commit, then by file/hunk within each commit).
62
+ * The returned hunks come from `diffTree(parent(from), to)`, so every hunk
63
+ * shares one pre-image (the tree at `parent(from)`) and the AI can freely
64
+ * reorder/split them across new commits. Per-hunk authorship is attributed
65
+ * via a line-range-overlap heuristic against per-commit hunks in the range.
58
66
  *
59
- * The snapshot records `branchSha = to` and `baseSha = from` so the apply
60
- * phase can re-verify the range state hasn't drifted.
67
+ * The snapshot records `branchSha = to` and `baseSha = parent(from)` so the
68
+ * apply phase can re-verify the range hasn't drifted.
61
69
  */
62
- declare function collectCommitRangeDiffs(git: ComposeGitPort, branchName: string, from: string, to: string, signal?: AbortSignal): Promise<BranchDiffResult>;
63
- declare function collectCommitsForSplit(git: ComposeGitPort, branchName: string, baseBranch: string, signal?: AbortSignal): Promise<SplitCollectionResult>;
70
+ declare function collectCommitRangeDiffs(git: ComposeGitPort, branchName: string, from: string, to: string): Promise<BranchDiffResult>;
71
+ /**
72
+ * Shared collection path for `commit-range` and `branch` sources. Returns the combined diff
73
+ * `diffTree(parentOfFromSha, toSha)` parsed into hunks, with per-hunk authorship attributed
74
+ * via a line-range-overlap heuristic against a per-commit side-table.
75
+ *
76
+ * Merge commits walk their first-parent diff. The incoming content from the other parent is
77
+ * already in `parentOfFromSha`'s tree (for `branch` sources) and won't match the heuristic;
78
+ * conflict-resolution edits at the merge DO appear in the combined diff and correctly
79
+ * attribute to the merge committer.
80
+ */
81
+ declare function collectCombinedDiffRange(git: ComposeGitPort, parentOfFromSha: string, toSha: string): Promise<BranchDiffResult>;
82
+ /**
83
+ * Collect a commit-range plus selected portions of the working directory as one combined input.
84
+ *
85
+ * All hunks come from a single combined diff `diffTree(parent(from), finalTree)`, where
86
+ * `finalTree` is HEAD's tree with the requested workdir portions applied. The safety
87
+ * snapshot hashes that same combined diff — the bytes the AI sees match the bytes the
88
+ * apply phase verifies against.
89
+ *
90
+ * Requires `to` to equal both the branch tip and HEAD, and HEAD to be non-detached.
91
+ * Mixing WIP with a range only makes sense when the user is actively on the branch tip
92
+ * being recomposed.
93
+ */
94
+ declare function collectCommitRangeWithWorkdir(git: ComposeGitPort, branchName: string, from: string, to: string, options: {
95
+ includeStaged: boolean;
96
+ includeUnstaged: boolean;
97
+ includeUntracked: boolean;
98
+ }): Promise<BranchDiffResult>;
99
+ /**
100
+ * Build a tree representing HEAD plus the requested workdir portions, and return its SHA.
101
+ *
102
+ * Each portion is staged into a temp index in dependency order: staged first (HEAD→index),
103
+ * then unstaged (index→worktree-tracked), then untracked (intent-to-add + content).
104
+ */
105
+ declare function collectWorkdirPortionForRange(git: ComposeGitPort, headSha: string, options: {
106
+ includeStaged: boolean;
107
+ includeUnstaged: boolean;
108
+ includeUntracked: boolean;
109
+ }): Promise<{
110
+ finalTreeSha: string;
111
+ }>;
112
+ declare function collectCommitsForSplit(git: ComposeGitPort, branchName: string, baseBranch: string): Promise<SplitCollectionResult>;
64
113
  /**
65
114
  * Compute per-source content hashes alongside the authoritative combined hash.
66
115
  *
@@ -73,8 +122,9 @@ declare function collectCommitsForSplit(git: ComposeGitPort, branchName: string,
73
122
  * verify path with a generic error message.
74
123
  */
75
124
  declare function collectSourceHashes(git: ComposeGitPort, options: {
125
+ includeStaged?: boolean;
76
126
  includeUnstaged: boolean;
77
127
  includeUntracked: boolean;
78
- }, signal?: AbortSignal): Promise<SafetySnapshot['sourceHashes']>;
128
+ }): Promise<SafetySnapshot['sourceHashes']>;
79
129
  //#endregion
80
- export { BranchCommitInfo, BranchDiffResult, CollectOptions, CollectionResult, SplitCollectionResult, WorkingDiffOptions, WorkingDiffResult, collectBranchDiffs, collectCommitRangeDiffs, collectCommitsForSplit, collectFromSource, collectSourceHashes, collectWorkingDiffs };
130
+ export { BranchCommitInfo, BranchDiffResult, CollectionResult, SplitCollectionResult, WorkingDiffOptions, WorkingDiffResult, collectBranchDiffs, collectCombinedDiffRange, collectCommitRangeDiffs, collectCommitRangeWithWorkdir, collectCommitsForSplit, collectFromSource, collectSourceHashes, collectWorkdirPortionForRange, collectWorkingDiffs };
@@ -1,8 +1,7 @@
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,currentBranch as i,diffTree as a,gitDir as o,listUntracked as s,log as c,mergeBase as l,mergeBaseIsAncestor as u,readTree as d,revParse as f,revParseVerify as p,stageAll as m,writeTree as h}from"./port-dispatch.js";import{getAuthorAndCoAuthorsForCombinedDiffHunk as g}from"./utils/authorship.js";import{sha256 as _}from"./utils/crypto.js";import{parseUnifiedDiff as v}from"./utils/hunk-parser.js";import{withUntrackedInTempIndex as y}from"./utils/temp-index.js";import{randomUUID as b}from"node:crypto";import{copyFileSync as x,unlinkSync as S}from"node:fs";import{tmpdir as C}from"node:os";import{join as w}from"node:path";async function T(e,t){if(t.type===`workdir`){let n=!t.stagedOnly&&!t.noUntracked;return{...await E(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 L(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 M(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 j(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 E(e,t){let n=t.includeUnstaged||t.includeUntracked;return t.includeUntracked?k(e,n):D(e,n)}async function D(e,t){let n=await f(e,`HEAD`),r=await O(e,n,t);return{hunks:v(r),snapshot:V(r,n,void 0,void 0,await H(e,{includeUnstaged:t,includeUntracked:!1})),combinedDiff:r}}async function O(e,t,n){if(!n)return a(e,t,await h(e));let r=w(await o(e),`index`),i=w(C(),`compose-snapshot-idx-${b()}`);try{x(r,i);let n={GIT_INDEX_FILE:i};return await m(e,{trackedOnly:!0,indexEnv:n}),a(e,t,await h(e,{indexEnv:n}))}finally{try{S(i)}catch{}}}async function k(e,t){return y(e,async n=>{await A(e,n,t);let r=await h(e,{indexEnv:n}),i=await f(e,`HEAD`),o=await a(e,i,r);return{hunks:v(o),snapshot:V(o,i,void 0,void 0,await H(e,{includeUnstaged:t,includeUntracked:!0})),combinedDiff:o}})}async function A(e,t,n){n&&await m(e,{trackedOnly:!0,indexEnv:t}),await m(e,{indexEnv:t})}async function j(e,t,n){let r=await B(e,t);return P(e,await l(e,await B(e,n),r),await f(e,r))}async function M(e,t,n,r){await B(e,t);let i=await f(e,n),a=await f(e,r);return P(e,await N(e,i),a)}async function N(e,n){try{return await f(e,`${n}^`)}catch{throw new t(`Cannot recompose commit ${n.slice(0,8)} — it has no parent (root commit). Recomposing the root commit is not currently supported.`)}}async function P(n,r,i){if(!await u(n,r,i))throw new t(`Cannot collect range ${r.slice(0,8)}..${i.slice(0,8)}: '${r.slice(0,8)}' is not an ancestor of '${i.slice(0,8)}'.`);let o=await a(n,r,i),s=v(o),l=I(await c(n,{format:`%H%n%an%n%ae%n%aI%n%B%n---COMMIT-END---`,range:`${r}..${i}`,reverse:!0}));if(l.length===0)throw new e(`NO_CHANGES`,`No commits in range '${r.slice(0,8)}..${i.slice(0,8)}'.`);let d=await F(n,l);for(let e of s){let{author:t,coAuthors:n}=g(d,e);t!=null&&(e.author=t),n.length>0&&(e.coAuthors=n)}let p=V(o,await f(n,`HEAD`),i,r);return{hunks:s,commits:l.map(e=>({sha:e.sha,message:e.message})),snapshot:p,combinedDiff:o}}async function F(e,t){let n=[];for(let r of t){let t;try{t=await a(e,`${r.sha}^`,r.sha)}catch{continue}let i=v(t,0);for(let e of i)e.author={name:r.authorName,email:r.authorEmail,date:r.authorDate};n.push(...i)}return n}function I(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 L(e,n,r,o,s){let l=await f(e,await B(e,n)),d=await f(e,r),p=await f(e,o),m=await f(e,`HEAD`);if(p!==l)throw new t(`commit-range with includeWorkdir requires 'to' to equal branch tip. Branch '${n}' tip is ${l}; got 'to'=${p}.`);if(m!==p)throw new t(`commit-range with includeWorkdir requires HEAD to equal 'to'. HEAD=${m}, 'to'=${p}.`);if(await i(e)==null)throw new t(`commit-range with includeWorkdir requires HEAD to be on a branch (non-detached). Check out the branch holding the range before composing.`);let h=await N(e,d),{finalTreeSha:_}=await R(e,m,s),y=await a(e,h,_);if(!await u(e,h,p))throw new t(`Cannot collect range ${h.slice(0,8)}..${p.slice(0,8)}: '${h.slice(0,8)}' is not an ancestor of '${p.slice(0,8)}'.`);let b=v(y),x=I(await c(e,{format:`%H%n%an%n%ae%n%aI%n%B%n---COMMIT-END---`,range:`${h}..${p}`,reverse:!0})),S=await F(e,x);for(let e of b){let{author:t,coAuthors:n}=g(S,e);t!=null&&(e.author=t),n.length>0&&(e.coAuthors=n)}let C=V(y,m,p,h,await H(e,s));return{hunks:b,commits:x.map(e=>({sha:e.sha,message:e.message})),snapshot:C,combinedDiff:y}}async function R(e,t,r){let i=w(C(),`compose-workdir-${b()}`),c={GIT_INDEX_FILE:i};try{if(await d(e,t,{indexEnv:c}),r.includeStaged){let r=await a(e,t,await h(e));r.trim()&&await n(e,r,{indexEnv:c})}if(r.includeUnstaged){let t=await h(e),r=w(C(),`compose-workdir-u-${b()}`),i={GIT_INDEX_FILE:r};try{x(w(await o(e),`index`),r),await m(e,{trackedOnly:!0,indexEnv:i});let s=await a(e,t,await h(e,{indexEnv:i}));s.trim()&&await n(e,s,{indexEnv:c})}finally{try{S(r)}catch{}}}if(r.includeUntracked){let t=await s(e);t.length>0&&(await m(e,{intentToAdd:!0,paths:t,indexEnv:c}),await m(e,{paths:t,indexEnv:c}))}return{finalTreeSha:await h(e,{indexEnv:c})}}finally{try{S(i)}catch{}}}async function z(e,t,n){let r=await B(e,t),i=await l(e,await B(e,n),r),o=await f(e,r),s=(await c(e,{format:`%H`,range:`${i}..${r}`,reverse:!0})).trim().split(`
4
+ `).filter(Boolean),u=[];for(let t of s){let[n,r,i]=await Promise.all([c(e,{format:`%B`,sha:t}).then(e=>e.trim()),a(e,`${t}^`,t),a(e,`${t}^`,t,{binary:!1,nameOnly:!0})]),o=i.trim().split(`
5
+ `).filter(Boolean);u.push({sha:t,message:n,diff:r,filesChanged:o})}return{commits:u,mergeBaseSha:i,branchSha:o}}async function B(t,n){if(await p(t,`refs/heads/${n}`)||await p(t,n))return n;if(await p(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 V(e,t,n,r,i){return{diffHash:_(e),headSha:t,branchSha:n,baseSha:r,sourceHashes:i}}async function H(e,t){if(!e.exec)return;let n=t.includeStaged??!0,r={};if(n)try{r.staged=_(await e.exec([`diff`,`--cached`,`--binary`,`HEAD`]))}catch{}if(t.includeUnstaged)try{r.unstaged=_(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=_(n.join(`
7
+ `))}else r.untracked=_(``)}catch{}return Object.keys(r).length>0?r:void 0}export{j as collectBranchDiffs,P as collectCombinedDiffRange,M as collectCommitRangeDiffs,L as collectCommitRangeWithWorkdir,z as collectCommitsForSplit,T as collectFromSource,H as collectSourceHashes,R as collectWorkdirPortionForRange,E as collectWorkingDiffs};
@@ -2,6 +2,6 @@ import { ApplyBranchEntry, ApplyBranchStructureParams, applyBranchStructure, cre
2
2
  import { SafetySnapshot } from "./types.js";
3
3
  import { BranchCommitInfo, BranchDiffResult, CollectionResult, SplitCollectionResult, WorkingDiffOptions, WorkingDiffResult, collectBranchDiffs, collectCommitsForSplit, collectFromSource, collectWorkingDiffs } from "./diff-collector.js";
4
4
  import { ApplyToTargetParams, ApplyToTargetResult, ComposeBranchPlan, applyToTarget } from "./apply-target.js";
5
- import { ApplyCommitChainParams, ApplyRecomposeParams, BuildCommitChainParams, BuildCommitChainResult, applyCommitChain, applyRecompose, buildCommitChain } from "./commit-chain.js";
5
+ import { ApplyCommitChainParams, BuildCommitChainParams, BuildCommitChainResult, applyCommitChain, buildCommitChain } from "./commit-chain.js";
6
6
  import { verifyMultiBranchDiff, verifyResultingDiff, verifySnapshot } from "./safety.js";
7
- export { type ApplyBranchEntry, type ApplyBranchStructureParams, type ApplyCommitChainParams, type ApplyRecomposeParams, type ApplyToTargetParams, type ApplyToTargetResult, type BranchCommitInfo, type BranchDiffResult, type BuildCommitChainParams, type BuildCommitChainResult, type CollectionResult, type ComposeBranchPlan, type SafetySnapshot, type SplitCollectionResult, type WorkingDiffOptions, type WorkingDiffResult, applyBranchStructure, applyCommitChain, applyRecompose, applyToTarget, buildCommitChain, collectBranchDiffs, collectCommitsForSplit, collectFromSource, collectWorkingDiffs, createStackedBranches, verifyMultiBranchDiff, verifyResultingDiff, verifySnapshot };
7
+ export { type ApplyBranchEntry, type ApplyBranchStructureParams, type ApplyCommitChainParams, type ApplyToTargetParams, type ApplyToTargetResult, type BranchCommitInfo, type BranchDiffResult, type BuildCommitChainParams, type BuildCommitChainResult, type CollectionResult, type ComposeBranchPlan, type SafetySnapshot, type SplitCollectionResult, type WorkingDiffOptions, type WorkingDiffResult, applyBranchStructure, applyCommitChain, applyToTarget, buildCommitChain, collectBranchDiffs, collectCommitsForSplit, collectFromSource, collectWorkingDiffs, createStackedBranches, verifyMultiBranchDiff, verifyResultingDiff, verifySnapshot };
package/dist/git/index.js CHANGED
@@ -1 +1 @@
1
- import{applyBranchStructure as e,createStackedBranches as t}from"./apply-branches.js";import{applyCommitChain as n,applyRecompose as r,buildCommitChain as i}from"./commit-chain.js";import{collectBranchDiffs as a,collectCommitsForSplit as o,collectFromSource as s,collectWorkingDiffs as c}from"./diff-collector.js";import{verifyMultiBranchDiff as l,verifyResultingDiff as u,verifySnapshot as d}from"./safety.js";import{applyToTarget as f}from"./apply-target.js";export{e as applyBranchStructure,n as applyCommitChain,r as applyRecompose,f as applyToTarget,i as buildCommitChain,a as collectBranchDiffs,o as collectCommitsForSplit,s as collectFromSource,c as collectWorkingDiffs,t as createStackedBranches,l as verifyMultiBranchDiff,u as verifyResultingDiff,d as verifySnapshot};
1
+ import{applyBranchStructure as e,createStackedBranches as t}from"./apply-branches.js";import{applyCommitChain as n,buildCommitChain as r}from"./commit-chain.js";import{collectBranchDiffs as i,collectCommitsForSplit as a,collectFromSource as o,collectWorkingDiffs as s}from"./diff-collector.js";import{verifyMultiBranchDiff as c,verifyResultingDiff as l,verifySnapshot as u}from"./safety.js";import{applyToTarget as d}from"./apply-target.js";export{e as applyBranchStructure,n as applyCommitChain,d as applyToTarget,r as buildCommitChain,i as collectBranchDiffs,a as collectCommitsForSplit,o as collectFromSource,s as collectWorkingDiffs,t as createStackedBranches,c as verifyMultiBranchDiff,l as verifyResultingDiff,u as verifySnapshot};
@@ -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, RevListChildrenEntry, RevListChildrenOptions, 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>;
@@ -6,9 +6,11 @@ declare function revParseVerify(git: ComposeGitPort, ref: string, opts?: OpOptio
6
6
  declare function gitDir(git: ComposeGitPort, opts?: OpOptions): Promise<string>;
7
7
  declare function currentBranch(git: ComposeGitPort, opts?: OpOptions): Promise<string | null>;
8
8
  declare function mergeBase(git: ComposeGitPort, a: string, b: string, opts?: OpOptions): Promise<string>;
9
+ declare function mergeBaseIsAncestor(git: ComposeGitPort, ancestor: string, descendant: string, opts?: OpOptions): Promise<boolean>;
9
10
  declare function diffTree(git: ComposeGitPort, from: string, to: string, opts?: DiffTreeOptions): Promise<string>;
10
11
  declare function log(git: ComposeGitPort, opts: LogOptions): Promise<string>;
11
12
  declare function forEachRef(git: ComposeGitPort, opts: ForEachRefOptions): Promise<string>;
13
+ declare function revListChildren(git: ComposeGitPort, opts: RevListChildrenOptions): Promise<RevListChildrenEntry[]>;
12
14
  declare function writeTree(git: ComposeGitPort, opts?: IndexScopedOptions): Promise<string>;
13
15
  declare function readTree(git: ComposeGitPort, sha: string, opts?: IndexScopedOptions): Promise<void>;
14
16
  declare function stageAll(git: ComposeGitPort, opts?: StageAllOptions): Promise<void>;
@@ -18,6 +20,7 @@ declare function showFile(git: ComposeGitPort, ref: string, path: string, opts?:
18
20
  declare function getConfig(git: ComposeGitPort, key: string, opts?: OpOptions): Promise<string | null>;
19
21
  declare function commitTree(git: ComposeGitPort, tree: string, parents: string[], message: string, opts?: CommitTreeOptions): Promise<string>;
20
22
  declare function applyPatchToIndex(git: ComposeGitPort, patch: string, opts?: ApplyPatchOptions): Promise<void>;
23
+ declare function applyPatchToWorkdir(git: ComposeGitPort, patch: string, opts?: ApplyPatchToWorkdirOptions): Promise<void>;
21
24
  declare function mergeTree(git: ComposeGitPort, a: string, b: string, opts?: OpOptions): Promise<string>;
22
25
  declare function createBranch(git: ComposeGitPort, name: string, startPoint: string, opts?: OpOptions): Promise<void>;
23
26
  declare function deleteBranch(git: ComposeGitPort, name: string, opts?: DeleteBranchOptions): Promise<void>;
@@ -27,8 +30,13 @@ declare function reset(git: ComposeGitPort, ref: string, mode: 'soft' | 'mixed'
27
30
  declare function cleanWorkdir(git: ComposeGitPort, opts?: CleanOptions): Promise<void>;
28
31
  declare function discardWorkdirChanges(git: ComposeGitPort, paths?: string[], opts?: OpOptions): Promise<void>;
29
32
  declare function stashPush(git: ComposeGitPort, opts?: StashPushOptions): Promise<void>;
30
- declare function stashPop(git: ComposeGitPort, opts?: OpOptions): Promise<void>;
33
+ declare const localeNeutralEnv: {
34
+ LANG: string;
35
+ LC_ALL: string;
36
+ };
37
+ declare function stashPop(git: ComposeGitPort, opts?: StashPopOptions): Promise<void>;
38
+ declare function stashDrop(git: ComposeGitPort, stashRef: string, opts?: OpOptions): Promise<void>;
31
39
  declare function cherryPick(git: ComposeGitPort, sha: string, opts?: OpOptions): Promise<void>;
32
40
  declare function cherryPickAbort(git: ComposeGitPort, opts?: OpOptions): Promise<void>;
33
41
  //#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 };
42
+ export { applyPatchToIndex, applyPatchToWorkdir, checkout, cherryPick, cherryPickAbort, cleanWorkdir, commitTree, createBranch, currentBranch, deleteBranch, diffTree, discardWorkdirChanges, forEachRef, getConfig, gitDir, listUntracked, localeNeutralEnv, log, mergeBase, mergeBaseIsAncestor, mergeTree, readTree, reset, revListChildren, revParse, revParseVerify, showFile, stageAll, stashDrop, stashPop, stashPush, statusPorcelain, updateRef, writeTree };
@@ -1,3 +1,4 @@
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.mergeBaseIsAncestor)return e.mergeBaseIsAncestor(n,r,i);if(e.exec)try{return await e.exec([`merge-base`,`--is-ancestor`,n,r]),!0}catch{return!1}return t(`mergeBaseIsAncestor`)}async function c(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 l(e,n){if(e.log)return e.log(n);if(e.exec){let t=[`log`,`--format=${n.format}`];if(n.noMerges&&t.push(`--no-merges`),n.reverse&&t.push(`--reverse`),n.topoOrder&&t.push(`--topo-order`),n.limit!=null&&t.push(`-n`,String(n.limit)),n.author&&t.push(`--author=${n.author}`),n.sha)t.push(`-1`,n.sha);else if(n.range)for(let e of n.range.split(/\s+/))e&&t.push(e);return e.exec(t)}return t(`log`)}async function u(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.pointsAt&&t.push(`--points-at=${n.pointsAt}`),n.prefix&&t.push(n.prefix),n.limit!=null&&t.push(`--count=${n.limit}`),e.exec(t)}return t(`forEachRef`)}async function d(e,n){if(e.revListChildren)return e.revListChildren(n);if(e.exec){let t=[`rev-list`,`--children`];return n.reverse&&t.push(`--reverse`),t.push(n.range),f(await e.exec(t))}return t(`revListChildren`)}function f(e){let t=[];for(let n of e.split(`
2
+ `)){let e=n.trim();if(!e)continue;let r=e.split(/\s+/);t.push({sha:r[0],children:r.slice(1)})}return t}async function p(e,n){return e.writeTree?e.writeTree(n):e.exec?(await e.exec([`write-tree`],{env:n?.indexEnv})).trim():t(`writeTree`)}async function m(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 h(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 g(e,n){return e.statusPorcelain?e.statusPorcelain(n):e.exec?e.exec([`status`,`--porcelain`]):t(`statusPorcelain`)}async function _(e,n){return e.listUntracked?e.listUntracked(n):e.exec?(await e.exec([`ls-files`,`--others`,`--exclude-standard`])).trim().split(`
3
+ `).filter(Boolean):t(`listUntracked`)}async function v(e,n,r,i){return e.showFile?e.showFile(n,r,i):e.exec?e.exec([`show`,`${n}:${r}`]):t(`showFile`)}async function y(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 b(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 x(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 S(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 C(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(`
4
+ `)[0]:t(`mergeTree`)}async function w(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 T(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 E(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 D(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 O(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 k(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 A(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 j(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 M={LANG:`C`,LC_ALL:`C`};async function N(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:M});return}return t(`stashPop`)}async function P(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 F(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 I(e,n){if(e.cherryPickAbort)return e.cherryPickAbort(n);if(e.exec){await e.exec([`cherry-pick`,`--abort`]);return}return t(`cherryPickAbort`)}export{x as applyPatchToIndex,S as applyPatchToWorkdir,D as checkout,F as cherryPick,I as cherryPickAbort,k as cleanWorkdir,b as commitTree,w as createBranch,a as currentBranch,T as deleteBranch,c as diffTree,A as discardWorkdirChanges,u as forEachRef,y as getConfig,i as gitDir,_ as listUntracked,M as localeNeutralEnv,l as log,o as mergeBase,s as mergeBaseIsAncestor,C as mergeTree,m as readTree,O as reset,d as revListChildren,n as revParse,r as revParseVerify,v as showFile,h as stageAll,P as stashDrop,N as stashPop,j as stashPush,g as statusPorcelain,E as updateRef,p as writeTree};
@@ -0,0 +1,39 @@
1
+ import { ComposeGitPort, SigningConfig } from "../ports/git.js";
2
+
3
+ //#region src/git/reparent.d.ts
4
+ interface ReparentedRef {
5
+ refname: string;
6
+ oldSha: string;
7
+ newSha: string;
8
+ }
9
+ interface ReparentDescendantsParams {
10
+ git: ComposeGitPort;
11
+ /** The old tip of the rewritten range — descendants of this SHA get re-parented. */
12
+ oldToSha: string;
13
+ /** The new last commit of the recomposed chain. */
14
+ newTopSha: string;
15
+ /**
16
+ * The source branch's short name (e.g. `main`). When provided, the result
17
+ * includes `sourceBranchNewSha` if the source branch's tip was a strict
18
+ * descendant of `oldToSha` and got re-parented by this function. When
19
+ * undefined, the caller must move the source branch manually.
20
+ */
21
+ sourceBranchName?: string;
22
+ signing?: SigningConfig;
23
+ }
24
+ interface ReparentDescendantsResult {
25
+ reparentedRefs: ReparentedRef[];
26
+ /**
27
+ * Set when the source branch's tip was a strict descendant of `oldToSha` and got re-parented
28
+ * here. Undefined otherwise — the caller handles the source branch (move to `newTopSha` when
29
+ * it pointed at `oldToSha`, or leave at an interior commit per user allowance).
30
+ */
31
+ sourceBranchNewSha?: string;
32
+ }
33
+ /**
34
+ * Re-parent every ref whose tip is a strict descendant of `oldToSha` onto `newTopSha`.
35
+ * Refs at `oldToSha` itself are excluded — the caller moves those to the new tip directly.
36
+ */
37
+ declare function reparentDescendants(params: ReparentDescendantsParams): Promise<ReparentDescendantsResult>;
38
+ //#endregion
39
+ export { ReparentDescendantsParams, ReparentDescendantsResult, ReparentedRef, reparentDescendants };
@@ -0,0 +1,5 @@
1
+ import{GitError as e}from"../errors/git.js";import{commitTree as t,forEachRef as n,log as r,mergeBaseIsAncestor as i,updateRef as a}from"./port-dispatch.js";async function o(n){let{git:r,oldToSha:i,newTopSha:o,sourceBranchName:u,signing:d}=n,f=await s(r,i);if(f.length===0)return{reparentedRefs:[]};let p=await c(r,i,f.map(e=>e.sha)),m=new Map;m.set(i,o);for(let e of p){if(m.has(e))continue;let n=await l(r,e),i=n.parents.map(e=>m.get(e)??e),a=await t(r,n.tree,i,n.message,{author:n.author,signing:d});m.set(e,a)}let h=u?`refs/heads/${u}`:void 0,g,_=[];for(let t of f){let n=m.get(t.sha);if(n==null)throw new e(`OPERATION_FAILED`,`Internal error during descendant re-parenting: ref ${t.refname} at ${t.sha.slice(0,8)} did not appear in the downstream walk.`);await a(r,t.refname,n),_.push({refname:t.refname,oldSha:t.sha,newSha:n}),h!=null&&t.refname===h&&(g=n)}return{reparentedRefs:_,sourceBranchNewSha:g}}async function s(e,t){let r=await n(e,{format:`%(refname) %(objectname)`}),a=[];for(let n of r.split(`
2
+ `)){let r=n.trim();if(!r)continue;let o=r.indexOf(` `);if(o<0)continue;let s=r.slice(0,o),c=r.slice(o+1).trim();!s.startsWith(`refs/heads/`)&&!s.startsWith(`refs/tags/`)||c!==t&&await i(e,t,c)&&a.push({refname:s,sha:c})}return a}async function c(e,t,n){return(await r(e,{format:`%H`,range:`${n.join(` `)} ^${t}`,reverse:!0,topoOrder:!0})).trim().split(`
3
+ `).filter(Boolean)}async function l(t,n){let i=(await r(t,{format:`%T%n%P%n%an%n%ae%n%aI%n%B`,sha:n})).split(`
4
+ `);if(i.length<5)throw new e(`OPERATION_FAILED`,`Unable to read commit info for ${n.slice(0,8)}`);let a=i[0].trim(),o=i[1].trim(),s=o?o.split(/\s+/):[],c=i[2],l=i[3],u=i[4],d=i.slice(5).join(`
5
+ `).trimEnd(),f=u?new Date(u):void 0;return{tree:a,parents:s,author:{name:c,email:l,date:f&&!Number.isNaN(f.getTime())?f:void 0},message:d}}export{o as reparentDescendants};
@@ -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};
@@ -0,0 +1,33 @@
1
+ import { ComposeGitPort } from "../ports/git.js";
2
+ import { ComposeSource, ComposeTarget } from "../workflows/types.js";
3
+
4
+ //#region src/git/safety-range.d.ts
5
+ interface InteriorForkViolation {
6
+ kind: 'interior-fork';
7
+ sha: string;
8
+ /** Children of the offending commit. When > 1, the commit is forked. */
9
+ children: string[];
10
+ reason: 'multiple-children' | 'child-outside-range';
11
+ }
12
+ interface InteriorRefViolation {
13
+ kind: 'interior-ref';
14
+ refname: string;
15
+ sha: string;
16
+ }
17
+ type RewriteSafetyViolation = InteriorForkViolation | InteriorRefViolation;
18
+ interface ValidateRewriteRangeSafetyParams {
19
+ git: ComposeGitPort;
20
+ source: ComposeSource;
21
+ target: ComposeTarget;
22
+ }
23
+ /**
24
+ * Run the interior-fork and interior-ref checks against the source range.
25
+ * Throws `ComposeWorkflowInputError` with all violations listed when either
26
+ * check fails; returns silently on success.
27
+ *
28
+ * No-op when the target doesn't rewrite history (`head`, `branch` with
29
+ * `preserveOriginal: true`, `auto` with `preserveOriginal: true`).
30
+ */
31
+ declare function validateRewriteRangeSafety(params: ValidateRewriteRangeSafetyParams): Promise<void>;
32
+ //#endregion
33
+ export { InteriorForkViolation, InteriorRefViolation, RewriteSafetyViolation, ValidateRewriteRangeSafetyParams, validateRewriteRangeSafety };
@@ -0,0 +1,5 @@
1
+ import{ComposeWorkflowInputError as e}from"../errors/workflow.js";import{currentBranch as t,forEachRef as n,log as r,mergeBase as i,mergeBaseIsAncestor as a,revListChildren as o,revParse as s}from"./port-dispatch.js";async function c(t){let{git:n,source:r,target:i}=t;if(!l(r,i))return;let o=await u(n,r);if(o==null)return;let{sourceBranchName:s,parentOfFromSha:c,toSha:m}=o;if(!await a(n,c,m))return;let h=[],g=await d(n,c,m);h.push(...g);let _=await f(n,c,m,s);if(h.push(..._),h.length>0)throw new e(p(h))}function l(e,t){return t.type===`rewrite-range`?!0:(t.type===`branch`||t.type===`auto`)&&e.type===`branch`?!t.preserveOriginal:!1}async function u(t,n){if(n.type===`workdir`)return null;if(n.type===`branch`){let e=n.mergeTarget??`main`,r=await s(t,n.name),a=await i(t,await s(t,e),r);return{sourceBranchName:n.name,parentOfFromSha:a,toSha:r}}let r=await s(t,n.from),a=await s(t,n.to),o;try{o=await s(t,`${r}^`)}catch{throw new e(`Cannot rewrite commit ${r.slice(0,8)} — it has no parent (root commit). Rewriting the root commit is not currently supported.`)}return{sourceBranchName:n.branch,parentOfFromSha:o,toSha:a}}async function d(e,t,n){let i=await r(e,{format:`%H`,range:`${t}..${n}`}),a=new Set(i.trim().split(`
2
+ `).filter(Boolean)),s=await o(e,{range:`${t}..${n}`}),c=[];for(let e of s){if(e.sha===n||!a.has(e.sha))continue;if(e.children.length!==1){c.push({kind:`interior-fork`,sha:e.sha,children:e.children,reason:`multiple-children`});continue}let t=e.children[0];a.has(t)||c.push({kind:`interior-fork`,sha:e.sha,children:e.children,reason:`child-outside-range`})}return c}async function f(e,i,a,o){let c=await r(e,{format:`%H`,range:`${i}..${a}`}),l=new Set(c.trim().split(`
3
+ `).filter(Boolean));l.delete(a);let u=await n(e,{format:`%(refname) %(objectname)`}),d=`refs/heads/${o}`,f=[];for(let e of u.split(`
4
+ `)){let t=e.trim();if(!t)continue;let n=t.indexOf(` `);if(n<0)continue;let r=t.slice(0,n),i=t.slice(n+1).trim();r!==d&&(!r.startsWith(`refs/heads/`)&&!r.startsWith(`refs/tags/`)||l.has(i)&&f.push({kind:`interior-ref`,refname:r,sha:i}))}if(await t(e)==null){let t=await s(e,`HEAD`);l.has(t)&&f.push({kind:`interior-ref`,refname:`HEAD (detached)`,sha:t})}return f}function p(e){let t=[`Cannot safely rewrite the source range — applying the rewrite would orphan refs or lose history outside the range.`,``],n=e.filter(e=>e.kind===`interior-fork`),r=e.filter(e=>e.kind===`interior-ref`);if(n.length>0){t.push(`Interior fork(s) detected — every commit in the rewritten range (except the tip) must have exactly one child within the range:`);for(let e of n){let n=e.children.length>0?e.children.map(e=>e.slice(0,8)).join(`, `):`(none)`,r=e.reason===`multiple-children`?`has ${e.children.length} children`:`has a child that is outside the rewritten range`;t.push(` - ${e.sha.slice(0,8)}: ${r} (${n})`)}t.push(``)}if(r.length>0){t.push(`Refs pointing at interior commits — these would be orphaned by the rewrite:`);for(let e of r)t.push(` - ${e.refname} → ${e.sha.slice(0,8)}`);t.push(``),t.push(`To proceed: delete the offending refs (or move them outside the range), or check out a stable ref if HEAD is detached at an interior commit, then re-run.`)}return t.join(`
5
+ `)}export{c as validateRewriteRangeSafety};
@@ -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};
@@ -1,3 +1,5 @@
1
+ import { RefMovement, RefSnapshot } from "@gitkraken/shared-tools";
2
+
1
3
  //#region src/git/types.d.ts
2
4
  /**
3
5
  * Hash-based fingerprint of the diff at the start of an AI-driven compose pass.
@@ -23,21 +25,20 @@ interface SafetySnapshot {
23
25
  untracked?: string;
24
26
  };
25
27
  }
26
- /** Tracks the git mutations performed during an apply phase, for undo manifest construction. */
28
+ /**
29
+ * Git mutations performed during an apply phase, for undo manifest construction. Ref entries
30
+ * carry a `kind` (`'branch'` or `'tag'`) so undo can reconstruct the full refname and dispatch
31
+ * to the right plumbing.
32
+ */
27
33
  interface ApplyMutations {
28
- createdBranches: {
29
- name: string;
30
- sha: string;
31
- }[];
32
- deletedBranches: {
33
- name: string;
34
- sha: string;
35
- }[];
36
- movedBranches: {
37
- name: string;
38
- previousSha: string;
39
- newSha: string;
40
- }[];
34
+ createdRefs: RefSnapshot[];
35
+ deletedRefs: RefSnapshot[];
36
+ movedRefs: RefMovement[];
37
+ }
38
+ /** Reported when the apply path's stash pop hit conflicts. The apply itself succeeded. */
39
+ interface StashConflict {
40
+ stashLabel: string;
41
+ conflictedFiles: string[];
41
42
  }
42
43
  //#endregion
43
- export { ApplyMutations, SafetySnapshot };
44
+ export { ApplyMutations, SafetySnapshot, StashConflict };
@@ -0,0 +1,22 @@
1
+ import { ComposeHunk } from "../../tasks/types.js";
2
+
3
+ //#region src/git/utils/authorship.d.ts
4
+ declare function getHunkSimilarityValue(hunkA: ComposeHunk, hunkB: ComposeHunk): number;
5
+ type Identity = NonNullable<ComposeHunk['author']>;
6
+ /**
7
+ * Given a combined-diff hunk and the side-table of per-commit hunks, pick
8
+ * a primary author + co-authors by line-range overlap. Returns
9
+ * `{ author: undefined, coAuthors: [] }` when no per-commit hunk for the
10
+ * file matches — the caller treats that as "no opinion."
11
+ *
12
+ * Tie-breaking: when two authors have equal cumulative overlap, the first
13
+ * one to reach that score wins primary (iteration order over
14
+ * `perCommitHunks`). Both remain in the returned set — the tied author who
15
+ * didn't win primary surfaces as a co-author.
16
+ */
17
+ declare function getAuthorAndCoAuthorsForCombinedDiffHunk(perCommitHunks: ComposeHunk[], combinedHunk: ComposeHunk): {
18
+ author: Identity | undefined;
19
+ coAuthors: Identity[];
20
+ };
21
+ //#endregion
22
+ export { getAuthorAndCoAuthorsForCombinedDiffHunk, getHunkSimilarityValue };
@@ -0,0 +1 @@
1
+ const e=/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;function t(t){let n=t.match(e);return n?{oldStart:Number.parseInt(n[1],10),oldCount:n[2]==null?1:Number.parseInt(n[2],10),newStart:Number.parseInt(n[3],10),newCount:n[4]==null?1:Number.parseInt(n[4],10)}:null}function n(e,t,n,r){let i=e+t,a=n+r;return Math.max(0,Math.min(i,a)-Math.max(e,n))}function r(e,r){let i=t(e.hunkHeader),a=t(r.hunkHeader);return i==null||a==null?0:n(i.oldStart,i.oldCount,a.oldStart,a.oldCount)+n(i.newStart,i.newCount,a.newStart,a.newCount)}function i(e){return`${e.name.toLowerCase()}\x00${e.email.toLowerCase()}`}function a(e,t){let n=e.filter(e=>e.author!=null&&e.fileName===t.fileName&&(t.isRename!==!0||e.isRename===t.isRename));if(n.length===0)return{author:void 0,coAuthors:[]};let a=new Map,o=new Map,s,c=0;for(let e of n){if(!e.author)continue;let n=i(e.author);o.set(n,e.author);for(let t of e.coAuthors??[])o.set(i(t),t);let l=r(e,t),u=(a.get(n)??0)+l;a.set(n,u),(s==null||u>c)&&(c=u,s=n)}if(s==null)return{author:void 0,coAuthors:[]};let l=o.get(s),u=[];for(let[e,t]of o)e!==s&&u.push(t);return{author:l,coAuthors:u}}export{a as getAuthorAndCoAuthorsForCombinedDiffHunk,r as getHunkSimilarityValue};
@@ -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};