@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,6 @@
1
1
  import { ComposeHunk } from "../../tasks/types.js";
2
2
 
3
3
  //#region src/git/utils/hunk-parser.d.ts
4
- declare function parseUnifiedDiff(diffText: string, source: ComposeHunk['source'], startIndex?: number): ComposeHunk[];
4
+ declare function parseUnifiedDiff(diffText: string, startIndex?: number): ComposeHunk[];
5
5
  //#endregion
6
6
  export { parseUnifiedDiff };
@@ -1,4 +1,4 @@
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(`
1
+ const e=/^diff --git /m,t=/^@@ /m;function n(e,t=0){if(!e.trim())return[];let n=r(e),a=[],o=t;for(let e of n){let{diffHeader:t,fileName:n,originalFileName:r,isRename:s,isBinary:c,binaryPatchData:l,hunkSections:u}=i(e);if(c){a.push({index:o++,fileName:n,diffHeader:t,hunkHeader:``,content:`Binary file change`,additions:0,deletions:0,isRename:s,originalFileName:r,isBinary:!0,binaryPatchData:l});continue}if(u.length===0){a.push({index:o++,fileName:n,diffHeader:t,hunkHeader:s?`rename`:``,content:s?`Rename from ${r}\nRename to ${n}`:``,additions:0,deletions:0,isRename:s,originalFileName:r});continue}for(let e of u)a.push({index:o++,fileName:n,diffHeader:t,hunkHeader:e.header,content:e.content,additions:e.additions,deletions:e.deletions,isRename:!1,originalFileName:r})}return a}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
2
  GIT binary patch
3
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
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(`
@@ -1,6 +1,6 @@
1
1
  import { ComposeGitPort } from "../../ports/git.js";
2
2
 
3
3
  //#region src/git/utils/temp-index.d.ts
4
- declare function withUntrackedInTempIndex<T>(git: ComposeGitPort, fn: (indexEnv: Record<string, string>) => Promise<T>, signal?: AbortSignal): Promise<T>;
4
+ declare function withUntrackedInTempIndex<T>(git: ComposeGitPort, fn: (indexEnv: Record<string, string>) => Promise<T>): Promise<T>;
5
5
  //#endregion
6
6
  export { withUntrackedInTempIndex };
@@ -1 +1 @@
1
- import{ComposeGitPortMissingOpError as e}from"../../ports/git.js";import{gitDir as t,listUntracked as n,stageAll as r}from"../port-dispatch.js";import{randomUUID as i}from"node:crypto";import*as a from"node:fs";import*as o from"node:os";import*as s from"node:path";async function c(e,t,c){let u=await l(e,c),d=s.join(o.tmpdir(),`compose-tmp-idx-${i()}`);try{a.copyFileSync(u,d);let i={GIT_INDEX_FILE:d},o=await n(e,{signal:c});return o.length>0&&await r(e,{intentToAdd:!0,paths:o,indexEnv:i,signal:c}),await t(i)}finally{try{a.unlinkSync(d)}catch{}}}async function l(n,r){let i=await t(n,{signal:r});if(!i)throw new e(`gitDir`,`The temp-index helper needs to resolve the real .git/index path. Provide 'gitDir' (or 'exec').`);return s.join(i,`index`)}export{c as withUntrackedInTempIndex};
1
+ import{ComposeGitPortMissingOpError as e}from"../../ports/git.js";import{gitDir as t,listUntracked as n,stageAll as r}from"../port-dispatch.js";import{randomUUID as i}from"node:crypto";import*as a from"node:fs";import*as o from"node:os";import*as s from"node:path";async function c(e,t){let c=await l(e),u=s.join(o.tmpdir(),`compose-tmp-idx-${i()}`);try{a.copyFileSync(c,u);let i={GIT_INDEX_FILE:u},o=await n(e);return o.length>0&&await r(e,{intentToAdd:!0,paths:o,indexEnv:i}),await t(i)}finally{try{a.unlinkSync(u)}catch{}}}async function l(n){let r=await t(n);if(!r)throw new e(`gitDir`,`The temp-index helper needs to resolve the real .git/index path. Provide 'gitDir' (or 'exec').`);return s.join(r,`index`)}export{c as withUntrackedInTempIndex};
package/dist/index.d.ts CHANGED
@@ -2,11 +2,12 @@ import { PhaseDetail, ProgressEvent, TokenUsage } from "./ai/types.js";
2
2
  import { AIError, AIErrorCode } from "./errors/ai.js";
3
3
  import { GitError, GitErrorCode } from "./errors/git.js";
4
4
  import { TaskInputError } from "./errors/task.js";
5
- import { WorkflowError, WorkflowErrorCode, WorkflowInputError } from "./errors/workflow.js";
5
+ import { ComposeWorkflowError, ComposeWorkflowErrorCode, ComposeWorkflowInputError } from "./errors/workflow.js";
6
6
  import { ApplyPatchOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitOps, ComposeGitPort, ComposeGitPortMissingOpError, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, GitCommitIdentity, IndexScopedOptions, LogOptions, OpOptions, SigningConfig, StageAllOptions, StashPushOptions, UpdateRefOptions } from "./ports/git.js";
7
7
  import { BranchGroup, BranchOrdering, BranchPartition, CommitDetail, CommitSuggestion, ComposeHunk, ComposeTaskName, PromptOverride } from "./tasks/types.js";
8
8
  import { ConventionDescriptor } from "./git/utils/conventions.js";
9
9
  import { AfterModelCallInfo, BeforeModelCallInfo, BeforePromptInfo, OnAfterModelCall, OnBeforeModelCall, OnBeforePrompt, estimatedCharactersPerToken } from "./tasks/ai-loop.js";
10
+ import { REDACTED_HUNK_CONTENT, RedactHunkContent } from "./tasks/utils/redact.js";
10
11
  import { AnalysisResult, AnalyzeInput, AnalyzeOutput, Concern, ConcernDependency, HunkAnnotation, HunkOrderingConstraint } from "./tasks/analyze/types.js";
11
12
  import { GroupInput, GroupOutput, GroupingResult, GroupingStrategy } from "./tasks/group/types.js";
12
13
  import { OrderInput, OrderOutput, OrderingResult } from "./tasks/order/types.js";
@@ -14,6 +15,7 @@ import { QuickComposeInput, QuickComposeOutput, QuickComposeResult } from "./tas
14
15
  import { ComposeSession } from "./tasks/session.js";
15
16
  import { PartitionInput, PartitionOutput, PartitionResult, PartitionStrategy } from "./tasks/partition/types.js";
16
17
  import { ComposeContext, ComposeSource, ComposeTarget } from "./workflows/types.js";
18
+ import { StashConflict } from "./git/types.js";
17
19
  import { ComposeBranchPlan } from "./git/apply-target.js";
18
20
  import { analyze } from "./tasks/analyze/analyze.js";
19
21
  import { ComposeGroupInput, ComposeGroupOutput } from "./tasks/compose-group/types.js";
@@ -25,4 +27,5 @@ import { quickCompose } from "./tasks/quick-compose/quick-compose.js";
25
27
  import { ApplyComposePlanInput, ApplyComposePlanResult, ComposeApplyPlan, ComposeDepth, ComposeInput, ComposePlan, ComposePlanInput, ComposePlanResult, ComposeProgressEvent, ComposeResult, ComposeStrategy, applyComposePlan, compose, composePlan, defaultPhaseMessages } from "./workflows/compose.js";
26
28
  import { SplitInput, SplitPlan, SplitProgressEvent, SplitResult, split } from "./workflows/split.js";
27
29
  import { UndoComposeInput, ValidateUndoComposeInput, listUndoIds, undoCompose, validateUndoCompose } from "./workflows/undo.js";
28
- export { AIError, type AIErrorCode, type AfterModelCallInfo, type AnalysisResult, type AnalyzeInput, type AnalyzeOutput, type ApplyComposePlanInput, type ApplyComposePlanResult, type ApplyPatchOptions, type BeforeModelCallInfo, type BeforePromptInfo, type BranchGroup, type BranchOrdering, type BranchPartition, type CheckoutOptions, type CleanOptions, type CommitDetail, type CommitSuggestion, type CommitTreeOptions, type ComposeApplyPlan, type ComposeBranchPlan, type ComposeContext, type ComposeDepth, type ComposeGitOps, type ComposeGitPort, ComposeGitPortMissingOpError, type ComposeGroupInput, type ComposeGroupOutput, type ComposeHunk, type ComposeInput, type ComposePlan, type ComposePlanInput, type ComposePlanResult, type ComposeProgressEvent, type ComposeResult, type ComposeSession, type ComposeSource, type ComposeStrategy, type ComposeTarget, type ComposeTaskName, type Concern, type ConcernDependency, type ConventionDescriptor, type DeleteBranchOptions, type DiffTreeOptions, type ForEachRefOptions, type GitCommitIdentity, GitError, type GitErrorCode, type GroupInput, type GroupOutput, type GroupingResult, type GroupingStrategy, type HunkAnnotation, type HunkOrderingConstraint, type IndexScopedOptions, type LogOptions, type OnAfterModelCall, type OnBeforeModelCall, type OnBeforePrompt, type OpOptions, type OrderInput, type OrderOutput, type OrderingResult, type PartitionInput, type PartitionOutput, type PartitionResult, type PartitionStrategy, type PhaseDetail, type ProgressEvent, type PromptOverride, type QuickComposeInput, type QuickComposeOutput, type QuickComposeResult, type SigningConfig, type SplitInput, type SplitPlan, type SplitProgressEvent, type SplitResult, type StageAllOptions, type StashPushOptions, TaskInputError, type TokenUsage, type UndoComposeInput, type UpdateRefOptions, type ValidateUndoComposeInput, WorkflowError, type WorkflowErrorCode, WorkflowInputError, analyze, applyComposePlan, compose, composeGroup, composePlan, defaultPhaseMessages, estimatedCharactersPerToken, group, listUndoIds, order, partition, quickCompose, split, undoCompose, validateUndoCompose };
30
+ import { UndoForceOptions } from "@gitkraken/shared-tools";
31
+ export { AIError, type AIErrorCode, type AfterModelCallInfo, type AnalysisResult, type AnalyzeInput, type AnalyzeOutput, type ApplyComposePlanInput, type ApplyComposePlanResult, type ApplyPatchOptions, type BeforeModelCallInfo, type BeforePromptInfo, type BranchGroup, type BranchOrdering, type BranchPartition, type CheckoutOptions, type CleanOptions, type CommitDetail, type CommitSuggestion, type CommitTreeOptions, type ComposeApplyPlan, type ComposeBranchPlan, type ComposeContext, type ComposeDepth, type ComposeGitOps, type ComposeGitPort, ComposeGitPortMissingOpError, type ComposeGroupInput, type ComposeGroupOutput, type ComposeHunk, type ComposeInput, type ComposePlan, type ComposePlanInput, type ComposePlanResult, type ComposeProgressEvent, type ComposeResult, type ComposeSession, type ComposeSource, type ComposeStrategy, type ComposeTarget, type ComposeTaskName, ComposeWorkflowError, type ComposeWorkflowErrorCode, ComposeWorkflowInputError, type Concern, type ConcernDependency, type ConventionDescriptor, type DeleteBranchOptions, type DiffTreeOptions, type ForEachRefOptions, type GitCommitIdentity, GitError, type GitErrorCode, type GroupInput, type GroupOutput, type GroupingResult, type GroupingStrategy, type HunkAnnotation, type HunkOrderingConstraint, type IndexScopedOptions, type LogOptions, type OnAfterModelCall, type OnBeforeModelCall, type OnBeforePrompt, type OpOptions, type OrderInput, type OrderOutput, type OrderingResult, type PartitionInput, type PartitionOutput, type PartitionResult, type PartitionStrategy, type PhaseDetail, type ProgressEvent, type PromptOverride, type QuickComposeInput, type QuickComposeOutput, type QuickComposeResult, REDACTED_HUNK_CONTENT, type RedactHunkContent, type SigningConfig, type SplitInput, type SplitPlan, type SplitProgressEvent, type SplitResult, type StageAllOptions, type StashConflict, type StashPushOptions, TaskInputError, type TokenUsage, type UndoComposeInput, type UndoForceOptions, type UpdateRefOptions, type ValidateUndoComposeInput, analyze, applyComposePlan, compose, composeGroup, composePlan, defaultPhaseMessages, estimatedCharactersPerToken, group, listUndoIds, order, partition, quickCompose, split, undoCompose, validateUndoCompose };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{WorkflowError as e,WorkflowInputError as t}from"./errors/workflow.js";import{AIError as n}from"./errors/ai.js";import{GitError as r}from"./errors/git.js";import{TaskInputError as i}from"./errors/task.js";import{ComposeGitPortMissingOpError as a}from"./ports/git.js";import{estimatedCharactersPerToken as o}from"./tasks/ai-loop.js";import{analyze as s}from"./tasks/analyze/analyze.js";import{composeGroup as c}from"./tasks/compose-group/compose-group.js";import{group as l}from"./tasks/group/group.js";import{order as u}from"./tasks/order/order.js";import{partition as d}from"./tasks/partition/partition.js";import{quickCompose as f}from"./tasks/quick-compose/quick-compose.js";import{applyComposePlan as p,compose as m,composePlan as h,defaultPhaseMessages as g}from"./workflows/compose.js";import{split as _}from"./workflows/split.js";import{listUndoIds as v,undoCompose as y,validateUndoCompose as b}from"./workflows/undo.js";export{n as AIError,a as ComposeGitPortMissingOpError,r as GitError,i as TaskInputError,e as WorkflowError,t as WorkflowInputError,s as analyze,p as applyComposePlan,m as compose,c as composeGroup,h as composePlan,g as defaultPhaseMessages,o as estimatedCharactersPerToken,l as group,v as listUndoIds,u as order,d as partition,f as quickCompose,_ as split,y as undoCompose,b as validateUndoCompose};
1
+ import{AIError as e}from"./errors/ai.js";import{GitError as t}from"./errors/git.js";import{TaskInputError as n}from"./errors/task.js";import{ComposeWorkflowError as r,ComposeWorkflowInputError as i}from"./errors/workflow.js";import{ComposeGitPortMissingOpError as a}from"./ports/git.js";import{estimatedCharactersPerToken as o}from"./tasks/ai-loop.js";import{REDACTED_HUNK_CONTENT as s}from"./tasks/utils/redact.js";import{analyze as c}from"./tasks/analyze/analyze.js";import{composeGroup as l}from"./tasks/compose-group/compose-group.js";import{group as u}from"./tasks/group/group.js";import{order as d}from"./tasks/order/order.js";import{partition as f}from"./tasks/partition/partition.js";import{quickCompose as p}from"./tasks/quick-compose/quick-compose.js";import{applyComposePlan as m,compose as h,composePlan as g,defaultPhaseMessages as _}from"./workflows/compose.js";import{split as v}from"./workflows/split.js";import{listUndoIds as y,undoCompose as b,validateUndoCompose as x}from"./workflows/undo.js";export{e as AIError,a as ComposeGitPortMissingOpError,r as ComposeWorkflowError,i as ComposeWorkflowInputError,t as GitError,s as REDACTED_HUNK_CONTENT,n as TaskInputError,c as analyze,m as applyComposePlan,h as compose,l as composeGroup,g as composePlan,_ as defaultPhaseMessages,o as estimatedCharactersPerToken,u as group,y as listUndoIds,d as order,f as partition,p as quickCompose,v as split,b as undoCompose,x as validateUndoCompose};
@@ -28,12 +28,19 @@ interface ComposeGitOps {
28
28
  currentBranch?: (opts?: OpOptions) => Promise<string | null>;
29
29
  /** Best common ancestor between two refs. Covers `merge-base a b`. */
30
30
  mergeBase?: (a: string, b: string, opts?: OpOptions) => Promise<string>;
31
+ /** Check whether `ancestor` is an ancestor of `descendant`. Covers `merge-base --is-ancestor`. */
32
+ mergeBaseIsAncestor?: (ancestor: string, descendant: string, opts?: OpOptions) => Promise<boolean>;
31
33
  /** Tree-to-tree unified diff. Covers `diff --binary from to` and `diff --name-only from to`. */
32
34
  diffTree?: (from: string, to: string, opts?: DiffTreeOptions) => Promise<string>;
33
35
  /** Commit log with custom format. Covers various `git log --format=... <range>` / `-1 <sha>` uses. */
34
36
  log?: (opts: LogOptions) => Promise<string>;
35
37
  /** Ref enumeration. Covers `for-each-ref`. */
36
38
  forEachRef?: (opts: ForEachRefOptions) => Promise<string>;
39
+ /**
40
+ * List commits in a range alongside their immediate children. Covers `rev-list --children`.
41
+ * `children` includes every direct child, even those outside the queried range.
42
+ */
43
+ revListChildren?: (opts: RevListChildrenOptions) => Promise<RevListChildrenEntry[]>;
37
44
  /** Write the current (or temp) index to a tree object. Covers `write-tree`. */
38
45
  writeTree?: (opts?: IndexScopedOptions) => Promise<string>;
39
46
  /** Load a tree's contents into the current (or temp) index. Covers `read-tree <sha>`. */
@@ -52,6 +59,8 @@ interface ComposeGitOps {
52
59
  commitTree?: (tree: string, parents: string[], message: string, opts?: CommitTreeOptions) => Promise<string>;
53
60
  /** Apply a unified diff patch to the current (or temp) index. Covers `apply --cached --allow-binary-replacement`. */
54
61
  applyPatchToIndex?: (patch: string, opts?: ApplyPatchOptions) => Promise<void>;
62
+ /** Apply a unified diff patch to the working tree (no staging). Covers `apply --allow-binary-replacement`. */
63
+ applyPatchToWorkdir?: (patch: string, opts?: ApplyPatchToWorkdirOptions) => Promise<void>;
55
64
  /** 3-way merge producing a tree SHA. Covers `merge-tree --write-tree a b`. */
56
65
  mergeTree?: (a: string, b: string, opts?: OpOptions) => Promise<string>;
57
66
  /** Create a branch at `startPoint`. Covers `branch name start`. */
@@ -70,8 +79,10 @@ interface ComposeGitOps {
70
79
  discardWorkdirChanges?: (paths?: string[], opts?: OpOptions) => Promise<void>;
71
80
  /** Push current workdir state to stash. Covers `stash push --include-untracked -m msg`. */
72
81
  stashPush?: (opts?: StashPushOptions) => Promise<void>;
73
- /** Pop the top stash entry. Covers `stash pop`. */
74
- stashPop?: (opts?: OpOptions) => Promise<void>;
82
+ /** Pop the top stash entry. `withIndex: true` maps to `--index`. Covers `stash pop` and `stash pop --index`. */
83
+ stashPop?: (opts?: StashPopOptions) => Promise<void>;
84
+ /** Drop a specific stash entry. Covers `stash drop stash@{N}`. */
85
+ stashDrop?: (stashRef: string, opts?: OpOptions) => Promise<void>;
75
86
  /**
76
87
  * Apply an existing commit onto the current HEAD, producing a new commit.
77
88
  * Covers `git cherry-pick <sha>`. Throws when conflicts make the pick fail —
@@ -84,10 +95,16 @@ interface ComposeGitOps {
84
95
  /** Abort an in-progress cherry-pick. Covers `git cherry-pick --abort`. */
85
96
  cherryPickAbort?: (opts?: OpOptions) => Promise<void>;
86
97
  }
87
- /** Options passed to every op — all ops can be cancelled via signal. */
88
- interface OpOptions {
89
- signal?: AbortSignal;
90
- }
98
+ /**
99
+ * Options passed to every op. Currently empty — git operations are not
100
+ * cancellable in this library; they run to completion to avoid leaving the
101
+ * repo in a half-mutated state. Domain workflows route cancellation only
102
+ * into the AI loop.
103
+ *
104
+ * Kept as an interface so individual ops that need extra options (e.g.
105
+ * {@link CheckoutOptions}, {@link UpdateRefOptions}) can extend it.
106
+ */
107
+ interface OpOptions {}
91
108
  interface DiffTreeOptions extends OpOptions {
92
109
  /** Include binary hunks as base85. Default true. */
93
110
  binary?: boolean;
@@ -102,12 +119,26 @@ interface LogOptions extends OpOptions {
102
119
  noMerges?: boolean;
103
120
  reverse?: boolean;
104
121
  author?: string;
122
+ /** Force parents-before-children ordering. Maps to `--topo-order`. */
123
+ topoOrder?: boolean;
105
124
  }
106
125
  interface ForEachRefOptions extends OpOptions {
107
126
  format: string;
108
127
  prefix?: string;
109
128
  sort?: string;
110
129
  limit?: number;
130
+ /** When set, only returns refs pointing at the given commit SHA. Maps to `--points-at=<sha>`. */
131
+ pointsAt?: string;
132
+ }
133
+ interface RevListChildrenOptions extends OpOptions {
134
+ /** Commit range to query, e.g. `from..to`. */
135
+ range: string;
136
+ /** Reverse the output (oldest first). Maps to `--reverse`. */
137
+ reverse?: boolean;
138
+ }
139
+ interface RevListChildrenEntry {
140
+ sha: string;
141
+ children: string[];
111
142
  }
112
143
  /**
113
144
  * Index-scoped options.
@@ -154,6 +185,10 @@ interface ApplyPatchOptions extends IndexScopedOptions {
154
185
  /** Allow binary hunks in the patch. Default true. */
155
186
  allowBinaryReplacement?: boolean;
156
187
  }
188
+ interface ApplyPatchToWorkdirOptions extends OpOptions {
189
+ /** Allow binary hunks in the patch. Default true. */
190
+ allowBinaryReplacement?: boolean;
191
+ }
157
192
  interface DeleteBranchOptions extends OpOptions {
158
193
  force?: boolean;
159
194
  }
@@ -179,6 +214,12 @@ interface StashPushOptions extends OpOptions {
179
214
  message?: string;
180
215
  includeUntracked?: boolean;
181
216
  }
217
+ interface StashPopOptions extends OpOptions {
218
+ /** Restore staged-vs-unstaged split where possible. Maps to `--index`. */
219
+ withIndex?: boolean;
220
+ /** Specific stash entry to pop (e.g. `stash@{2}`). Defaults to the topmost. */
221
+ ref?: string;
222
+ }
182
223
  /**
183
224
  * The full port shape the library accepts. Exec is optional — if absent,
184
225
  * every flow must be fully served by high-level ops.
@@ -196,4 +237,4 @@ declare class ComposeGitPortMissingOpError extends Error {
196
237
  constructor(op: string, hint?: string);
197
238
  }
198
239
  //#endregion
199
- export { ApplyPatchOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitOps, ComposeGitPort, ComposeGitPortMissingOpError, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, GitCommitIdentity, IndexScopedOptions, LogOptions, OpOptions, SigningConfig, StageAllOptions, StashPushOptions, UpdateRefOptions };
240
+ export { ApplyPatchOptions, ApplyPatchToWorkdirOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitOps, ComposeGitPort, ComposeGitPortMissingOpError, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, GitCommitIdentity, IndexScopedOptions, LogOptions, OpOptions, RevListChildrenEntry, RevListChildrenOptions, SigningConfig, StageAllOptions, StashPopOptions, StashPushOptions, UpdateRefOptions };
@@ -34,15 +34,14 @@ interface AfterModelCallInfo {
34
34
  type OnBeforeModelCall = (info: BeforeModelCallInfo) => void;
35
35
  type OnAfterModelCall = (info: AfterModelCallInfo) => void;
36
36
  /**
37
- * Gating hook fired immediately before each model.generate call.
37
+ * Gating hook fired immediately before each `model.generate` call. Returning
38
+ * `false` aborts the loop with a native `AbortError` (DOMException with
39
+ * `name === 'AbortError'`). Intended for cost warnings, large-prompt
40
+ * confirmations, policy checks, and similar caller-side UX.
38
41
  *
39
- * Returning `false` aborts the workflow; the library throws the resulting
40
- * rejection as a `WorkflowError('CANCELLED')`. Intended for cost warnings,
41
- * large-prompt confirmations, policy checks, and similar consumer-side UX.
42
- *
43
- * The library provides `tokenEstimate` as a rough `charCount / 2.8`
44
- * approximation — reasonable for code-heavy prompts but not authoritative.
45
- * Consumers can ignore it and estimate their own way.
42
+ * `tokenEstimate` is a rough `charCount / 2.8` approximation — reasonable for
43
+ * code-heavy prompts but not authoritative. Callers can ignore it and
44
+ * estimate their own way.
46
45
  */
47
46
  interface BeforePromptInfo {
48
47
  phase: string;
@@ -65,16 +64,11 @@ interface AiLoopParams<T> {
65
64
  onProgress?: (event: ProgressEvent) => void;
66
65
  validate: (responseText: string) => ValidationResult<T>;
67
66
  taskName: string;
68
- signal?: AbortSignal;
69
- /** Phase label stamped on Before/After model-call hook events. */
67
+ cancellation?: AbortSignal;
68
+ /** Phase label stamped on Before/After model-call hook events. Defaults to `taskName`. */
70
69
  phase?: string;
71
70
  onBeforeModelCall?: OnBeforeModelCall;
72
71
  onAfterModelCall?: OnAfterModelCall;
73
- /**
74
- * Gating hook. Called before each model.generate. If it returns `false`, the
75
- * loop aborts with an AbortError (caught by the workflow and surfaced as a
76
- * `WorkflowError('CANCELLED')`).
77
- */
78
72
  onBeforePrompt?: OnBeforePrompt;
79
73
  promptVersion?: string;
80
74
  }
@@ -1,2 +1,2 @@
1
- import{WorkflowError as e}from"../errors/workflow.js";import{AIError as t}from"../errors/ai.js";import{TaskInputError as n}from"../errors/task.js";import{appendAssistantMessage as r,appendUserMessage as i,createAiSession as a}from"../ai/session.js";import{addUsage as o}from"../ai/types.js";import{randomUUID as s}from"node:crypto";const c=2.8;async function l(o){let{model:c,userPrompt:l,maxRetries:f=3,maxTokens:p=16384,onProgress:m,validate:h,taskName:g,signal:_,phase:v=g,onBeforeModelCall:y,onAfterModelCall:b,promptVersion:x}=o;if(!o.session&&!o.systemPrompt)throw new n(`${g}: either 'session' or 'systemPrompt' must be provided`);let S=Date.now(),C;o.session?C=i(o.session,l):(C=a(o.systemPrompt),C=i(C,l));let w,T=[],E=f+1,D=[];for(let t=0;t<=f;t++){_?.throwIfAborted();let n=t+1;if(m?.({type:`attempt_start`,attempt:n,maxAttempts:E}),o.onBeforePrompt){let t=(C.systemPrompt?.length??0)+C.messages.reduce((e,t)=>e+t.content.length,0);if(!await o.onBeforePrompt({phase:v,attempt:n,maxAttempts:E,charCount:t,messageCount:C.messages.length,tokenEstimate:Math.ceil(t/2.8)}))throw new e(`CANCELLED`,`Prompt gate declined by consumer (onBeforePrompt returned false)`)}let a=s();D.push(a);let l=Date.now();y?.({phase:v,attempt:n,maxAttempts:E,callId:a});let g;try{g=await c.generate({system:C.systemPrompt,messages:[...C.messages],maxTokens:p,temperature:0,signal:_})}catch(t){let r=t instanceof Error?t.message:String(t);throw b?.({phase:v,attempt:n,callId:a,success:!1,durationMs:Date.now()-l,error:{code:t?.code??`UNKNOWN`,message:r},promptVersion:x}),u(t)||_?.aborted?new e(`CANCELLED`,r,{cause:t}):t}b?.({phase:v,attempt:n,callId:a,success:!0,durationMs:Date.now()-l,usage:g.usage,promptVersion:x}),w=d(w,g.usage);let O=h(g.text);if(O.valid&&O.result!==void 0)return C=r(C,g.text),{result:O.result,session:C,usage:w,attempts:n,durationMs:Date.now()-S,callIds:D};T=O.errors??[`Unknown validation error`],m?.({type:`validation_failed`,attempt:n,maxAttempts:E,errors:T}),t<f&&O.retryFeedback&&(C=r(C,g.text),C=i(C,O.retryFeedback))}throw new t(`VALIDATION_EXHAUSTED`,`Failed to get valid ${g} after ${E} attempts. Last errors:\n${T.join(`
2
- `)}`)}function u(e){return e instanceof Error&&(e.name===`AbortError`||e.name===`DOMException`)}function d(e,t){if(!e)return t;if(!t)return e;let n={...e};return o(n,t),n}function f(e){let t=e.match(/<output>([\s\S]*?)(?:<\/output>|$)/)?.[1]?.trim()??null;return t?t.match(/^```(?:\w*)\n?([\s\S]*?)```$/)?.[1]?.trim()??t:null}export{c as estimatedCharactersPerToken,f as extractOutputTag,l as runAiLoop};
1
+ import{AIError as e}from"../errors/ai.js";import{TaskInputError as t}from"../errors/task.js";import{appendAssistantMessage as n,appendUserMessage as r,createAiSession as i}from"../ai/session.js";import{addUsage as a}from"../ai/types.js";import{randomUUID as o}from"node:crypto";const s=2.8;async function c(a){let{model:s,userPrompt:c,maxRetries:d=3,maxTokens:f=16384,onProgress:p,validate:m,taskName:h,cancellation:g,phase:_=h,onBeforeModelCall:v,onAfterModelCall:y,promptVersion:b}=a;if(!a.session&&!a.systemPrompt)throw new t(`${h}: either 'session' or 'systemPrompt' must be provided`);let x=Date.now(),S;a.session?S=r(a.session,c):(S=i(a.systemPrompt),S=r(S,c));let C,w=[],T=d+1,E=[];for(let e=0;e<=d;e++){g?.throwIfAborted();let t=e+1;if(p?.({type:`attempt_start`,attempt:t,maxAttempts:T}),a.onBeforePrompt){let e=(S.systemPrompt?.length??0)+S.messages.reduce((e,t)=>e+t.content.length,0);if(!await a.onBeforePrompt({phase:_,attempt:t,maxAttempts:T,charCount:e,messageCount:S.messages.length,tokenEstimate:Math.ceil(e/2.8)}))throw new DOMException(`Prompt gate declined by caller (onBeforePrompt returned false)`,`AbortError`)}let i=o();E.push(i);let c=Date.now();v?.({phase:_,attempt:t,maxAttempts:T,callId:i});let h;try{h=await s.generate({system:S.systemPrompt,messages:[...S.messages],maxTokens:f,temperature:0,signal:g})}catch(e){let n=e instanceof Error?e.message:String(e);throw y?.({phase:_,attempt:t,callId:i,success:!1,durationMs:Date.now()-c,error:{code:e?.code??`UNKNOWN`,message:n},promptVersion:b}),l(e)||g?.aborted?new DOMException(n,`AbortError`):e}y?.({phase:_,attempt:t,callId:i,success:!0,durationMs:Date.now()-c,usage:h.usage,promptVersion:b}),C=u(C,h.usage);let D=m(h.text);if(D.valid&&D.result!==void 0)return S=n(S,h.text),{result:D.result,session:S,usage:C,attempts:t,durationMs:Date.now()-x,callIds:E};w=D.errors??[`Unknown validation error`],p?.({type:`validation_failed`,attempt:t,maxAttempts:T,errors:w}),e<d&&D.retryFeedback&&(S=n(S,h.text),S=r(S,D.retryFeedback))}throw new e(`VALIDATION_EXHAUSTED`,`Failed to get valid ${h} after ${T} attempts. Last errors:\n${w.join(`
2
+ `)}`)}function l(e){return e instanceof Error&&(e.name===`AbortError`||e.name===`DOMException`)}function u(e,t){if(!e)return t;if(!t)return e;let n={...e};return a(n,t),n}function d(e){let t=e.match(/<output>([\s\S]*?)(?:<\/output>|$)/)?.[1]?.trim()??null;return t?t.match(/^```(?:\w*)\n?([\s\S]*?)```$/)?.[1]?.trim()??t:null}export{s as estimatedCharactersPerToken,d as extractOutputTag,c as runAiLoop};
@@ -1 +1 @@
1
- import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{buildAnalyzeSystemPrompt as r,buildAnalyzeUserPrompt as i}from"./prompt.js";import{validateAnalyzeResponse as a}from"./validator.js";async function o(o){let s=o.hunks;if(!s||s.length===0)throw new e(`No hunks provided: supply 'hunks'`);if(o.session?.completedSteps.analyze)return{result:o.session.completedSteps.analyze,session:o.session,attempts:0,durationMs:0,callIds:[]};let c=n(i({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions}),o.promptOverride),l=s.length,u=await t({model:o.model,systemPrompt:o.session?void 0:o.promptOverride?.system??r(),userPrompt:c,session:o.session,maxRetries:o.maxRetries,onProgress:o.onProgress,signal:o.signal,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>a(e,l),taskName:`analyze`}),d=o.session?.completedSteps??{},f={...u.session,completedSteps:{...d,analyze:u.result}};return{result:u.result,session:f,usage:u.usage,attempts:u.attempts,durationMs:u.durationMs,callIds:u.callIds}}export{o as analyze};
1
+ import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{buildAnalyzeSystemPrompt as r,buildAnalyzeUserPrompt as i}from"./prompt.js";import{validateAnalyzeResponse as a}from"./validator.js";async function o(o){let s=o.hunks;if(!s||s.length===0)throw new e(`No hunks provided: supply 'hunks'`);if(o.session?.completedSteps.analyze)return{result:o.session.completedSteps.analyze,session:o.session,attempts:0,durationMs:0,callIds:[]};let c=n(i({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions,redactHunkContent:o.redactHunkContent}),o.promptOverride),l=s.length,u=await t({model:o.model,systemPrompt:o.session?void 0:o.promptOverride?.system??r(),userPrompt:c,session:o.session,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>a(e,l),taskName:`analyze`}),d=o.session?.completedSteps??{},f={...u.session,completedSteps:{...d,analyze:u.result}};return{result:u.result,session:f,usage:u.usage,attempts:u.attempts,durationMs:u.durationMs,callIds:u.callIds}}export{o as analyze};
@@ -1,4 +1,5 @@
1
1
  import { ComposeHunk } from "../types.js";
2
+ import { RedactHunkContent } from "../utils/redact.js";
2
3
 
3
4
  //#region src/tasks/analyze/prompt.d.ts
4
5
  declare function buildAnalyzeSystemPrompt(): string;
@@ -11,6 +12,7 @@ declare function buildAnalyzeUserPrompt(params: {
11
12
  issueUrl?: string;
12
13
  pullRequestUrl?: string;
13
14
  instructions?: string;
15
+ redactHunkContent?: RedactHunkContent;
14
16
  }): string;
15
17
  //#endregion
16
18
  export { buildAnalyzeSystemPrompt, buildAnalyzeUserPrompt };
@@ -1,4 +1,4 @@
1
- import{analysisResultSchema as e}from"./schema.js";import{z as t}from"zod";const n=JSON.stringify(t.toJSONSchema(e),null,2);function r(){return`You are an advanced AI code analysis assistant. Your task is to deeply analyze a set of code changes (provided as diff hunks) and produce a structured understanding of what the changes accomplish, what concerns they address, and how hunks relate to each other.
1
+ import{hunkContentForPrompt as e}from"../utils/redact.js";import{analysisResultSchema as t}from"./schema.js";import{z as n}from"zod";const r=JSON.stringify(n.toJSONSchema(t),null,2);function i(){return`You are an advanced AI code analysis assistant. Your task is to deeply analyze a set of code changes (provided as diff hunks) and produce a structured understanding of what the changes accomplish, what concerns they address, and how hunks relate to each other.
2
2
 
3
3
  Your analysis must produce:
4
4
  1. **Title** — a short, PR-style title for the changes (under 72 characters, imperative mood, e.g. "Add user authentication and session management"). This should capture the essence of what the changes accomplish.
@@ -25,10 +25,10 @@ Rules:
25
25
  Output your analysis as JSON inside a <output> tag matching this schema:
26
26
 
27
27
  \`\`\`json
28
- ${n}
28
+ ${r}
29
29
  \`\`\`
30
30
 
31
- Return only the <output> tag and no other text.`}function i(e){let{hunks:t,commitMessages:n,commitOrder:r,branchName:i,baseBranch:a,issueUrl:o,pullRequestUrl:s,instructions:c}=e,l=`Analyze the following code changes:\n\n<hunks>\n${JSON.stringify(t.map(e=>({index:e.index,fileName:e.fileName,hunkHeader:e.hunkHeader,content:e.content,...e.isBinary?{isBinary:!0}:{},...e.isRename?{isRename:!0}:{},...e.originalFileName?{originalFileName:e.originalFileName}:{}})))}\n</hunks>`,u=[];if(i&&u.push(`Branch: ${i}`),a&&u.push(`Base branch: ${a}`),o&&u.push(`Issue: ${o}`),s&&u.push(`Pull request: ${s}`),u.length>0&&(l+=`\n\n<context>\n${u.join(`
32
- `)}\n</context>`),n?.length){let e=n.map((e,t)=>({index:t,sha:r?.[t],message:e}));l+=`\n\n<commit_messages>\n${JSON.stringify(e)}\n</commit_messages>`}return c&&(l+=`\n\nCarefully follow these additional instructions, but do not deviate from the output structure:\n${c}`),l+=`
31
+ Return only the <output> tag and no other text.`}function a(t){let{hunks:n,commitMessages:r,commitOrder:i,branchName:a,baseBranch:o,issueUrl:s,pullRequestUrl:c,instructions:l,redactHunkContent:u}=t,d=`Analyze the following code changes:\n\n<hunks>\n${JSON.stringify(n.map(t=>({index:t.index,fileName:t.fileName,hunkHeader:t.hunkHeader,content:e(t,u),...t.isBinary?{isBinary:!0}:{},...t.isRename?{isRename:!0}:{},...t.originalFileName?{originalFileName:t.originalFileName}:{}})))}\n</hunks>`,f=[];if(a&&f.push(`Branch: ${a}`),o&&f.push(`Base branch: ${o}`),s&&f.push(`Issue: ${s}`),c&&f.push(`Pull request: ${c}`),f.length>0&&(d+=`\n\n<context>\n${f.join(`
32
+ `)}\n</context>`),r?.length){let e=r.map((e,t)=>({index:t,sha:i?.[t],message:e}));d+=`\n\n<commit_messages>\n${JSON.stringify(e)}\n</commit_messages>`}return l&&(d+=`\n\nCarefully follow these additional instructions, but do not deviate from the output structure:\n${l}`),d+=`
33
33
 
34
- Now analyze the changes and return your structured analysis. Return only the <output> tag and no other text.`,l}export{r as buildAnalyzeSystemPrompt,i as buildAnalyzeUserPrompt};
34
+ Now analyze the changes and return your structured analysis. Return only the <output> tag and no other text.`,d}export{i as buildAnalyzeSystemPrompt,a as buildAnalyzeUserPrompt};
@@ -1,6 +1,7 @@
1
1
  import { ProgressEvent, TokenUsage } from "../../ai/types.js";
2
2
  import { ComposeHunk, PromptOverride } from "../types.js";
3
3
  import { OnAfterModelCall, OnBeforeModelCall, OnBeforePrompt } from "../ai-loop.js";
4
+ import { RedactHunkContent } from "../utils/redact.js";
4
5
  import { ComposeSession } from "../session.js";
5
6
  import { AiModelPort } from "@gitkraken/shared-tools";
6
7
 
@@ -14,11 +15,15 @@ interface AnalyzeInput {
14
15
  issueUrl?: string;
15
16
  pullRequestUrl?: string;
16
17
  instructions?: string;
18
+ /** See {@link RedactHunkContent}. */
19
+ redactHunkContent?: RedactHunkContent;
17
20
  model: AiModelPort;
18
21
  session?: ComposeSession;
19
22
  maxRetries?: number;
23
+ /** Output-token cap forwarded to `runAiLoop`. Default 16384; bump for analyze on very large diffs where the structured output (per-hunk annotations) overflows the cap mid-JSON. */
24
+ maxTokens?: number;
20
25
  onProgress?: (event: ProgressEvent) => void;
21
- signal?: AbortSignal;
26
+ cancellation?: AbortSignal;
22
27
  onBeforeModelCall?: OnBeforeModelCall;
23
28
  onAfterModelCall?: OnAfterModelCall;
24
29
  onBeforePrompt?: OnBeforePrompt;
@@ -1,4 +1,4 @@
1
- import{extractOutputTag as e}from"../ai-loop.js";import{analysisResultSchema as t}from"./schema.js";function n(n,a){try{let o=e(n);if(!o)return r([`No <output> tag found in response`],i());let s=JSON.parse(o),c=t.safeParse(s);if(!c.success)return r(c.error.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`),i());let l=c.data,u=new Set(l.hunkAnnotations.map(e=>e.hunkIndex)),d=[...new Set(Array.from({length:a},(e,t)=>t))].filter(e=>!u.has(e));return d.length>0?r([`Missing annotations for hunks: ${d.join(`, `)}`],`Your response is missing annotations for some hunks. Every hunk must be annotated.\n\nMissing: ${d.join(`, `)}\n\nPlease provide a corrected response.`):{valid:!0,result:{title:l.title,summary:l.summary,concerns:l.concerns,dependencies:l.dependencies,hunkAnnotations:l.hunkAnnotations,orderingConstraints:l.orderingConstraints}}}catch{return r([`Failed to parse response as valid JSON`],i())}}function r(e,t){return{valid:!1,errors:e,retryFeedback:t}}function i(){return`Your response has an invalid structure. Please return JSON inside <output> tags with this structure:
1
+ import{extractOutputTag as e}from"../ai-loop.js";import{analysisResultSchema as t}from"./schema.js";function n(n,a){let o=null;try{if(o=e(n),!o)return r([`No <output> tag found in response`],i());let s=JSON.parse(o),c=t.safeParse(s);if(!c.success)return r(c.error.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`),i());let l=c.data,u=new Set(l.hunkAnnotations.map(e=>e.hunkIndex)),d=[...new Set(Array.from({length:a},(e,t)=>t))].filter(e=>!u.has(e));return d.length>0?r([`Missing annotations for hunks: ${d.join(`, `)}`],`Your response is missing annotations for some hunks. Every hunk must be annotated.\n\nMissing: ${d.join(`, `)}\n\nPlease provide a corrected response.`):{valid:!0,result:{title:l.title,summary:l.summary,concerns:l.concerns,dependencies:l.dependencies,hunkAnnotations:l.hunkAnnotations,orderingConstraints:l.orderingConstraints}}}catch(e){let t=e instanceof Error?e.message:String(e),a=(o??n).slice(-300);return r([`Failed to parse response as valid JSON: ${t}`,`Response tail: ${a}`],i())}}function r(e,t){return{valid:!1,errors:e,retryFeedback:t}}function i(){return`Your response has an invalid structure. Please return JSON inside <output> tags with this structure:
2
2
  <output>
3
3
  {
4
4
  "title": "Short PR-style title (under 72 chars)",
@@ -1 +1 @@
1
- import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{validateGroupResponse as r}from"../group/validator.js";import{buildComposeGroupSystemPrompt as i,buildComposeGroupUserPrompt as a}from"./prompt.js";async function o(o){let s=o.hunks,c=o.mode??`branches`;if(!s||s.length===0)throw new e(`No hunks provided for compose-group`);if(o.session?.completedSteps.composeGroup)return{result:o.session.completedSteps.composeGroup,session:o.session,attempts:0,durationMs:0,callIds:[]};let l=o.promptOverride?.system??i(o.conventions,c,o.strategy,o.includeAnalysis,o.lightweight),u=n(a({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions,condensed:o.condensed}),o.promptOverride),d=await t({model:o.model,systemPrompt:l,userPrompt:u,maxRetries:o.maxRetries,onProgress:o.onProgress,signal:o.signal,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>r(e,s.length,c,o.existingBranchNames),taskName:`compose-group`}),f=o.session?.completedSteps??{},p={...d.session,completedSteps:{...f,composeGroup:d.result}};return{result:d.result,session:p,usage:d.usage,attempts:d.attempts,durationMs:d.durationMs,callIds:d.callIds}}export{o as composeGroup};
1
+ import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{validateGroupResponse as r}from"../group/validator.js";import{buildComposeGroupSystemPrompt as i,buildComposeGroupUserPrompt as a}from"./prompt.js";async function o(o){let s=o.hunks,c=o.mode??`branches`;if(!s||s.length===0)throw new e(`No hunks provided for compose-group`);if(o.session?.completedSteps.composeGroup)return{result:o.session.completedSteps.composeGroup,session:o.session,attempts:0,durationMs:0,callIds:[]};let l=o.promptOverride?.system??i(o.conventions,c,o.strategy,o.includeAnalysis,o.lightweight),u=n(a({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions,condensed:o.condensed,redactHunkContent:o.redactHunkContent}),o.promptOverride),d=await t({model:o.model,systemPrompt:l,userPrompt:u,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>r(e,s.length,c,o.existingBranchNames),taskName:`compose-group`}),f=o.session?.completedSteps??{},p={...d.session,completedSteps:{...f,composeGroup:d.result}};return{result:d.result,session:p,usage:d.usage,attempts:d.attempts,durationMs:d.durationMs,callIds:d.callIds}}export{o as composeGroup};
@@ -1,5 +1,6 @@
1
1
  import { ComposeHunk } from "../types.js";
2
2
  import { ConventionDescriptor } from "../../git/utils/conventions.js";
3
+ import { RedactHunkContent } from "../utils/redact.js";
3
4
  import { GroupingStrategy } from "../group/types.js";
4
5
 
5
6
  //#region src/tasks/compose-group/prompt.d.ts
@@ -21,6 +22,7 @@ declare function buildComposeGroupUserPrompt(params: {
21
22
  pullRequestUrl?: string;
22
23
  instructions?: string;
23
24
  condensed?: boolean;
25
+ redactHunkContent?: RedactHunkContent;
24
26
  }): string;
25
27
  //#endregion
26
28
  export { buildComposeGroupSystemPrompt, buildComposeGroupUserPrompt };
@@ -1,4 +1,4 @@
1
- import{groupingResultSchema as e}from"../group/schema.js";import{z as t}from"zod";const n=JSON.stringify(t.toJSONSchema(e),null,2),r=`
1
+ import{hunkContentForPrompt as e}from"../utils/redact.js";import{groupingResultSchema as t}from"../group/schema.js";import{z as n}from"zod";const r=JSON.stringify(n.toJSONSchema(t),null,2),i=`
2
2
  In addition to the \`branches\` field, include an \`analysis\` field at the top level:
3
3
 
4
4
  \`\`\`json
@@ -10,31 +10,38 @@ In addition to the \`branches\` field, include an \`analysis\` field at the top
10
10
  "branches": [ ... ]
11
11
  }
12
12
  \`\`\`
13
- `,i=`5. Write a commit message for each commit:
14
- - First line: concise summary, 50 chars max, third-person singular present tense ("Fixes", "Adds", "Improves")
15
- - Then a blank line followed by details focusing on "why" not "what"
16
- - Informal yet professional tone`;function a(e,t=`branches`,n,r,i){return i?t===`commits`?u(e,n):d(e,n):t===`commits`?o(e,n,r):s(e,n,r)}function o(e,t,a){let o=e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
13
+ `,a=`5. Write a full commit message for each commit containing both a summary and a description:
14
+ - **Summary** (first line): concise, 50 chars max, third-person singular present tense ("Fixes", "Adds", "Improves"). This line stands alone as the commit subject.
15
+ - **Blank line**: mandatory separator between summary and description.
16
+ - **Description** (body): one or more lines explaining *why* this change was made, not *what* it does. Focus on motivation, trade-offs, or context a reviewer needs. For non-trivial changes, always include a description — don't leave it at just the summary.
17
+ - Informal yet professional tone.`;function o(e,t=`branches`,n,r,i){return i?t===`commits`?d(e,n):f(e,n):t===`commits`?s(e,n,r):c(e,n,r)}function s(e,t,n){let o=e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
17
18
  6. Write a detailed explanation for each commit as if explaining to a reviewer.
18
19
 
19
- ${e.promptFragment}`:`${i}
20
- 6. Write a detailed explanation for each commit as if explaining to a reviewer`,s=l(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits.
20
+ ${e.promptFragment}`:`${a}
21
+ 6. Write a detailed explanation for each commit as if explaining to a reviewer`,s=u(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits.
21
22
 
22
- ${a?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
23
+ ## Critical constraints (output is rejected if violated)
24
+
25
+ 1. **Each hunk index appears exactly once across the entire response.** Not zero. Not two. Exactly one occurrence — meaning it appears in exactly one commit's \`hunks\` array, and only once within that array. Listing the same index twice inside one commit's \`hunks\`, OR listing it in two different commits, both count as duplicates and both fail validation. This is the most common failure mode when two hunks look similar (same file, same kind of change).
26
+ 2. **All hunk indices 0..N-1 must appear** across the union of every commit's \`hunks\` array.
27
+ 3. Each commit must contain at least one hunk. No empty commits.
28
+ 4. Each commit's \`id\` must be unique (e.g. \`commit-1\`, \`commit-2\`).
29
+
30
+ Before producing the \`<output>\` tag, do a self-check: count the total number of hunk indices across every commit's \`hunks\` array. That count MUST equal the input hunk count. If it does not match, you have either duplicated or skipped a hunk — find and fix it before outputting.
31
+
32
+ ${n?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
23
33
 
24
34
  Before grouping, analyze the changes to understand:
25
35
  - **Concerns** — what distinct themes, features, or purposes exist across the hunks
26
36
  - **Dependencies** — which hunks provide things (define functions, types, files) that other hunks consume (import, call, reference)
27
37
  - **Ordering constraints** — if hunk A defines something hunk B uses, they should be in the same commit or A's commit should come before B's
28
38
 
29
- ${a?"Output this analysis as a JSON field named `analysis` alongside your `branches` field. The `analysis` field should contain:\n- `concerns`: array of strings, each describing a distinct theme or feature in the changes\n- `key_dependencies`: array of strings, each describing an ordering dependency between concerns":`Do NOT output this analysis. Use it internally to inform your grouping decisions.`}
39
+ ${n?"Output this analysis as a JSON field named `analysis` alongside your `branches` field. The `analysis` field should contain:\n- `concerns`: array of strings, each describing a distinct theme or feature in the changes\n- `key_dependencies`: array of strings, each describing an ordering dependency between concerns":`Do NOT output this analysis. Use it internally to inform your grouping decisions.`}
30
40
 
31
41
  ## Step 2: Group hunks into commits
32
42
 
33
43
  1. Group hunks into logical units that make sense together and can be applied atomically
34
- 2. Use each hunk only once. Ensure all hunks are assigned to exactly one commit
35
- 3. Ensure each commit is self-contained and atomic
36
- 4. Each commit must have a unique ID (e.g., "commit-1", "commit-2")
37
- 5. Every commit must contain at least one hunk. Do not produce empty commits — if you want to split a theme across multiple commits, each resulting commit needs its own hunks
44
+ 2. Ensure each commit is self-contained and atomic
38
45
  ${o}
39
46
  ${s}
40
47
 
@@ -43,22 +50,27 @@ ${s}
43
50
  Output your organization as JSON inside a <output> tag matching this schema:
44
51
 
45
52
  \`\`\`json
46
- ${n}
53
+ ${r}
47
54
  \`\`\`
48
- ${a?r:``}
55
+ ${n?i:``}
49
56
  Wrap all commits in a single branch object with id "default", name "", title "", and description "".
50
57
 
51
- Remember:
52
- - Every hunk must be assigned to exactly one commit
53
- - Every commit must contain at least one hunk (no empty commits)
54
- - Use exactly one branch with id "default", name "", title "", and description ""
55
- - Return only the <output> tag and no other text.`}function s(e,t,a){let o=e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
58
+ Return only the <output> tag and no other text.`}function c(e,t,n){let o=e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
56
59
  6. Write a detailed explanation for each commit as if explaining to a reviewer.
57
60
 
58
- ${e.promptFragment}`:`${i}
59
- 6. Write a detailed explanation for each commit as if explaining to a reviewer`,s=c(t?.branchSplitting,e),u=l(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits and branches.
61
+ ${e.promptFragment}`:`${a}
62
+ 6. Write a detailed explanation for each commit as if explaining to a reviewer`,s=l(t?.branchSplitting,e),c=u(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits and branches.
60
63
 
61
- ${a?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
64
+ ## Critical constraints (output is rejected if violated)
65
+
66
+ 1. **Each hunk index appears exactly once across the entire response.** Not zero. Not two. Exactly one occurrence — meaning it appears in exactly one branch, in exactly one commit within that branch, and only once within that commit's \`hunks\` array. Listing the same index twice inside one commit's \`hunks\`, OR listing it in two different commits, OR listing it in two different branches, all count as duplicates and all fail validation. This is the most common failure mode when two hunks look similar (same file, same kind of change).
67
+ 2. **All hunk indices 0..N-1 must appear** across the union of every commit's \`hunks\` array across every branch.
68
+ 3. Each commit must contain at least one hunk. No empty commits.
69
+ 4. Each commit \`id\` must be unique across the whole response.
70
+
71
+ Before producing the \`<output>\` tag, do a self-check: count the total number of hunk indices across every commit's \`hunks\` array (across every branch). That count MUST equal the input hunk count. If it does not match, you have either duplicated or skipped a hunk — find and fix it before outputting.
72
+
73
+ ${n?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
62
74
 
63
75
  Before grouping, analyze the changes to understand:
64
76
  - **Concerns** — what distinct themes, features, or purposes exist across the hunks
@@ -66,7 +78,7 @@ Before grouping, analyze the changes to understand:
66
78
  - **Ordering constraints** — if hunk A defines something hunk B uses, they should be in the same commit or A's commit should come before B's
67
79
  - **Merge-order preference** — even if two concerns compile independently, would merging one without the other be confusing or incomplete?
68
80
 
69
- ${a?"Output this analysis as a JSON field named `analysis` alongside your `branches` field. The `analysis` field should contain:\n- `concerns`: array of strings, each describing a distinct theme or feature in the changes\n- `key_dependencies`: array of strings, each describing an ordering dependency between concerns":`Do NOT output this analysis. Use it internally to inform your grouping decisions.`}
81
+ ${n?"Output this analysis as a JSON field named `analysis` alongside your `branches` field. The `analysis` field should contain:\n- `concerns`: array of strings, each describing a distinct theme or feature in the changes\n- `key_dependencies`: array of strings, each describing an ordering dependency between concerns":`Do NOT output this analysis. Use it internally to inform your grouping decisions.`}
70
82
 
71
83
  ## Step 2: Organize hunks into commits and branches
72
84
 
@@ -78,12 +90,9 @@ Your job has two levels:
78
90
  ### Commit guidelines
79
91
 
80
92
  1. Group hunks into logical units that make sense together and can be applied atomically
81
- 2. Use each hunk only once. Ensure all hunks are assigned to exactly one commit
82
- 3. Ensure each commit is self-contained and atomic
83
- 4. Each commit must have a unique ID (e.g., "commit-1", "commit-2")
84
- 5. Every commit must contain at least one hunk. Do not produce empty commits — if you want to split a theme across multiple commits, each resulting commit needs its own hunks
93
+ 2. Ensure each commit is self-contained and atomic
85
94
  ${o}
86
- ${u}
95
+ ${c}
87
96
 
88
97
  ### Branch guidelines
89
98
 
@@ -94,17 +103,14 @@ ${s}
94
103
  Output your organization as JSON inside a <output> tag matching this schema:
95
104
 
96
105
  \`\`\`json
97
- ${n}
106
+ ${r}
98
107
  \`\`\`
99
- ${a?r:``}
100
- Remember:
101
- - Every hunk must be assigned to exactly one commit in exactly one branch
102
- - Every commit must contain at least one hunk (no empty commits)
103
- - Return only the <output> tag and no other text.`}function c(e,t){let n=`Each branch must have a descriptive name, a short PR-style title (under 72 characters, imperative mood), and at least one commit.`,r=`- Each commit belongs to exactly one branch.
108
+ ${n?i:``}
109
+ Return only the <output> tag and no other text.`}function l(e,t){let n=`Each branch must have a descriptive name, a short PR-style title (under 72 characters, imperative mood), and at least one commit.`,r=`- Each commit belongs to exactly one branch.
104
110
  ${t?.promptFragment?`- ${n} Follow the repository's branch naming conventions (see above).`:`- ${n}`}
105
- - Changes addressing the same pattern across different files belong in ONE branch.`,i=`- Consider merge-order preference, not just code dependencies. Even if two concerns compile independently, they may need to be in the same branch if merging one without the other would be confusing or incomplete.`;return e===`conservative`?`${r}\n- Default to a single branch. Only create multiple branches when concerns are completely unrelated.\n${i}\n- When in doubt, use fewer branches.`:e===`aggressive`?`${r}\n- Create separate branches for each distinct concern.\n${i}\n- Prefer more branches over fewer.`:`${r}\n- Create separate branches when concerns are genuinely independent — meaning a reviewer would gain nothing from seeing them together.\n- Keep changes in the same branch when they form a coherent story.\n${i}\n- When in doubt: "Would a reviewer want to see these together?"`}function l(e){return e===`consolidated`?`
111
+ - Changes addressing the same pattern across different files belong in ONE branch.`,i=`- Consider merge-order preference, not just code dependencies. Even if two concerns compile independently, they may need to be in the same branch if merging one without the other would be confusing or incomplete.`;return e===`conservative`?`${r}\n- Default to a single branch. Only create multiple branches when concerns are completely unrelated.\n${i}\n- When in doubt, use fewer branches.`:e===`aggressive`?`${r}\n- Create separate branches for each distinct concern.\n${i}\n- Prefer more branches over fewer.`:`${r}\n- Create separate branches when concerns are genuinely independent — meaning a reviewer would gain nothing from seeing them together.\n- Keep changes in the same branch when they form a coherent story.\n${i}\n- When in doubt: "Would a reviewer want to see these together?"`}function u(e){return e===`consolidated`?`
106
112
  Prefer fewer, larger commits. Combine related changes into a single commit when they serve the same purpose.`:e===`granular`?`
107
- Prefer more, smaller commits. Each distinct change should be its own commit.`:``}function u(e,t){return`Organize code hunks into clean, atomic commits.
113
+ Prefer more, smaller commits. Each distinct change should be its own commit.`:``}function d(e,t){return`Organize code hunks into clean, atomic commits.
108
114
 
109
115
  ## Rules
110
116
 
@@ -115,7 +121,7 @@ Prefer more, smaller commits. Each distinct change should be its own commit.`:``
115
121
  5. ${e?.promptFragment?`Write commit messages following the repository's conventions (see below). Write a one-sentence explanation per commit.
116
122
 
117
123
  ${e.promptFragment}`:`Write commit messages: first line under 50 chars, imperative mood ("Add", "Fix", "Update"). Write a one-sentence explanation per commit.`}
118
- ${l(t?.commitGranularity)}
124
+ ${u(t?.commitGranularity)}
119
125
 
120
126
  ## Output
121
127
 
@@ -125,9 +131,9 @@ JSON inside <output> tags. Use one branch with id "default", name "", title "",
125
131
  {"branches":[{"id":"default","name":"","title":"","description":"","commits":[{"id":"commit-1","message":"Add feature X","explanation":"Brief reason.","hunks":[0,1,2]}]}]}
126
132
  \`\`\`
127
133
 
128
- CRITICAL: Every hunk index must appear exactly once across all commits. Every commit must contain at least one hunk. Missing hunks, duplicate hunks, or empty commits will fail validation. Return only the <output> tag.`}function d(e,t){let n=e?.promptFragment?`Write commit messages following the repository's conventions (see below). Write a one-sentence explanation per commit.
134
+ CRITICAL: Every hunk index must appear exactly once across all commits. Every commit must contain at least one hunk. Missing hunks, duplicate hunks, or empty commits will fail validation. Return only the <output> tag.`}function f(e,t){let n=e?.promptFragment?`Write commit messages following the repository's conventions (see below). Write a one-sentence explanation per commit.
129
135
 
130
- ${e.promptFragment}`:`Write commit messages: first line under 50 chars, third-person singular present tense ("Fixes", "Adds", "Improves"). Write a one-sentence explanation per commit.`,r=c(t?.branchSplitting,e);return`Organize code hunks into clean, atomic commits and branches.
136
+ ${e.promptFragment}`:`Write commit messages: first line under 50 chars, third-person singular present tense ("Fixes", "Adds", "Improves"). Write a one-sentence explanation per commit.`,r=l(t?.branchSplitting,e);return`Organize code hunks into clean, atomic commits and branches.
131
137
 
132
138
  ## Commit rules
133
139
 
@@ -136,7 +142,7 @@ ${e.promptFragment}`:`Write commit messages: first line under 50 chars, third-pe
136
142
  3. Each commit needs a unique ID (e.g., "commit-1", "commit-2").
137
143
  4. Every commit must have at least one hunk. No empty commits.
138
144
  5. ${n}
139
- ${l(t?.commitGranularity)}
145
+ ${u(t?.commitGranularity)}
140
146
 
141
147
  ## Branch guidelines
142
148
 
@@ -152,11 +158,11 @@ JSON inside <output> tags. Each branch needs a unique ID and non-empty name.
152
158
 
153
159
  CRITICAL: Every hunk index must appear exactly once across all commits in all branches. Every commit must contain at least one hunk. Missing hunks, duplicate hunks, or empty commits will fail validation. Each branch must have a unique ID and non-empty name.
154
160
 
155
- Return only the <output> tag and no other text.`}function f(e){let{hunks:t,commitMessages:n,commitOrder:r,branchName:i,baseBranch:a,issueUrl:o,pullRequestUrl:s,instructions:c,condensed:l}=e,u=JSON.stringify(l?t.map(e=>({index:e.index,fileName:e.fileName,hunkHeader:e.hunkHeader,additions:e.additions,deletions:e.deletions,...e.isBinary?{isBinary:!0}:{},...e.isRename?{isRename:!0}:{},...e.originalFileName?{originalFileName:e.originalFileName}:{}})):t.map(e=>({index:e.index,fileName:e.fileName,hunkHeader:e.hunkHeader,content:e.content,...e.isBinary?{isBinary:!0}:{},...e.isRename?{isRename:!0}:{},...e.originalFileName?{originalFileName:e.originalFileName}:{}}))),d=`Organize these ${t.length} code hunks (indices 0 to ${t.length-1}) into commits. Every index must appear exactly once.
161
+ Return only the <output> tag and no other text.`}function p(t){let{hunks:n,commitMessages:r,commitOrder:i,branchName:a,baseBranch:o,issueUrl:s,pullRequestUrl:c,instructions:l,condensed:u,redactHunkContent:d}=t,f=JSON.stringify(u?n.map(e=>({index:e.index,fileName:e.fileName,hunkHeader:e.hunkHeader,additions:e.additions,deletions:e.deletions,...e.isBinary?{isBinary:!0}:{},...e.isRename?{isRename:!0}:{},...e.originalFileName?{originalFileName:e.originalFileName}:{}})):n.map(t=>({index:t.index,fileName:t.fileName,hunkHeader:t.hunkHeader,content:e(t,d),...t.isBinary?{isBinary:!0}:{},...t.isRename?{isRename:!0}:{},...t.originalFileName?{originalFileName:t.originalFileName}:{}}))),p=`Organize these ${n.length} code hunks (indices 0 to ${n.length-1}) into commits. Every index must appear exactly once.
156
162
 
157
163
  <hunks>
158
- ${u}
159
- </hunks>`,f=[];return i&&f.push(`Branch: ${i}`),a&&f.push(`Base: ${a}`),o&&f.push(`Issue: ${o}`),s&&f.push(`PR: ${s}`),f.length>0&&(d+=`\n\nContext:\n${f.join(`
160
- `)}`),n?.length&&(d+=`\n\nOriginal commit messages (match their style):\n<commit_messages>\n${JSON.stringify(n)}\n</commit_messages>`),r?.length&&(d+=`\n\nOriginal commit order:\n${JSON.stringify(r)}`),c&&(d+=`\n\nAdditional instructions:\n${c}`),d+=`
164
+ ${f}
165
+ </hunks>`,m=[];return a&&m.push(`Branch: ${a}`),o&&m.push(`Base: ${o}`),s&&m.push(`Issue: ${s}`),c&&m.push(`PR: ${c}`),m.length>0&&(p+=`\n\nContext:\n${m.join(`
166
+ `)}`),r?.length&&(p+=`\n\nOriginal commit messages (match their style):\n<commit_messages>\n${JSON.stringify(r)}\n</commit_messages>`),i?.length&&(p+=`\n\nOriginal commit order:\n${JSON.stringify(i)}`),l&&(p+=`\n\nAdditional instructions:\n${l}`),p+=`
161
167
 
162
- Return only the <output> tag and no other text.`,d}export{a as buildComposeGroupSystemPrompt,f as buildComposeGroupUserPrompt};
168
+ Return only the <output> tag and no other text.`,p}export{o as buildComposeGroupSystemPrompt,p as buildComposeGroupUserPrompt};