@gitkraken/compose-tools 0.1.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 (122) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +19 -0
  3. package/README.md +543 -0
  4. package/dist/ai/session.d.ts +12 -0
  5. package/dist/ai/session.js +1 -0
  6. package/dist/ai/types.d.ts +31 -0
  7. package/dist/ai/types.js +1 -0
  8. package/dist/cancellation.d.ts +20 -0
  9. package/dist/cancellation.js +1 -0
  10. package/dist/errors/ai.d.ts +10 -0
  11. package/dist/errors/ai.js +1 -0
  12. package/dist/errors/git.d.ts +12 -0
  13. package/dist/errors/git.js +1 -0
  14. package/dist/errors/task.d.ts +6 -0
  15. package/dist/errors/task.js +1 -0
  16. package/dist/errors/workflow.d.ts +13 -0
  17. package/dist/errors/workflow.js +1 -0
  18. package/dist/git/apply-branches.d.ts +28 -0
  19. package/dist/git/apply-branches.js +1 -0
  20. package/dist/git/apply-target.d.ts +39 -0
  21. package/dist/git/apply-target.js +1 -0
  22. package/dist/git/commit-chain.d.ts +87 -0
  23. package/dist/git/commit-chain.js +6 -0
  24. package/dist/git/diff-collector.d.ts +80 -0
  25. package/dist/git/diff-collector.js +8 -0
  26. package/dist/git/index.d.ts +7 -0
  27. package/dist/git/index.js +1 -0
  28. package/dist/git/port-dispatch.d.ts +34 -0
  29. package/dist/git/port-dispatch.js +3 -0
  30. package/dist/git/require-exec.d.ts +17 -0
  31. package/dist/git/require-exec.js +1 -0
  32. package/dist/git/safety.d.ts +22 -0
  33. package/dist/git/safety.js +1 -0
  34. package/dist/git/types.d.ts +43 -0
  35. package/dist/git/types.js +1 -0
  36. package/dist/git/utils/conventions.d.ts +33 -0
  37. package/dist/git/utils/conventions.js +10 -0
  38. package/dist/git/utils/crypto.d.ts +4 -0
  39. package/dist/git/utils/crypto.js +1 -0
  40. package/dist/git/utils/hunk-parser.d.ts +6 -0
  41. package/dist/git/utils/hunk-parser.js +7 -0
  42. package/dist/git/utils/patch-builder.d.ts +6 -0
  43. package/dist/git/utils/patch-builder.js +2 -0
  44. package/dist/git/utils/temp-index.d.ts +6 -0
  45. package/dist/git/utils/temp-index.js +1 -0
  46. package/dist/index.d.ts +28 -0
  47. package/dist/index.js +1 -0
  48. package/dist/ports/git.d.ts +199 -0
  49. package/dist/ports/git.js +1 -0
  50. package/dist/tasks/ai-loop.d.ts +99 -0
  51. package/dist/tasks/ai-loop.js +2 -0
  52. package/dist/tasks/analyze/analyze.d.ts +6 -0
  53. package/dist/tasks/analyze/analyze.js +1 -0
  54. package/dist/tasks/analyze/prompt.d.ts +16 -0
  55. package/dist/tasks/analyze/prompt.js +34 -0
  56. package/dist/tasks/analyze/schema.d.ts +38 -0
  57. package/dist/tasks/analyze/schema.js +1 -0
  58. package/dist/tasks/analyze/types.d.ts +69 -0
  59. package/dist/tasks/analyze/types.js +1 -0
  60. package/dist/tasks/analyze/validator.d.ts +7 -0
  61. package/dist/tasks/analyze/validator.js +13 -0
  62. package/dist/tasks/compose-group/compose-group.d.ts +15 -0
  63. package/dist/tasks/compose-group/compose-group.js +1 -0
  64. package/dist/tasks/compose-group/prompt.d.ts +26 -0
  65. package/dist/tasks/compose-group/prompt.js +162 -0
  66. package/dist/tasks/compose-group/types.d.ts +62 -0
  67. package/dist/tasks/compose-group/types.js +1 -0
  68. package/dist/tasks/group/group.d.ts +6 -0
  69. package/dist/tasks/group/group.js +1 -0
  70. package/dist/tasks/group/prompt.d.ts +15 -0
  71. package/dist/tasks/group/prompt.js +158 -0
  72. package/dist/tasks/group/schema.d.ts +19 -0
  73. package/dist/tasks/group/schema.js +1 -0
  74. package/dist/tasks/group/types.d.ts +61 -0
  75. package/dist/tasks/group/types.js +1 -0
  76. package/dist/tasks/group/validator.d.ts +7 -0
  77. package/dist/tasks/group/validator.js +53 -0
  78. package/dist/tasks/index.d.ts +15 -0
  79. package/dist/tasks/index.js +1 -0
  80. package/dist/tasks/order/order.d.ts +6 -0
  81. package/dist/tasks/order/order.js +1 -0
  82. package/dist/tasks/order/prompt.d.ts +13 -0
  83. package/dist/tasks/order/prompt.js +45 -0
  84. package/dist/tasks/order/schema.d.ts +12 -0
  85. package/dist/tasks/order/schema.js +1 -0
  86. package/dist/tasks/order/types.d.ts +36 -0
  87. package/dist/tasks/order/types.js +1 -0
  88. package/dist/tasks/order/validator.d.ts +11 -0
  89. package/dist/tasks/order/validator.js +12 -0
  90. package/dist/tasks/partition/partition.d.ts +6 -0
  91. package/dist/tasks/partition/partition.js +1 -0
  92. package/dist/tasks/partition/prompt.d.ts +16 -0
  93. package/dist/tasks/partition/prompt.js +61 -0
  94. package/dist/tasks/partition/schema.d.ts +14 -0
  95. package/dist/tasks/partition/schema.js +1 -0
  96. package/dist/tasks/partition/types.d.ts +53 -0
  97. package/dist/tasks/partition/types.js +1 -0
  98. package/dist/tasks/partition/validator.d.ts +7 -0
  99. package/dist/tasks/partition/validator.js +11 -0
  100. package/dist/tasks/quick-compose/prompt.d.ts +18 -0
  101. package/dist/tasks/quick-compose/prompt.js +47 -0
  102. package/dist/tasks/quick-compose/quick-compose.d.ts +6 -0
  103. package/dist/tasks/quick-compose/quick-compose.js +1 -0
  104. package/dist/tasks/quick-compose/schema.d.ts +27 -0
  105. package/dist/tasks/quick-compose/schema.js +1 -0
  106. package/dist/tasks/quick-compose/types.d.ts +63 -0
  107. package/dist/tasks/quick-compose/types.js +1 -0
  108. package/dist/tasks/quick-compose/validator.d.ts +7 -0
  109. package/dist/tasks/quick-compose/validator.js +25 -0
  110. package/dist/tasks/session.d.ts +22 -0
  111. package/dist/tasks/session.js +1 -0
  112. package/dist/tasks/types.d.ts +101 -0
  113. package/dist/tasks/types.js +3 -0
  114. package/dist/workflows/compose.d.ts +371 -0
  115. package/dist/workflows/compose.js +2 -0
  116. package/dist/workflows/split.d.ts +80 -0
  117. package/dist/workflows/split.js +3 -0
  118. package/dist/workflows/types.d.ts +63 -0
  119. package/dist/workflows/types.js +1 -0
  120. package/dist/workflows/undo.d.ts +56 -0
  121. package/dist/workflows/undo.js +1 -0
  122. package/package.json +37 -0
@@ -0,0 +1,10 @@
1
+ //#region src/errors/ai.d.ts
2
+ type AIErrorCode = 'VALIDATION_EXHAUSTED';
3
+ declare class AIError extends Error {
4
+ readonly code: AIErrorCode;
5
+ constructor(code: AIErrorCode, message: string, options?: {
6
+ cause?: unknown;
7
+ });
8
+ }
9
+ //#endregion
10
+ export { AIError, AIErrorCode };
@@ -0,0 +1 @@
1
+ var e=class extends Error{code;constructor(e,t,n){super(t,n),this.name=`AIError`,this.code=e}};export{e as AIError};
@@ -0,0 +1,12 @@
1
+ //#region src/errors/git.d.ts
2
+ type GitErrorCode = 'BRANCH_NOT_FOUND' | 'OPERATION_FAILED' | 'SAFETY_CHECK_FAILED' | 'NO_CHANGES' | 'CHERRY_PICK_CONFLICT';
3
+ declare class GitError extends Error {
4
+ readonly code: GitErrorCode;
5
+ readonly detail?: Record<string, unknown>;
6
+ constructor(code: GitErrorCode, message: string, options?: {
7
+ cause?: unknown;
8
+ detail?: Record<string, unknown>;
9
+ });
10
+ }
11
+ //#endregion
12
+ export { GitError, GitErrorCode };
@@ -0,0 +1 @@
1
+ var e=class extends Error{code;detail;constructor(e,t,n){super(t,n),this.name=`GitError`,this.code=e,this.detail=n?.detail}};export{e as GitError};
@@ -0,0 +1,6 @@
1
+ //#region src/errors/task.d.ts
2
+ declare class TaskInputError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ //#endregion
6
+ export { TaskInputError };
@@ -0,0 +1 @@
1
+ var e=class extends Error{constructor(e){super(e),this.name=`TaskInputError`}};export{e as TaskInputError};
@@ -0,0 +1,13 @@
1
+ //#region src/errors/workflow.d.ts
2
+ type WorkflowErrorCode = 'SAFETY_CHECK_FAILED' | 'INTERNAL' | 'CANCELLED';
3
+ declare class WorkflowInputError extends Error {
4
+ constructor(message: string);
5
+ }
6
+ declare class WorkflowError extends Error {
7
+ readonly code: WorkflowErrorCode;
8
+ constructor(code: WorkflowErrorCode, message: string, options?: {
9
+ cause?: unknown;
10
+ });
11
+ }
12
+ //#endregion
13
+ export { WorkflowError, WorkflowErrorCode, WorkflowInputError };
@@ -0,0 +1 @@
1
+ var e=class extends Error{constructor(e){super(e),this.name=`WorkflowInputError`}},t=class extends Error{code;constructor(e,t,n){super(t,n),this.name=`WorkflowError`,this.code=e}};export{t as WorkflowError,e as WorkflowInputError};
@@ -0,0 +1,28 @@
1
+ import { ComposeGitPort } from "../ports/git.js";
2
+ import { BranchPartition } from "../tasks/types.js";
3
+
4
+ //#region src/git/apply-branches.d.ts
5
+ interface ApplyBranchStructureParams {
6
+ git: ComposeGitPort;
7
+ /** Each entry: a branch name and the SHA it should point at. May include stacked partitions. */
8
+ branches: ApplyBranchEntry[];
9
+ originalBranchName?: string;
10
+ originalBranchSha?: string;
11
+ signal?: AbortSignal;
12
+ }
13
+ interface ApplyBranchEntry {
14
+ /** The partition(s) for this branch group. Each partition becomes a git branch. */
15
+ partitions: BranchPartition[];
16
+ /** The SHA of the tip commit for the entire branch group (last partition's last commit). */
17
+ tipSha: string;
18
+ }
19
+ declare function applyBranchStructure(params: ApplyBranchStructureParams): Promise<void>;
20
+ declare function createStackedBranches(params: {
21
+ git: ComposeGitPort;
22
+ partitions: BranchPartition[];
23
+ originalBranchName?: string;
24
+ originalBranchSha?: string;
25
+ signal?: AbortSignal;
26
+ }): Promise<void>;
27
+ //#endregion
28
+ export { ApplyBranchEntry, ApplyBranchStructureParams, applyBranchStructure, createStackedBranches };
@@ -0,0 +1 @@
1
+ import{checkout as e,createBranch as t,currentBranch as n,deleteBranch as r}from"./port-dispatch.js";async function i(i){let{git:a,branches:o,originalBranchName:s,originalBranchSha:c,signal:l}=i;if(l?.throwIfAborted(),s&&c){let i=await n(a,{signal:l})===s;i&&await e(a,c,{detach:!0,signal:l}),await r(a,s,{force:!0,signal:l});for(let e of o)for(let n of e.partitions){l?.throwIfAborted();let e=n.commitIds[n.commitIds.length-1];await t(a,n.branchName,e,{signal:l})}if(i&&o.length>0){let t=o[o.length-1],n=t.partitions[t.partitions.length-1];await e(a,n.branchName,{signal:l})}}else for(let e of o)for(let n of e.partitions){l?.throwIfAborted();let e=n.commitIds[n.commitIds.length-1];await t(a,n.branchName,e,{signal:l})}}async function a(e){return i({git:e.git,branches:[{partitions:e.partitions,tipSha:e.partitions[e.partitions.length-1].commitIds[e.partitions[e.partitions.length-1].commitIds.length-1]}],originalBranchName:e.originalBranchName,originalBranchSha:e.originalBranchSha,signal:e.signal})}export{i as applyBranchStructure,a as createStackedBranches};
@@ -0,0 +1,39 @@
1
+ import { ComposeGitPort, SigningConfig } from "../ports/git.js";
2
+ import { BranchGroup, BranchPartition, CommitSuggestion, ComposeHunk } from "../tasks/types.js";
3
+ import { PartitionResult } from "../tasks/partition/types.js";
4
+ import { ComposeTarget } from "../workflows/types.js";
5
+ import { ApplyMutations, SafetySnapshot } from "./types.js";
6
+ import { CollectionResult } from "./diff-collector.js";
7
+
8
+ //#region src/git/apply-target.d.ts
9
+ interface ComposeBranchPlan {
10
+ branchGroup: BranchGroup;
11
+ orderedCommitIds: string[];
12
+ partitioning?: PartitionResult;
13
+ }
14
+ interface ApplyToTargetParams {
15
+ git: ComposeGitPort;
16
+ target: ComposeTarget;
17
+ branchPlans: ComposeBranchPlan[];
18
+ allOrderedCommits: CommitSuggestion[];
19
+ hunks: ComposeHunk[];
20
+ snapshot: SafetySnapshot;
21
+ collection: CollectionResult;
22
+ signal?: AbortSignal;
23
+ signing?: SigningConfig;
24
+ authorAttribution?: 'plurality' | 'first-hunk' | ((hunks: ComposeHunk[]) => {
25
+ name: string;
26
+ email: string;
27
+ } | undefined);
28
+ }
29
+ interface ApplyToTargetResult {
30
+ partitions?: BranchPartition[];
31
+ /** Partitions grouped by independent stack. Each inner array is one stack (root first, tip last). */
32
+ partitionGroups?: BranchPartition[][];
33
+ commitShas: Record<string, string>;
34
+ /** Git mutations performed during apply, for undo manifest construction. */
35
+ mutations: ApplyMutations;
36
+ }
37
+ declare function applyToTarget(params: ApplyToTargetParams): Promise<ApplyToTargetResult>;
38
+ //#endregion
39
+ export { ApplyToTargetParams, ApplyToTargetResult, ComposeBranchPlan, applyToTarget };
@@ -0,0 +1 @@
1
+ import{GitError as e}from"../errors/git.js";import{cleanWorkdir as t,discardWorkdirChanges as n,revParse as r}from"./port-dispatch.js";import{applyBranchStructure as i}from"./apply-branches.js";import{applyCommitChain as a,applyRangeRewrite as o,applyRecompose as s,buildCommitChain as c}from"./commit-chain.js";import{verifyMultiBranchDiff as l,verifyResultingDiff as u}from"./safety.js";async function d(t){let{git:n,target:l,branchPlans:d,allOrderedCommits:m,hunks:h,snapshot:v,collection:y,signal:b,signing:x,authorAttribution:S}=t;if(b?.throwIfAborted(),l.type===`rewrite-range`){if(!y.branchName||!y.branchSha)throw new e(`OPERATION_FAILED`,`rewrite-range target requires a source that records branchName and branchSha (branch or commit-range).`);let{headSha:t,shaMap:r}=await c({git:n,baseSha:l.rangeFrom,commits:m,hunks:h,signal:b,signing:x,authorAttribution:S}),i=await o({git:n,branch:l.branch,rangeFrom:l.rangeFrom,rangeTo:l.rangeTo,newHeadSha:t,originalBranchSha:y.branchSha,preserveSuffix:l.preserveSuffix,signal:b});return{commitShas:Object.fromEntries(r),mutations:{createdBranches:[],deletedBranches:[],movedBranches:[{name:l.branch,previousSha:y.branchSha,newSha:i.finalTipSha}]}}}if(y.branchName&&!y.mergeBaseSha)throw new e(`OPERATION_FAILED`,`Branch source is missing mergeBaseSha — cannot determine base commit`);let C=y.mergeBaseSha??await r(n,`HEAD`,{signal:b});if(l.type===`auto`&&d.length>1)return f(n,C,d,h,v,y,l.preserveOriginal,b,x,S);if(l.type===`auto`&&d.length===1&&d[0].partitioning)return p(n,C,d[0],h,v,y,l.preserveOriginal,b,x,S);let{headSha:w,shaMap:T}=await c({git:n,baseSha:C,commits:m,hunks:h,signal:b,signing:x,authorAttribution:S}),E=Object.fromEntries(T),D=await u(n,C,w,v,b);if(!D.valid)throw new e(`SAFETY_CHECK_FAILED`,`Diff verification failed: ${D.error}`);if(l.type===`branch-replace`){if(!y.branchName||!y.branchSha)throw new e(`OPERATION_FAILED`,`branch-replace target requires a branch source with branchName and branchSha`);return await s({git:n,branchName:y.branchName,newHeadSha:w,originalBranchSha:y.branchSha,signal:b}),{commitShas:E,mutations:{createdBranches:[],deletedBranches:[],movedBranches:[{name:y.branchName,previousSha:y.branchSha,newSha:w}]}}}if(l.type===`branch`)return await a({git:n,newHeadSha:w,branchName:l.name,untrackedIncluded:y.untrackedIncluded,signal:b}),{commitShas:E,mutations:{createdBranches:[{name:l.name,sha:w}],deletedBranches:[],movedBranches:[]}};if(l.type===`auto`){let e=d[0]?.branchGroup.name||y.branchName;if(e){let t=!l.preserveOriginal&&y.branchName&&y.branchSha?{originalBranchName:y.branchName,originalBranchSha:y.branchSha}:{},r={branchName:e,title:d[0]?.branchGroup.title??``,description:d[0]?.branchGroup.description??``,commitIds:m.map(e=>e.id)};await i({git:n,branches:[{partitions:[r],tipSha:w}],...t,signal:b});let a=_([r],E,t.originalBranchName,t.originalBranchSha);return{partitions:[r],commitShas:E,mutations:a}}return await a({git:n,newHeadSha:w,untrackedIncluded:y.untrackedIncluded,signal:b}),{commitShas:E,mutations:g()}}return await a({git:n,newHeadSha:w,untrackedIncluded:y.untrackedIncluded,signal:b}),{commitShas:E,mutations:g()}}async function f(r,a,o,s,u,d,f,p,g,v){let y=[],b=[],x=[],S=[],C={};for(let e of o){p?.throwIfAborted();let t=m(e),{headSha:n,shaMap:i}=await c({git:r,baseSha:a,commits:t,hunks:s,signal:p,signing:g,authorAttribution:v});x.push(n);for(let[e,t]of i)C[e]=t;if(e.partitioning&&e.partitioning.partitions.length>1){let t=h(e.partitioning.partitions,i);y.push(...t),b.push(t),S.push({partitions:t,tipSha:n})}else{let r={branchName:e.branchGroup.name,title:e.branchGroup.title,description:e.branchGroup.description,commitIds:t.map(e=>i.get(e.id)??e.id)};y.push(r),b.push([r]),S.push({partitions:[r],tipSha:n})}}let w=await l(r,a,x,u,p);if(!w.valid)throw new e(`SAFETY_CHECK_FAILED`,`Diff verification failed: ${w.error}`);let T=!f&&d.branchName&&d.branchSha?{originalBranchName:d.branchName,originalBranchSha:d.branchSha}:{};return await i({git:r,branches:S,...T,signal:p}),!d.branchName&&!f&&(await n(r,void 0,{signal:p}),d.untrackedIncluded&&await t(r,{signal:p})),{partitions:y,partitionGroups:b,commitShas:C,mutations:_(y,C,T.originalBranchName,T.originalBranchSha)}}async function p(r,o,s,l,d,f,p,v,y,b){let{headSha:x,shaMap:S}=await c({git:r,baseSha:o,commits:m(s),hunks:l,signal:v,signing:y,authorAttribution:b}),C=await u(r,o,x,d,v);if(!C.valid)throw new e(`SAFETY_CHECK_FAILED`,`Diff verification failed: ${C.error}`);let w=h(s.partitioning.partitions,S),T=Object.fromEntries(S);if(w.length>1){let e=!p&&f.branchName&&f.branchSha?{originalBranchName:f.branchName,originalBranchSha:f.branchSha}:{};return await i({git:r,branches:[{partitions:w,tipSha:x}],...e,signal:v}),!f.branchName&&!p&&(await n(r,void 0,{signal:v}),f.untrackedIncluded&&await t(r,{force:!0,directories:!0,signal:v})),{partitions:w,commitShas:T,mutations:_(w,T,e.originalBranchName,e.originalBranchSha)}}return await a({git:r,newHeadSha:x,untrackedIncluded:f.untrackedIncluded,signal:v}),{partitions:w,commitShas:T,mutations:g()}}function m(t){return t.orderedCommitIds.map(n=>{let r=t.branchGroup.commits.find(e=>e.id===n);if(!r)throw new e(`OPERATION_FAILED`,`Commit ${n} not found in branch '${t.branchGroup.id}'`);return r})}function h(t,n){return t.map(t=>({...t,commitIds:t.commitIds.map(t=>{let r=n.get(t);if(!r)throw new e(`OPERATION_FAILED`,`No SHA found for commit ID '${t}'`);return r})}))}function g(){return{createdBranches:[],deletedBranches:[],movedBranches:[]}}function _(e,t,n,r){let i=e.map(e=>{let n=e.commitIds[e.commitIds.length-1];return{name:e.branchName,sha:t[n]??n}}),a=[];return n&&r&&a.push({name:n,sha:r}),{createdBranches:i,deletedBranches:a,movedBranches:[]}}export{d as applyToTarget};
@@ -0,0 +1,87 @@
1
+ import { ComposeGitPort, SigningConfig } from "../ports/git.js";
2
+ import { CommitSuggestion, ComposeHunk } from "../tasks/types.js";
3
+
4
+ //#region src/git/commit-chain.d.ts
5
+ type AuthorAttributionStrategy = 'plurality' | 'first-hunk' | ((hunks: ComposeHunk[]) => {
6
+ name: string;
7
+ email: string;
8
+ } | undefined);
9
+ interface BuildCommitChainParams {
10
+ git: ComposeGitPort;
11
+ baseSha: string;
12
+ commits: CommitSuggestion[];
13
+ hunks: ComposeHunk[];
14
+ signal?: AbortSignal;
15
+ signing?: SigningConfig;
16
+ authorAttribution?: AuthorAttributionStrategy;
17
+ }
18
+ interface BuildCommitChainResult {
19
+ headSha: string;
20
+ /** Maps each CommitSuggestion.id to the real git SHA created for it */
21
+ shaMap: Map<string, string>;
22
+ }
23
+ declare function buildCommitChain(params: BuildCommitChainParams): Promise<BuildCommitChainResult>;
24
+ interface ApplyCommitChainParams {
25
+ git: ComposeGitPort;
26
+ newHeadSha: string;
27
+ branchName?: string;
28
+ /**
29
+ * Whether untracked files were included in the compose.
30
+ * When true, untracked files are staged before stashing to avoid
31
+ * conflicts between stash pop and the newly committed tree.
32
+ */
33
+ untrackedIncluded?: boolean;
34
+ signal?: AbortSignal;
35
+ }
36
+ declare function applyCommitChain(params: ApplyCommitChainParams): Promise<void>;
37
+ interface ApplyRecomposeParams {
38
+ git: ComposeGitPort;
39
+ branchName: string;
40
+ newHeadSha: string;
41
+ originalBranchSha: string;
42
+ signal?: AbortSignal;
43
+ }
44
+ declare function applyRecompose(params: ApplyRecomposeParams): Promise<void>;
45
+ interface ApplyRangeRewriteParams {
46
+ git: ComposeGitPort;
47
+ /** Branch whose tip will be updated to the result. */
48
+ branch: string;
49
+ /** Resolved SHA: parent of the first commit in the rewritten range. */
50
+ rangeFrom: string;
51
+ /** Resolved SHA: last commit in the rewritten range. */
52
+ rangeTo: string;
53
+ /** Resolved SHA: new tip of the range after the library applied its commit chain on top of `rangeFrom`. */
54
+ newHeadSha: string;
55
+ /** Resolved SHA: branch tip before the operation started. */
56
+ originalBranchSha: string;
57
+ /** When true (default), cherry-pick commits in `rangeTo..originalBranchSha` onto `newHeadSha`. */
58
+ preserveSuffix?: boolean;
59
+ signal?: AbortSignal;
60
+ }
61
+ interface ApplyRangeRewriteResult {
62
+ /** Final branch tip SHA after range rewrite + (optional) suffix cherry-picks. */
63
+ finalTipSha: string;
64
+ /** SHAs that were successfully cherry-picked, in order applied. Empty when preserveSuffix=false or no suffix. */
65
+ preservedSuffixShas: string[];
66
+ }
67
+ /**
68
+ * Rewrite a contiguous range of commits on a branch in-place.
69
+ *
70
+ * Given that the library has already built a new commit chain on top of
71
+ * `rangeFrom` (available at `newHeadSha`), this replays any commits that were
72
+ * after `rangeTo` on the original branch onto the new tip via cherry-pick, so
73
+ * work done later on the branch is preserved.
74
+ *
75
+ * Behavior:
76
+ * 1. Stash the user's workdir if dirty (restored in the caller via finally).
77
+ * 2. If `preserveSuffix` (default true) and the range didn't reach the branch
78
+ * tip, cherry-pick every commit in `rangeTo..originalBranchSha` onto
79
+ * `newHeadSha`. On conflict, abort the cherry-pick and throw
80
+ * `GitError('CHERRY_PICK_CONFLICT', …)` — repo state is left safe.
81
+ * 3. `update-ref` the branch to the final tip.
82
+ * 4. If the user was on the branch, `reset --hard` to the final tip so their
83
+ * checked-out state follows the rewrite.
84
+ */
85
+ declare function applyRangeRewrite(params: ApplyRangeRewriteParams): Promise<ApplyRangeRewriteResult>;
86
+ //#endregion
87
+ export { ApplyCommitChainParams, ApplyRangeRewriteParams, ApplyRangeRewriteResult, ApplyRecomposeParams, AuthorAttributionStrategy, BuildCommitChainParams, BuildCommitChainResult, applyCommitChain, applyRangeRewrite, applyRecompose, buildCommitChain };
@@ -0,0 +1,6 @@
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(`
5
+ `),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};
@@ -0,0 +1,80 @@
1
+ import { ComposeGitPort } from "../ports/git.js";
2
+ import { CommitDetail, ComposeHunk } from "../tasks/types.js";
3
+ import { ComposeSource } from "../workflows/types.js";
4
+ import { SafetySnapshot } from "./types.js";
5
+
6
+ //#region src/git/diff-collector.d.ts
7
+ interface BranchCommitInfo {
8
+ sha: string;
9
+ message: string;
10
+ }
11
+ interface WorkingDiffResult {
12
+ hunks: ComposeHunk[];
13
+ snapshot: SafetySnapshot;
14
+ combinedDiff: string;
15
+ }
16
+ interface BranchDiffResult {
17
+ hunks: ComposeHunk[];
18
+ commits: BranchCommitInfo[];
19
+ snapshot: SafetySnapshot;
20
+ combinedDiff: string;
21
+ }
22
+ interface SplitCollectionResult {
23
+ commits: CommitDetail[];
24
+ mergeBaseSha: string;
25
+ branchSha: string;
26
+ }
27
+ interface CollectionResult {
28
+ hunks: ComposeHunk[];
29
+ snapshot: SafetySnapshot;
30
+ combinedDiff: string;
31
+ commitMessages?: string[];
32
+ commitOrder?: string[];
33
+ branchName?: string;
34
+ baseBranch?: string;
35
+ branchSha?: string;
36
+ mergeBaseSha?: string;
37
+ untrackedIncluded: boolean;
38
+ }
39
+ interface CollectOptions {
40
+ signal?: AbortSignal;
41
+ }
42
+ declare function collectFromSource(git: ComposeGitPort, source: ComposeSource, options?: CollectOptions): Promise<CollectionResult>;
43
+ interface WorkingDiffOptions {
44
+ includeUnstaged: boolean;
45
+ includeUntracked: boolean;
46
+ }
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
+ /**
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.
52
+ *
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).
58
+ *
59
+ * The snapshot records `branchSha = to` and `baseSha = from` so the apply
60
+ * phase can re-verify the range state hasn't drifted.
61
+ */
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>;
64
+ /**
65
+ * Compute per-source content hashes alongside the authoritative combined hash.
66
+ *
67
+ * Stored on {@link SafetySnapshot.sourceHashes} for diagnostic purposes — when
68
+ * a combined-hash verify fails, the library compares per-source hashes against
69
+ * the current state to tell the consumer which subset drifted.
70
+ *
71
+ * Currently uses `git.exec` directly (cheap, diagnostic-only). Consumers
72
+ * without `exec` don't get per-source hashes — they still get the combined
73
+ * verify path with a generic error message.
74
+ */
75
+ declare function collectSourceHashes(git: ComposeGitPort, options: {
76
+ includeUnstaged: boolean;
77
+ includeUntracked: boolean;
78
+ }, signal?: AbortSignal): Promise<SafetySnapshot['sourceHashes']>;
79
+ //#endregion
80
+ export { BranchCommitInfo, BranchDiffResult, CollectOptions, CollectionResult, SplitCollectionResult, WorkingDiffOptions, WorkingDiffResult, collectBranchDiffs, collectCommitRangeDiffs, collectCommitsForSplit, collectFromSource, collectSourceHashes, collectWorkingDiffs };
@@ -0,0 +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(`
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};
@@ -0,0 +1,7 @@
1
+ import { ApplyBranchEntry, ApplyBranchStructureParams, applyBranchStructure, createStackedBranches } from "./apply-branches.js";
2
+ import { SafetySnapshot } from "./types.js";
3
+ import { BranchCommitInfo, BranchDiffResult, CollectionResult, SplitCollectionResult, WorkingDiffOptions, WorkingDiffResult, collectBranchDiffs, collectCommitsForSplit, collectFromSource, collectWorkingDiffs } from "./diff-collector.js";
4
+ import { ApplyToTargetParams, ApplyToTargetResult, ComposeBranchPlan, applyToTarget } from "./apply-target.js";
5
+ import { ApplyCommitChainParams, ApplyRecomposeParams, BuildCommitChainParams, BuildCommitChainResult, applyCommitChain, applyRecompose, buildCommitChain } from "./commit-chain.js";
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 };
@@ -0,0 +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};
@@ -0,0 +1,34 @@
1
+ import { ApplyPatchOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitPort, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, IndexScopedOptions, LogOptions, OpOptions, StageAllOptions, StashPushOptions, UpdateRefOptions } from "../ports/git.js";
2
+
3
+ //#region src/git/port-dispatch.d.ts
4
+ declare function revParse(git: ComposeGitPort, ref: string, opts?: OpOptions): Promise<string>;
5
+ declare function revParseVerify(git: ComposeGitPort, ref: string, opts?: OpOptions): Promise<string | null>;
6
+ declare function gitDir(git: ComposeGitPort, opts?: OpOptions): Promise<string>;
7
+ declare function currentBranch(git: ComposeGitPort, opts?: OpOptions): Promise<string | null>;
8
+ declare function mergeBase(git: ComposeGitPort, a: string, b: string, opts?: OpOptions): Promise<string>;
9
+ declare function diffTree(git: ComposeGitPort, from: string, to: string, opts?: DiffTreeOptions): Promise<string>;
10
+ declare function log(git: ComposeGitPort, opts: LogOptions): Promise<string>;
11
+ declare function forEachRef(git: ComposeGitPort, opts: ForEachRefOptions): Promise<string>;
12
+ declare function writeTree(git: ComposeGitPort, opts?: IndexScopedOptions): Promise<string>;
13
+ declare function readTree(git: ComposeGitPort, sha: string, opts?: IndexScopedOptions): Promise<void>;
14
+ declare function stageAll(git: ComposeGitPort, opts?: StageAllOptions): Promise<void>;
15
+ declare function statusPorcelain(git: ComposeGitPort, opts?: OpOptions): Promise<string>;
16
+ declare function listUntracked(git: ComposeGitPort, opts?: OpOptions): Promise<string[]>;
17
+ declare function showFile(git: ComposeGitPort, ref: string, path: string, opts?: OpOptions): Promise<string>;
18
+ declare function getConfig(git: ComposeGitPort, key: string, opts?: OpOptions): Promise<string | null>;
19
+ declare function commitTree(git: ComposeGitPort, tree: string, parents: string[], message: string, opts?: CommitTreeOptions): Promise<string>;
20
+ declare function applyPatchToIndex(git: ComposeGitPort, patch: string, opts?: ApplyPatchOptions): Promise<void>;
21
+ declare function mergeTree(git: ComposeGitPort, a: string, b: string, opts?: OpOptions): Promise<string>;
22
+ declare function createBranch(git: ComposeGitPort, name: string, startPoint: string, opts?: OpOptions): Promise<void>;
23
+ declare function deleteBranch(git: ComposeGitPort, name: string, opts?: DeleteBranchOptions): Promise<void>;
24
+ declare function updateRef(git: ComposeGitPort, ref: string, sha: string, opts?: UpdateRefOptions): Promise<void>;
25
+ declare function checkout(git: ComposeGitPort, ref: string, opts?: CheckoutOptions): Promise<void>;
26
+ declare function reset(git: ComposeGitPort, ref: string, mode: 'soft' | 'mixed' | 'hard', opts?: OpOptions): Promise<void>;
27
+ declare function cleanWorkdir(git: ComposeGitPort, opts?: CleanOptions): Promise<void>;
28
+ declare function discardWorkdirChanges(git: ComposeGitPort, paths?: string[], opts?: OpOptions): Promise<void>;
29
+ declare function stashPush(git: ComposeGitPort, opts?: StashPushOptions): Promise<void>;
30
+ declare function stashPop(git: ComposeGitPort, opts?: OpOptions): Promise<void>;
31
+ declare function cherryPick(git: ComposeGitPort, sha: string, opts?: OpOptions): Promise<void>;
32
+ declare function cherryPickAbort(git: ComposeGitPort, opts?: OpOptions): Promise<void>;
33
+ //#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 };
@@ -0,0 +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};
@@ -0,0 +1,17 @@
1
+ import { ComposeGitPort } from "../ports/git.js";
2
+ import { GitPort } from "@gitkraken/shared-tools";
3
+
4
+ //#region src/git/require-exec.d.ts
5
+ /**
6
+ * Extract an exec-shaped `GitPort` from a `ComposeGitPort`, throwing a workflow
7
+ * error when `exec` is absent.
8
+ *
9
+ * The shared undo infrastructure (`UndoStore`, `undoOperation`, `validateUndo`,
10
+ * `capturePreOperationState`) still speaks raw exec. Flows that need to hand
11
+ * off to them funnel through this helper. A future refactor can switch those
12
+ * flows to named ops; until then, consumers that want undo support must
13
+ * provide exec.
14
+ */
15
+ declare function requireExec(git: ComposeGitPort, feature: string): GitPort;
16
+ //#endregion
17
+ export { requireExec };
@@ -0,0 +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};
@@ -0,0 +1,22 @@
1
+ import { ComposeGitPort } from "../ports/git.js";
2
+ import { SafetySnapshot } from "./types.js";
3
+
4
+ //#region src/git/safety.d.ts
5
+ declare function verifySnapshot(git: ComposeGitPort, snapshot: SafetySnapshot, options: {
6
+ includeUnstaged: boolean;
7
+ includeUntracked: boolean;
8
+ signal?: AbortSignal;
9
+ }): Promise<{
10
+ valid: boolean;
11
+ error?: string;
12
+ }>;
13
+ declare function verifyResultingDiff(git: ComposeGitPort, baseSha: string, newHeadSha: string, snapshot: SafetySnapshot, signal?: AbortSignal): Promise<{
14
+ valid: boolean;
15
+ error?: string;
16
+ }>;
17
+ declare function verifyMultiBranchDiff(git: ComposeGitPort, baseSha: string, branchHeadShas: string[], snapshot: SafetySnapshot, signal?: AbortSignal): Promise<{
18
+ valid: boolean;
19
+ error?: string;
20
+ }>;
21
+ //#endregion
22
+ export { verifyMultiBranchDiff, verifyResultingDiff, verifySnapshot };
@@ -0,0 +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};
@@ -0,0 +1,43 @@
1
+ //#region src/git/types.d.ts
2
+ /**
3
+ * Hash-based fingerprint of the diff at the start of an AI-driven compose pass.
4
+ *
5
+ * Captured during collection and re-checked at apply time to ensure the working
6
+ * tree (or branch state) hasn't drifted between when the AI saw the diff and
7
+ * when commits are written. Distinct from undo manifests, which record what
8
+ * was changed for rollback purposes.
9
+ *
10
+ * `diffHash` is the authoritative combined hash. `sourceHashes` additionally
11
+ * stores per-source fingerprints for workdir sources so verification failures
12
+ * can report which subset drifted.
13
+ */
14
+ interface SafetySnapshot {
15
+ diffHash: string;
16
+ headSha: string;
17
+ branchSha?: string;
18
+ baseSha?: string;
19
+ /** Per-source content hashes. Populated for workdir source; undefined for branch. */
20
+ sourceHashes?: {
21
+ /** Hash of `git diff --cached` output (HEAD to index). */staged?: string; /** Hash of `git diff` output (index to working tree, tracked files only). */
22
+ unstaged?: string; /** Hash of synthesized diff for untracked files (or null if none). */
23
+ untracked?: string;
24
+ };
25
+ }
26
+ /** Tracks the git mutations performed during an apply phase, for undo manifest construction. */
27
+ 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
+ }[];
41
+ }
42
+ //#endregion
43
+ export { ApplyMutations, SafetySnapshot };
@@ -0,0 +1 @@
1
+ export{};
@@ -0,0 +1,33 @@
1
+ import { ComposeGitPort } from "../../ports/git.js";
2
+
3
+ //#region src/git/utils/conventions.d.ts
4
+ interface CommitConventions {
5
+ format: 'conventional-commits' | 'plain' | 'unknown';
6
+ prefixes?: string[];
7
+ scopeStyle?: 'parenthesized' | 'none';
8
+ casing?: 'lowercase' | 'capitalized';
9
+ issueRefPattern?: string;
10
+ samples: string[];
11
+ }
12
+ interface BranchConventions {
13
+ format: 'prefixed' | 'issue-number' | 'plain' | 'unknown';
14
+ prefixes?: string[];
15
+ separator?: string;
16
+ samples: string[];
17
+ }
18
+ interface ConventionDescriptor {
19
+ commits: CommitConventions;
20
+ branches: BranchConventions;
21
+ sources: string[];
22
+ promptFragment: string;
23
+ }
24
+ interface ExtractConventionsOptions {
25
+ git: ComposeGitPort;
26
+ author?: string;
27
+ commitLimit?: number;
28
+ branchLimit?: number;
29
+ signal?: AbortSignal;
30
+ }
31
+ declare function extractConventions(options: ExtractConventionsOptions): Promise<ConventionDescriptor>;
32
+ //#endregion
33
+ export { CommitConventions, ConventionDescriptor, extractConventions };
@@ -0,0 +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(`
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
+ `];return n.push(x(e)),n.push(S(t)),n.push(`Match these conventions in your output.`),n.join(`
6
+ `)}function x(e){if(e.format===`unknown`&&e.samples.length===0)return`Commit messages: No clear convention detected. Use clear, concise messages.
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
+ `)}\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
+ `;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};
@@ -0,0 +1,4 @@
1
+ //#region src/git/utils/crypto.d.ts
2
+ declare function sha256(text: string): string;
3
+ //#endregion
4
+ export { sha256 };
@@ -0,0 +1 @@
1
+ import{createHash as e}from"node:crypto";function t(t){return e(`sha256`).update(t).digest(`hex`)}export{t as sha256};
@@ -0,0 +1,6 @@
1
+ import { ComposeHunk } from "../../tasks/types.js";
2
+
3
+ //#region src/git/utils/hunk-parser.d.ts
4
+ declare function parseUnifiedDiff(diffText: string, source: ComposeHunk['source'], startIndex?: number): ComposeHunk[];
5
+ //#endregion
6
+ export { parseUnifiedDiff };
@@ -0,0 +1,7 @@
1
+ const e=/^diff --git /m,t=/^@@ /m;function n(e,t,n=0){if(!e.trim())return[];let a=r(e),o=[],s=n;for(let e of a){let{diffHeader:n,fileName:r,originalFileName:a,isRename:c,isBinary:l,binaryPatchData:u,hunkSections:d}=i(e);if(l){o.push({index:s++,fileName:r,diffHeader:n,hunkHeader:``,content:`Binary file change`,additions:0,deletions:0,source:t,isRename:c,originalFileName:a,isBinary:!0,binaryPatchData:u});continue}if(d.length===0){o.push({index:s++,fileName:r,diffHeader:n,hunkHeader:c?`rename`:``,content:c?`Rename from ${a}\nRename to ${r}`:``,additions:0,deletions:0,source:t,isRename:c,originalFileName:a});continue}for(let e of d)o.push({index:s++,fileName:r,diffHeader:n,hunkHeader:e.header,content:e.content,additions:e.additions,deletions:e.deletions,source:t,isRename:!1,originalFileName:a})}return o}function r(t){let n=[],r=t.split(e);for(let e=1;e<r.length;e++)n.push(`diff --git ${r[e]}`);return n}function i(e){let n=e.indexOf(`
2
+ GIT binary patch
3
+ `);if(n!==-1){let t=e.slice(0,n).trimEnd(),r=e.slice(n+1),i=a(t),{isRename:s,originalFileName:c}=o(t);return{diffHeader:t,fileName:i,originalFileName:c,isRename:s,isBinary:!0,binaryPatchData:r,hunkSections:[]}}let r=e.search(t),i,c;r===-1?(i=e.trimEnd(),c=``):(i=e.slice(0,r).trimEnd(),c=e.slice(r));let l=a(i),{isRename:u,originalFileName:d}=o(i),f=c?s(c):[];return{diffHeader:i,fileName:l,originalFileName:d,isRename:u,isBinary:!1,hunkSections:f}}function a(e){let t=e.split(`
4
+ `);for(let e of t)if(e.startsWith(`+++ b/`))return e.slice(6);for(let e of t)if(e.startsWith(`rename to `))return e.slice(10);return t[0]?.match(/^diff --git a\/.+ b\/(.+)$/)?.[1]??`unknown`}function o(e){let t=e.split(`
5
+ `),n,r=!1;for(let e of t)e.startsWith(`rename from `)&&(n=e.slice(12),r=!0);return{isRename:r,originalFileName:n}}function s(e){let t=[],n=e.split(`
6
+ `),r=``,i=[];for(let e of n)e.startsWith(`@@ `)?(r&&t.push(c(r,i)),r=e,i=[]):r&&i.push(e);return r&&t.push(c(r,i)),t}function c(e,t){for(;t.length>0&&t[t.length-1]===``;)t.pop();let n=0,r=0;for(let e of t)e.startsWith(`+`)&&!e.startsWith(`+++`)?n++:e.startsWith(`-`)&&!e.startsWith(`---`)&&r++;return{header:e,content:t.join(`
7
+ `),additions:n,deletions:r}}export{n as parseUnifiedDiff};
@@ -0,0 +1,6 @@
1
+ import { ComposeHunk } from "../../tasks/types.js";
2
+
3
+ //#region src/git/utils/patch-builder.d.ts
4
+ declare function buildPatchForCommit(hunks: ComposeHunk[]): string;
5
+ //#endregion
6
+ export { buildPatchForCommit };