@gitkraken/compose-tools 0.5.0 → 0.7.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 (50) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/errors/task.d.ts +11 -1
  3. package/dist/errors/task.js +1 -1
  4. package/dist/git/diff-collector.js +5 -5
  5. package/dist/git/utils/conventions.d.ts +7 -0
  6. package/dist/git/utils/conventions.js +8 -9
  7. package/dist/index.d.ts +5 -2
  8. package/dist/index.js +1 -1
  9. package/dist/tasks/analyze/analyze.js +1 -1
  10. package/dist/tasks/analyze/prompt.d.ts +4 -1
  11. package/dist/tasks/analyze/prompt.js +5 -5
  12. package/dist/tasks/analyze/schema.d.ts +127 -36
  13. package/dist/tasks/analyze/schema.js +1 -1
  14. package/dist/tasks/analyze/types.d.ts +8 -0
  15. package/dist/tasks/analyze/validator.js +2 -2
  16. package/dist/tasks/compose-group/compose-group.js +1 -1
  17. package/dist/tasks/compose-group/prompt.d.ts +4 -1
  18. package/dist/tasks/compose-group/prompt.js +23 -29
  19. package/dist/tasks/compose-group/types.d.ts +5 -0
  20. package/dist/tasks/group/group.js +1 -1
  21. package/dist/tasks/group/prompt.d.ts +4 -1
  22. package/dist/tasks/group/prompt.js +15 -21
  23. package/dist/tasks/group/schema.d.ts +89 -17
  24. package/dist/tasks/group/schema.js +1 -1
  25. package/dist/tasks/group/types.d.ts +8 -0
  26. package/dist/tasks/group/validator.js +6 -6
  27. package/dist/tasks/order/prompt.js +5 -5
  28. package/dist/tasks/order/schema.d.ts +35 -10
  29. package/dist/tasks/order/schema.js +1 -1
  30. package/dist/tasks/order/validator.js +2 -2
  31. package/dist/tasks/partition/prompt.js +9 -9
  32. package/dist/tasks/partition/schema.d.ts +46 -12
  33. package/dist/tasks/partition/schema.js +1 -1
  34. package/dist/tasks/partition/validator.js +2 -2
  35. package/dist/tasks/quick-compose/prompt.js +8 -9
  36. package/dist/tasks/quick-compose/schema.d.ts +142 -25
  37. package/dist/tasks/quick-compose/schema.js +1 -1
  38. package/dist/tasks/quick-compose/validator.js +5 -5
  39. package/dist/tasks/utils/budget.d.ts +40 -0
  40. package/dist/tasks/utils/budget.js +1 -0
  41. package/dist/tasks/utils/json-schema.d.ts +61 -0
  42. package/dist/tasks/utils/json-schema.js +1 -0
  43. package/dist/tasks/utils/noise.d.ts +21 -0
  44. package/dist/tasks/utils/noise.js +2 -0
  45. package/dist/tasks/utils/redact.d.ts +7 -1
  46. package/dist/tasks/utils/redact.js +1 -1
  47. package/dist/workflows/compose.d.ts +36 -0
  48. package/dist/workflows/compose.js +3 -3
  49. package/dist/workflows/split.js +2 -2
  50. package/package.json +2 -2
@@ -1,4 +1,4 @@
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=`
1
+ import{assembleHunkPrompt as e}from"../utils/budget.js";import{groupingResultJsonSchema as t}from"../group/schema.js";const n=JSON.stringify(t,null,2),r=`
2
2
  In addition to the \`branches\` field, include an \`analysis\` field at the top level:
3
3
 
4
4
  \`\`\`json
@@ -10,15 +10,12 @@ In addition to the \`branches\` field, include an \`analysis\` field at the top
10
10
  "branches": [ ... ]
11
11
  }
12
12
  \`\`\`
13
- `,a=`5. Write a full commit message for each commit containing both a summary and a description:
13
+ `,i=`5. Write a full commit message for each commit containing both a summary and a description:
14
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
15
  - **Blank line**: mandatory separator between summary and description.
16
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).
18
- 6. Write a detailed explanation for each commit as if explaining to a reviewer.
19
-
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.
17
+ - Informal yet professional tone.`,a=`5. Write a concise single-line commit message for each commit — a subject only, no body: 50 chars max, third-person singular present tense ("Fixes", "Adds", "Improves"). This repository's history overwhelmingly uses one-line commit messages, so do not add a description.`;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,o){let s=`${e?.commits.bodyStyle===`minimal`?a:i}
18
+ 6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`,c=u(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits.
22
19
 
23
20
  ## Critical constraints (output is rejected if violated)
24
21
 
@@ -29,37 +26,34 @@ ${e.promptFragment}`:`${a}
29
26
 
30
27
  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
28
 
32
- ${n?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
29
+ ${o?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
33
30
 
34
31
  Before grouping, analyze the changes to understand:
35
32
  - **Concerns** — what distinct themes, features, or purposes exist across the hunks
36
33
  - **Dependencies** — which hunks provide things (define functions, types, files) that other hunks consume (import, call, reference)
37
34
  - **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
38
35
 
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.`}
36
+ ${o?"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.`}
40
37
 
41
38
  ## Step 2: Group hunks into commits
42
39
 
43
40
  1. Group hunks into logical units that make sense together and can be applied atomically
44
41
  2. Ensure each commit is self-contained and atomic
45
- ${o}
46
42
  ${s}
43
+ ${c}
47
44
 
48
45
  ## Required Output Format
49
46
 
50
47
  Output your organization as JSON inside a <output> tag matching this schema:
51
48
 
52
49
  \`\`\`json
53
- ${r}
50
+ ${n}
54
51
  \`\`\`
55
- ${n?i:``}
52
+ ${o?r:``}
56
53
  Wrap all commits in a single branch object with id "default", name "", title "", and description "".
57
54
 
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).
59
- 6. Write a detailed explanation for each commit as if explaining to a reviewer.
60
-
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.
55
+ Return only the <output> tag and no other text.`}function c(e,t,o){let s=`${e?.commits.bodyStyle===`minimal`?a:i}
56
+ 6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`,c=l(t?.branchSplitting,e),d=u(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits and branches.
63
57
 
64
58
  ## Critical constraints (output is rejected if violated)
65
59
 
@@ -70,7 +64,7 @@ ${e.promptFragment}`:`${a}
70
64
 
71
65
  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
66
 
73
- ${n?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
67
+ ${o?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
74
68
 
75
69
  Before grouping, analyze the changes to understand:
76
70
  - **Concerns** — what distinct themes, features, or purposes exist across the hunks
@@ -78,7 +72,7 @@ Before grouping, analyze the changes to understand:
78
72
  - **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
79
73
  - **Merge-order preference** — even if two concerns compile independently, would merging one without the other be confusing or incomplete?
80
74
 
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.`}
75
+ ${o?"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.`}
82
76
 
83
77
  ## Step 2: Organize hunks into commits and branches
84
78
 
@@ -91,21 +85,21 @@ Your job has two levels:
91
85
 
92
86
  1. Group hunks into logical units that make sense together and can be applied atomically
93
87
  2. Ensure each commit is self-contained and atomic
94
- ${o}
95
- ${c}
88
+ ${s}
89
+ ${d}
96
90
 
97
91
  ### Branch guidelines
98
92
 
99
- ${s}
93
+ ${c}
100
94
 
101
95
  ## Required Output Format
102
96
 
103
97
  Output your organization as JSON inside a <output> tag matching this schema:
104
98
 
105
99
  \`\`\`json
106
- ${r}
100
+ ${n}
107
101
  \`\`\`
108
- ${n?i:``}
102
+ ${o?r:``}
109
103
  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.
110
104
  ${t?.promptFragment?`- ${n} Follow the repository's branch naming conventions (see above).`:`- ${n}`}
111
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 u(e){return e===`consolidated`?`
@@ -158,11 +152,11 @@ JSON inside <output> tags. Each branch needs a unique ID and non-empty name.
158
152
 
159
153
  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.
160
154
 
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.
155
+ 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,inputTokenBudget:f,systemPromptChars:p,onBudgetReduction:m}=t,{prompt:h,hunksCondensedForBudget:g}=e({hunks:n,assemble:e=>{let t=`Organize these ${n.length} code hunks (indices 0 to ${n.length-1}) into commits. Every index must appear exactly once.
162
156
 
163
157
  <hunks>
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+=`
158
+ ${e}
159
+ </hunks>`,u=[];return a&&u.push(`Branch: ${a}`),o&&u.push(`Base: ${o}`),s&&u.push(`Issue: ${s}`),c&&u.push(`PR: ${c}`),u.length>0&&(t+=`\n\nContext:\n${u.join(`
160
+ `)}`),r?.length&&(t+=`\n\nOriginal commit messages (match their style):\n<commit_messages>\n${JSON.stringify(r)}\n</commit_messages>`),i?.length&&(t+=`\n\nOriginal commit order:\n${JSON.stringify(i)}`),l&&(t+=`\n\nAdditional instructions:\n${l}`),t+=`
167
161
 
168
- Return only the <output> tag and no other text.`,p}export{o as buildComposeGroupSystemPrompt,p as buildComposeGroupUserPrompt};
162
+ Return only the <output> tag and no other text.`,t},condensed:u,redactHunkContent:d,inputTokenBudget:f,systemPromptChars:p});return m?.(g),h}export{o as buildComposeGroupSystemPrompt,p as buildComposeGroupUserPrompt};
@@ -40,6 +40,11 @@ interface ComposeGroupInput {
40
40
  condensed?: boolean;
41
41
  /** No-op when `condensed: true`. See {@link RedactHunkContent}. */
42
42
  redactHunkContent?: RedactHunkContent;
43
+ /**
44
+ * Input-token ceiling. When exceeded, hunks are condensed to metadata biggest-first;
45
+ * throws `DiffTooLargeError` if they still cannot fit. Defaults to `DEFAULT_INPUT_TOKEN_BUDGET`.
46
+ */
47
+ inputTokenBudget?: number;
43
48
  model: AiModelPort;
44
49
  /** Prior session to resume from. When `completedSteps.composeGroup` is present, skip the AI call. */
45
50
  session?: ComposeSession;
@@ -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"./validator.js";import{buildGroupContinuationPrompt as i,buildGroupSystemPrompt as a,buildGroupUserPrompt as o}from"./prompt.js";async function s(s){let c=s.hunks,l=s.mode??`branches`;if((!c||c.length===0)&&!s.session)throw new e(`No hunks provided: supply 'hunks' or run analyze() first with a session`);if(s.session?.completedSteps.group)return{result:s.session.completedSteps.group,session:s.session,attempts:0,durationMs:0,callIds:[]};let u=n(s.session?i(s,l):o({hunks:c,commitMessages:s.commitMessages,instructions:s.instructions,mode:l,redactHunkContent:s.redactHunkContent}),s.promptOverride),d=c?.length??s.session?.completedSteps.analyze?.hunkAnnotations.length??0;if(d===0)throw new e(`Cannot validate grouping: no hunk count available from input or session`);let f=await t({model:s.model,systemPrompt:s.session?void 0:s.promptOverride?.system??a(s.conventions,l,s.strategy),userPrompt:u,session:s.session,maxRetries:s.maxRetries,maxTokens:s.maxTokens,onProgress:s.onProgress,cancellation:s.cancellation,onBeforeModelCall:s.onBeforeModelCall,onAfterModelCall:s.onAfterModelCall,onBeforePrompt:s.onBeforePrompt,validate:e=>r(e,d,l,s.existingBranchNames),taskName:`group`}),p=s.session?.completedSteps??{},m={...f.session,completedSteps:{...p,group:f.result}};return{result:f.result,session:m,usage:f.usage,attempts:f.attempts,durationMs:f.durationMs,callIds:f.callIds}}export{s as group};
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"./validator.js";import{buildGroupContinuationPrompt as i,buildGroupSystemPrompt as a,buildGroupUserPrompt as o}from"./prompt.js";async function s(s){let c=s.hunks,l=s.mode??`branches`;if((!c||c.length===0)&&!s.session)throw new e(`No hunks provided: supply 'hunks' or run analyze() first with a session`);if(s.session?.completedSteps.group)return{result:s.session.completedSteps.group,session:s.session,attempts:0,durationMs:0,callIds:[]};let u=s.session?void 0:s.promptOverride?.system??a(s.conventions,l,s.strategy),d,f=n(s.session?i(s,l):o({hunks:c,commitMessages:s.commitMessages,instructions:s.instructions,mode:l,redactHunkContent:s.redactHunkContent,inputTokenBudget:s.inputTokenBudget,systemPromptChars:u?.length,onBudgetReduction:e=>{d=e}}),s.promptOverride),p=c?.length??s.session?.completedSteps.analyze?.hunkAnnotations.length??0;if(p===0)throw new e(`Cannot validate grouping: no hunk count available from input or session`);let m=await t({model:s.model,systemPrompt:u,userPrompt:f,session:s.session,maxRetries:s.maxRetries,maxTokens:s.maxTokens,onProgress:s.onProgress,cancellation:s.cancellation,onBeforeModelCall:s.onBeforeModelCall,onAfterModelCall:s.onAfterModelCall,onBeforePrompt:s.onBeforePrompt,validate:e=>r(e,p,l,s.existingBranchNames),taskName:`group`}),h=s.session?.completedSteps??{},g={...m.session,completedSteps:{...h,group:m.result}};return{result:m.result,session:g,usage:m.usage,attempts:m.attempts,durationMs:m.durationMs,callIds:m.callIds,hunksCondensedCount:d}}export{s as group};
@@ -10,7 +10,10 @@ declare function buildGroupUserPrompt(params: {
10
10
  commitMessages?: string[];
11
11
  instructions?: string;
12
12
  mode?: 'commits' | 'branches';
13
- redactHunkContent?: RedactHunkContent;
13
+ redactHunkContent?: RedactHunkContent; /** Input-token ceiling that triggers progressive hunk reduction. Defaults to the shared budget. */
14
+ inputTokenBudget?: number; /** Char count of the system prompt, folded into the budget estimate. Defaults to 0. */
15
+ systemPromptChars?: number; /** Reports how many hunks were condensed for budget (full to condensed), for consumer telemetry. */
16
+ onBudgetReduction?: (hunksCondensedForBudget: number) => void;
14
17
  }): string;
15
18
  declare function buildGroupContinuationPrompt(input: GroupInput, mode: 'commits' | 'branches'): string;
16
19
  //#endregion
@@ -1,8 +1,8 @@
1
- import{hunkContentForPrompt as e}from"../utils/redact.js";import{groupingResultSchema as t}from"./schema.js";import{z as n}from"zod";const r=JSON.stringify(n.toJSONSchema(t),null,2),i=`5. Write a full commit message for each commit containing both a summary and a description:
1
+ import{assembleHunkPrompt as e}from"../utils/budget.js";import{groupingResultJsonSchema as t}from"./schema.js";const n=JSON.stringify(t,null,2),r=`5. Write a full commit message for each commit containing both a summary and a description:
2
2
  - **Summary** (first line): concise, 50 chars max, third-person singular present tense ("Fixes", "Adds", "Improves"). This line stands alone as the commit subject.
3
3
  - **Blank line**: mandatory separator between summary and description.
4
4
  - **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.
5
- - Informal yet professional tone.`,a=`Each branch must have a descriptive name, a short PR-style title (under 72 characters, imperative mood), and at least one commit.`;function o(e,t=`branches`,n){return t===`commits`?s(e,n):c(e,n)}function s(e,t){return`You are an advanced AI programming assistant tasked with organizing code changes into commits. You will be working with individual code hunks.
5
+ - Informal yet professional tone.`,i=`5. Write a concise single-line commit message for each commit — a subject only, no body: 50 chars max, third-person singular present tense ("Fixes", "Adds", "Improves"). This repository's history overwhelmingly uses one-line commit messages, so do not add a description.`,a=`Each branch must have a descriptive name, a short PR-style title (under 72 characters, imperative mood), and at least one commit.`;function o(e,t=`branches`,n){return t===`commits`?s(e,n):c(e,n)}function s(e,t){return`You are an advanced AI programming assistant tasked with organizing code changes into commits. You will be working with individual code hunks.
6
6
 
7
7
  ## Critical constraints (output is rejected if violated)
8
8
 
@@ -17,11 +17,8 @@ Before producing the \`<output>\` tag, do a self-check: count the total number o
17
17
 
18
18
  1. Group hunks into logical units that make sense together and can be applied atomically
19
19
  2. Ensure each commit is self-contained and atomic
20
- ${e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
21
- 6. Write a detailed explanation for each commit as if explaining to a reviewer.
22
-
23
- ${e.promptFragment}`:`${i}
24
- 6. Write a detailed explanation for each commit as if explaining to a reviewer`}
20
+ ${`${e?.commits.bodyStyle===`minimal`?i:r}
21
+ 6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`}
25
22
  ${u(t?.commitGranularity)}
26
23
 
27
24
  ## Required Output Format
@@ -29,16 +26,13 @@ ${u(t?.commitGranularity)}
29
26
  Output your organization as JSON inside a <output> tag matching this schema:
30
27
 
31
28
  \`\`\`json
32
- ${r}
29
+ ${n}
33
30
  \`\`\`
34
31
 
35
32
  Wrap all commits in a single branch object with id "default", name "", title "", and description "".
36
33
 
37
- Return only the <output> tag and no other text.`}function c(e,t){let n=e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
38
- 6. Write a detailed explanation for each commit as if explaining to a reviewer.
39
-
40
- ${e.promptFragment}`:`${i}
41
- 6. Write a detailed explanation for each commit as if explaining to a reviewer`,a=l(t?.branchSplitting,e);return`You are an advanced AI programming assistant tasked with organizing code changes into commits and branches. You will be working with individual code hunks.
34
+ Return only the <output> tag and no other text.`}function c(e,t){let a=`${e?.commits.bodyStyle===`minimal`?i:r}
35
+ 6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`,o=l(t?.branchSplitting,e);return`You are an advanced AI programming assistant tasked with organizing code changes into commits and branches. You will be working with individual code hunks.
42
36
 
43
37
  Your job has two levels:
44
38
 
@@ -58,19 +52,19 @@ Before producing the \`<output>\` tag, do a self-check: count the total number o
58
52
 
59
53
  1. Group hunks into logical units that make sense together and can be applied atomically
60
54
  2. Ensure each commit is self-contained and atomic
61
- ${n}
55
+ ${a}
62
56
  ${u(t?.commitGranularity)}
63
57
 
64
58
  ## Branch guidelines
65
59
 
66
- ${a}
60
+ ${o}
67
61
 
68
62
  ## Required Output Format
69
63
 
70
64
  Output your organization as JSON inside a <output> tag matching this schema:
71
65
 
72
66
  \`\`\`json
73
- ${r}
67
+ ${n}
74
68
  \`\`\`
75
69
 
76
70
  Return only the <output> tag and no other text.`}function l(e,t){let n=`- Each commit belongs to exactly one branch.
@@ -87,20 +81,20 @@ ${r}
87
81
  ${r}
88
82
  - When in doubt, consider: "Would a reviewer want to see these changes together, or would separating them make review easier?" If separating helps, split.`}function u(e){return e===`consolidated`?`
89
83
  Prefer fewer, larger commits. Combine related changes into a single commit when they serve the same purpose. A commit can touch many files if they are all part of the same logical change.`:e===`granular`?`
90
- Prefer more, smaller commits. Each distinct change should be its own commit, even if multiple changes serve a similar purpose. Aim for commits that touch as few files as possible.`:``}function d(t){let{hunks:n,commitMessages:r,instructions:i,mode:a=`branches`,redactHunkContent:o}=t,s=JSON.stringify(n.map(t=>({index:t.index,fileName:t.fileName,hunkHeader:t.hunkHeader,content:e(t,o),...t.isBinary?{isBinary:!0}:{},...t.isRename?{isRename:!0}:{},...t.originalFileName?{originalFileName:t.originalFileName}:{}}))),c=`${a===`commits`?`Examine the following ${n.length} code hunks (indices 0 to ${n.length-1}) and organize them into commits. Every index must appear exactly once.`:`Examine the following ${n.length} code hunks (indices 0 to ${n.length-1}) and organize them into commits and branches. Every index must appear exactly once.`}
84
+ Prefer more, smaller commits. Each distinct change should be its own commit, even if multiple changes serve a similar purpose. Aim for commits that touch as few files as possible.`:``}function d(t){let{hunks:n,commitMessages:r,instructions:i,mode:a=`branches`,redactHunkContent:o,inputTokenBudget:s,systemPromptChars:c,onBudgetReduction:l}=t,u=a===`commits`?`Examine the following ${n.length} code hunks (indices 0 to ${n.length-1}) and organize them into commits. Every index must appear exactly once.`:`Examine the following ${n.length} code hunks (indices 0 to ${n.length-1}) and organize them into commits and branches. Every index must appear exactly once.`,{prompt:d,hunksCondensedForBudget:f}=e({hunks:n,assemble:e=>{let t=`${u}
91
85
 
92
86
  <hunks>
93
- ${s}
94
- </hunks>`;return r?.length&&(c+=`
87
+ ${e}
88
+ </hunks>`;return r?.length&&(t+=`
95
89
 
96
90
  The following commit messages are from the original commits. Use them to understand intent and match their style:
97
91
 
98
92
  <commit_messages>
99
93
  ${JSON.stringify(r)}
100
- </commit_messages>`),i&&(c+=`
94
+ </commit_messages>`),i&&(t+=`
101
95
 
102
96
  Carefully follow these additional instructions, but do not deviate from the output structure:
103
- ${i}`),c+=`\n\n${a===`commits`?`Now organize the hunks into commits.`:`Now organize the hunks into commits and branches.`} Return only the <output> tag and no other text.`,c}function f(e,t){let n;return n=t===`commits`?`Based on your analysis of the changes, now organize the hunks into commits. Each hunk must be assigned to exactly one commit.`:p(e.strategy),e.conventions?.promptFragment&&(n+=`\n\n${e.conventions.promptFragment}`),e.commitMessages?.length&&(n+=`\n\nMatch the style of these original commit messages:\n${JSON.stringify(e.commitMessages)}`),e.instructions&&(n+=`\n\nAdditional instructions:\n${e.instructions}`),t===`commits`?n+=`
97
+ ${i}`),t+=`\n\n${a===`commits`?`Now organize the hunks into commits.`:`Now organize the hunks into commits and branches.`} Return only the <output> tag and no other text.`,t},redactHunkContent:o,inputTokenBudget:s,systemPromptChars:c});return l?.(f),d}function f(e,t){let n;return n=t===`commits`?`Based on your analysis of the changes, now organize the hunks into commits. Each hunk must be assigned to exactly one commit.`:p(e.strategy),e.conventions?.promptFragment&&(n+=`\n\n${e.conventions.promptFragment}`),e.commitMessages?.length&&(n+=`\n\nMatch the style of these original commit messages:\n${JSON.stringify(e.commitMessages)}`),e.instructions&&(n+=`\n\nAdditional instructions:\n${e.instructions}`),t===`commits`?n+=`
104
98
 
105
99
  Return your grouping as JSON inside <output> tags. Wrap all commits in a single branch with id "default", name "", title "", description "":
106
100
  <output>
@@ -1,19 +1,91 @@
1
- import { z } from "zod";
2
-
3
1
  //#region src/tasks/group/schema.d.ts
4
- declare const groupingResultSchema: z.ZodObject<{
5
- branches: z.ZodArray<z.ZodObject<{
6
- id: z.ZodString;
7
- name: z.ZodString;
8
- title: z.ZodString;
9
- description: z.ZodString;
10
- commits: z.ZodArray<z.ZodObject<{
11
- id: z.ZodString;
12
- message: z.ZodString;
13
- explanation: z.ZodString;
14
- hunks: z.ZodArray<z.ZodNumber>;
15
- }, z.core.$strip>>;
16
- }, z.core.$strip>>;
17
- }, z.core.$strip>;
2
+ /** Shape of the LLM JSON output before the validator maps it to public types (`hunks` → `hunkIndices`). */
3
+ interface GroupingWire {
4
+ branches: GroupingWireBranch[];
5
+ }
6
+ interface GroupingWireBranch {
7
+ id: string;
8
+ name: string;
9
+ title: string;
10
+ description: string;
11
+ commits: GroupingWireCommit[];
12
+ }
13
+ interface GroupingWireCommit {
14
+ id: string;
15
+ message: string;
16
+ explanation: string;
17
+ hunks: number[];
18
+ }
19
+ declare const groupingResultJsonSchema: {
20
+ readonly $schema: "https://json-schema.org/draft/2020-12/schema";
21
+ readonly type: "object";
22
+ readonly properties: {
23
+ readonly branches: {
24
+ readonly minItems: 1;
25
+ readonly type: "array";
26
+ readonly items: {
27
+ readonly type: "object";
28
+ readonly properties: {
29
+ readonly id: {
30
+ readonly type: "string";
31
+ readonly minLength: 1;
32
+ readonly description: "Unique branch ID";
33
+ };
34
+ readonly name: {
35
+ readonly type: "string";
36
+ readonly description: "Branch name (empty string for commits-only mode)";
37
+ };
38
+ readonly title: {
39
+ readonly type: "string";
40
+ readonly description: "Short PR-style title for this branch (under 72 chars, imperative mood, e.g. 'Add user authentication')";
41
+ };
42
+ readonly description: {
43
+ readonly type: "string";
44
+ readonly description: "What this branch accomplishes";
45
+ };
46
+ readonly commits: {
47
+ readonly minItems: 1;
48
+ readonly type: "array";
49
+ readonly items: {
50
+ readonly type: "object";
51
+ readonly properties: {
52
+ readonly id: {
53
+ readonly type: "string";
54
+ readonly description: "Unique commit ID (e.g. commit-1)";
55
+ };
56
+ readonly message: {
57
+ readonly type: "string";
58
+ readonly description: "Commit message";
59
+ };
60
+ readonly explanation: {
61
+ readonly type: "string";
62
+ readonly description: "Detailed explanation of the commit";
63
+ };
64
+ readonly hunks: {
65
+ readonly type: "array";
66
+ readonly items: {
67
+ readonly type: "integer";
68
+ readonly minimum: -9007199254740991;
69
+ readonly maximum: 9007199254740991;
70
+ readonly description: "Hunk index";
71
+ };
72
+ readonly description: "Hunk indices included in this commit";
73
+ };
74
+ };
75
+ readonly required: readonly ["id", "message", "explanation", "hunks"];
76
+ readonly additionalProperties: false;
77
+ };
78
+ readonly description: "Commits in this branch";
79
+ };
80
+ };
81
+ readonly required: readonly ["id", "name", "title", "description", "commits"];
82
+ readonly additionalProperties: false;
83
+ };
84
+ readonly description: "Branches containing grouped commits";
85
+ };
86
+ };
87
+ readonly required: readonly ["branches"];
88
+ readonly additionalProperties: false;
89
+ };
18
90
  //#endregion
19
- export { groupingResultSchema };
91
+ export { GroupingWire, GroupingWireBranch, GroupingWireCommit, groupingResultJsonSchema };
@@ -1 +1 @@
1
- import{z as e}from"zod";const t=e.object({id:e.string().describe(`Unique commit ID (e.g. commit-1)`),message:e.string().describe(`Commit message`),explanation:e.string().describe(`Detailed explanation of the commit`),hunks:e.array(e.number().int().describe(`Hunk index`)).describe(`Hunk indices included in this commit`)}),n=e.object({id:e.string().min(1).describe(`Unique branch ID`),name:e.string().describe(`Branch name (empty string for commits-only mode)`),title:e.string().describe(`Short PR-style title for this branch (under 72 chars, imperative mood, e.g. 'Add user authentication')`),description:e.string().describe(`What this branch accomplishes`),commits:e.array(t).min(1).describe(`Commits in this branch`)}),r=e.object({branches:e.array(n).min(1).describe(`Branches containing grouped commits`)});export{r as groupingResultSchema};
1
+ const e={$schema:`https://json-schema.org/draft/2020-12/schema`,type:`object`,properties:{branches:{minItems:1,type:`array`,items:{type:`object`,properties:{id:{type:`string`,minLength:1,description:`Unique branch ID`},name:{type:`string`,description:`Branch name (empty string for commits-only mode)`},title:{type:`string`,description:`Short PR-style title for this branch (under 72 chars, imperative mood, e.g. 'Add user authentication')`},description:{type:`string`,description:`What this branch accomplishes`},commits:{minItems:1,type:`array`,items:{type:`object`,properties:{id:{type:`string`,description:`Unique commit ID (e.g. commit-1)`},message:{type:`string`,description:`Commit message`},explanation:{type:`string`,description:`Detailed explanation of the commit`},hunks:{type:`array`,items:{type:`integer`,minimum:-9007199254740991,maximum:9007199254740991,description:`Hunk index`},description:`Hunk indices included in this commit`}},required:[`id`,`message`,`explanation`,`hunks`],additionalProperties:!1},description:`Commits in this branch`}},required:[`id`,`name`,`title`,`description`,`commits`],additionalProperties:!1},description:`Branches containing grouped commits`}},required:[`branches`],additionalProperties:!1};export{e as groupingResultJsonSchema};
@@ -38,6 +38,12 @@ interface GroupInput {
38
38
  existingBranchNames?: Set<string>;
39
39
  /** See {@link RedactHunkContent}. */
40
40
  redactHunkContent?: RedactHunkContent;
41
+ /**
42
+ * Input-token ceiling for the hunk prompt. When exceeded, hunks are progressively reduced
43
+ * (biggest-first) to metadata; a diff that can't fit even condensed throws `DiffTooLargeError`.
44
+ * Defaults to `DEFAULT_INPUT_TOKEN_BUDGET`. Only applies to the initial (non-session) call.
45
+ */
46
+ inputTokenBudget?: number;
41
47
  model: AiModelPort;
42
48
  session?: ComposeSession;
43
49
  maxRetries?: number;
@@ -61,6 +67,8 @@ interface GroupOutput {
61
67
  attempts: number;
62
68
  durationMs: number;
63
69
  callIds: string[];
70
+ /** Hunks condensed to metadata to fit the input-token budget. Undefined when the AI call was skipped. */
71
+ hunksCondensedCount?: number;
64
72
  }
65
73
  //#endregion
66
74
  export { GroupInput, GroupOutput, GroupingResult, GroupingStrategy };
@@ -1,9 +1,9 @@
1
- import{extractOutputTag as e}from"../ai-loop.js";import{groupingResultSchema as t}from"./schema.js";function n(n,a,o=`branches`,s){let c=null;try{if(c=e(n),!c)return r([`No <output> tag found in response`],i(o));let l=JSON.parse(c),u=t.safeParse(l);if(!u.success)return r(u.error.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`),i(o));let d=u.data,f=[],p=new Set,m=[],h=new Map,g=new Set,_=[],v=[],y=new Set,b=new Set;for(let e of d.branches){if(y.has(e.id))return r([`Duplicate branch ID: '${e.id}'`],i(o));if(y.add(e.id),o===`branches`&&e.name.length===0)return r([`Branch '${e.id}': missing or empty 'name'`],i(o));if(o===`branches`&&e.name.length>0){if(b.has(e.name))return r([`Duplicate branch name: '${e.name}'`],`Multiple branches use the name '${e.name}'. Each branch must have a unique name. Please rename one of them.`);if(b.add(e.name),s?.has(e.name))return r([`Branch name '${e.name}' already exists in the repository`],`The branch name '${e.name}' already exists in the repository. Please choose a different name for branch '${e.id}'.`)}let t=[];for(let n of e.commits){g.has(n.id)&&_.push(n.id),g.add(n.id);let r=[],i=o===`branches`?`${e.id}/${n.id}`:n.id;for(let e of n.hunks){p.has(e)&&m.push(e),p.add(e),r.push(e);let t=h.get(e);t?t.push(i):h.set(e,[i])}r.length===0&&v.push(n.id),t.push({id:n.id,message:n.message,explanation:n.explanation,hunkIndices:r})}f.push({id:e.id,name:e.name,title:e.title,description:e.description,commits:t})}if(_.length>0)return r([`Duplicate commit IDs: ${_.join(`, `)}`],`Some commit IDs appear multiple times. Each must be unique.\n\nDuplicates: ${_.join(`, `)}`);if(v.length>0)return r([`Empty commits (no hunks assigned): ${v.join(`, `)}`],`Every commit must contain at least one hunk. These commits have an empty \`hunks\` array: ${v.join(`, `)}.
1
+ import{extractOutputTag as e}from"../ai-loop.js";import{validateJsonSchema as t}from"../utils/json-schema.js";import{groupingResultJsonSchema as n}from"./schema.js";function r(r,o,s=`branches`,c){let l=null;try{if(l=e(r),!l)return i([`No <output> tag found in response`],a(s));let u=t(n,JSON.parse(l));if(!u.success)return i(u.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`),a(s));let d=u.data,f=[],p=new Set,m=[],h=new Map,g=new Set,_=[],v=[],y=new Set,b=new Set;for(let e of d.branches){if(y.has(e.id))return i([`Duplicate branch ID: '${e.id}'`],a(s));if(y.add(e.id),s===`branches`&&e.name.length===0)return i([`Branch '${e.id}': missing or empty 'name'`],a(s));if(s===`branches`&&e.name.length>0){if(b.has(e.name))return i([`Duplicate branch name: '${e.name}'`],`Multiple branches use the name '${e.name}'. Each branch must have a unique name. Please rename one of them.`);if(b.add(e.name),c?.has(e.name))return i([`Branch name '${e.name}' already exists in the repository`],`The branch name '${e.name}' already exists in the repository. Please choose a different name for branch '${e.id}'.`)}let t=[];for(let n of e.commits){g.has(n.id)&&_.push(n.id),g.add(n.id);let r=[],i=s===`branches`?`${e.id}/${n.id}`:n.id;for(let e of n.hunks){p.has(e)&&m.push(e),p.add(e),r.push(e);let t=h.get(e);t?t.push(i):h.set(e,[i])}r.length===0&&v.push(n.id),t.push({id:n.id,message:n.message,explanation:n.explanation,hunkIndices:r})}f.push({id:e.id,name:e.name,title:e.title,description:e.description,commits:t})}if(_.length>0)return i([`Duplicate commit IDs: ${_.join(`, `)}`],`Some commit IDs appear multiple times. Each must be unique.\n\nDuplicates: ${_.join(`, `)}`);if(v.length>0)return i([`Empty commits (no hunks assigned): ${v.join(`, `)}`],`Every commit must contain at least one hunk. These commits have an empty \`hunks\` array: ${v.join(`, `)}.
2
2
 
3
- Either assign at least one hunk to each of these commits, or remove them entirely. If a commit's purpose is already covered by another commit, remove the duplicate. If a commit is supposed to cover specific hunks, assign those hunk indices to it. Every hunk index still needs to appear in exactly one commit.`);let x=new Set(Array.from({length:a},(e,t)=>t)),S=[...x].filter(e=>!p.has(e)),C=[...p].filter(e=>!x.has(e));if(m.length>0||S.length>0||C.length>0){let e=[],t=[];if(m.length>0){let n=[...new Set(m)].sort((e,t)=>e-t),r=n.map(e=>` hunk ${e} → ${(h.get(e)??[]).join(`, `)}`).join(`
4
- `);e.push(`Duplicate hunks (each must appear in exactly ONE commit):\n${r}`),t.push(`Duplicate hunks: ${n.join(`, `)}`)}S.length>0&&(e.push(`Missing hunks (not assigned to any commit): ${S.join(`, `)}`),t.push(`Missing hunks: ${S.join(`, `)}`)),C.length>0&&(e.push(`Extra hunks (index out of range): ${C.join(`, `)}`),t.push(`Extra hunks: ${C.join(`, `)}`));let n=[...h.values()].reduce((e,t)=>e+t.length,0);return r(t,`There are ${a} hunks (indices 0 to ${a-1}). Every index must appear in exactly one commit — no duplicates, no gaps, no out-of-range indices.
3
+ Either assign at least one hunk to each of these commits, or remove them entirely. If a commit's purpose is already covered by another commit, remove the duplicate. If a commit is supposed to cover specific hunks, assign those hunk indices to it. Every hunk index still needs to appear in exactly one commit.`);let x=new Set(Array.from({length:o},(e,t)=>t)),S=[...x].filter(e=>!p.has(e)),C=[...p].filter(e=>!x.has(e));if(m.length>0||S.length>0||C.length>0){let e=[],t=[];if(m.length>0){let n=[...new Set(m)].sort((e,t)=>e-t),r=n.map(e=>` hunk ${e} → ${(h.get(e)??[]).join(`, `)}`).join(`
4
+ `);e.push(`Duplicate hunks (each must appear in exactly ONE commit):\n${r}`),t.push(`Duplicate hunks: ${n.join(`, `)}`)}S.length>0&&(e.push(`Missing hunks (not assigned to any commit): ${S.join(`, `)}`),t.push(`Missing hunks: ${S.join(`, `)}`)),C.length>0&&(e.push(`Extra hunks (index out of range): ${C.join(`, `)}`),t.push(`Extra hunks: ${C.join(`, `)}`));let n=[...h.values()].reduce((e,t)=>e+t.length,0);return i(t,`There are ${o} hunks (indices 0 to ${o-1}). Every index must appear in exactly one commit — no duplicates, no gaps, no out-of-range indices.
5
5
 
6
- Your response listed ${n} hunk references across ${h.size} unique indices. Expected: ${a} references across ${a} unique indices.
6
+ Your response listed ${n} hunk references across ${h.size} unique indices. Expected: ${o} references across ${o} unique indices.
7
7
 
8
8
  Specific problems:
9
9
  ${e.map(e=>`- ${e}`).join(`
@@ -11,7 +11,7 @@ ${e.map(e=>`- ${e}`).join(`
11
11
 
12
12
  To fix duplicates: for each duplicated hunk index, decide which one commit it truly belongs in and remove it from the others. Do not split a single hunk across two commits — git only knows how to apply a hunk once.
13
13
 
14
- Return the corrected JSON inside <output> tags.`)}return{valid:!0,result:{branches:f}}}catch(e){let t=e instanceof Error?e.message:String(e),a=(c??n).slice(-300);return r([`Failed to parse response as valid JSON: ${t}`,`Response tail: ${a}`],i(o))}}function r(e,t){return{valid:!1,errors:e,retryFeedback:t}}function i(e){return e===`commits`?`Your response has an invalid structure. Please return JSON inside <output> tags. Use a single branch with id "default", name "", description "":
14
+ Return the corrected JSON inside <output> tags.`)}return{valid:!0,result:{branches:f}}}catch(e){let t=e instanceof Error?e.message:String(e),n=(l??r).slice(-300);return i([`Failed to parse response as valid JSON: ${t}`,`Response tail: ${n}`],a(s))}}function i(e,t){return{valid:!1,errors:e,retryFeedback:t}}function a(e){return e===`commits`?`Your response has an invalid structure. Please return JSON inside <output> tags. Use a single branch with id "default", name "", description "":
15
15
  <output>
16
16
  {
17
17
  "branches": [
@@ -55,4 +55,4 @@ Return only the <output> tag and no other text.`:`Your response has an invalid s
55
55
  }
56
56
  </output>
57
57
 
58
- Return only the <output> tag and no other text.`}export{n as validateGroupResponse};
58
+ Return only the <output> tag and no other text.`}export{r as validateGroupResponse};
@@ -1,4 +1,4 @@
1
- import{orderingResultSchema 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 programming assistant tasked with determining the optimal order for commits within each branch. Your goal is to order commits so that:
1
+ import{orderingResultJsonSchema as e}from"./schema.js";const t=JSON.stringify(e,null,2);function n(){return`You are an advanced AI programming assistant tasked with determining the optimal order for commits within each branch. Your goal is to order commits so that:
2
2
 
3
3
  1. **Dependencies are respected** — if commit A introduces something that commit B relies on, A must come before B
4
4
  2. **Refactoring before features** — structural improvements and refactors should precede new functionality that builds on them
@@ -13,12 +13,12 @@ You will receive one or more branches, each containing unordered commits. Order
13
13
  Output the ordered commit IDs per branch as JSON inside a <output> tag matching this schema:
14
14
 
15
15
  \`\`\`json
16
- ${n}
16
+ ${t}
17
17
  \`\`\`
18
18
 
19
- Return only the <output> tag and no other text.`}function i(e){let{branches:t,hunks:n,instructions:r}=e,i=`Determine the optimal commit order within each branch:\n\n<branches>\n${JSON.stringify(t.map(e=>({id:e.id,name:e.name,commits:e.commits.map(e=>({id:e.id,message:e.message,explanation:e.explanation,hunkIndices:e.hunkIndices}))})))}\n</branches>`;if(n?.length){let e=JSON.stringify(n.map(e=>({index:e.index,fileName:e.fileName,hunkHeader:e.hunkHeader})));i+=`\n\nFor reference, here is the hunk index to file mapping:\n<hunk_map>\n${e}\n</hunk_map>`}return r&&(i+=`\n\nAdditional instructions:\n${r}`),i+=`
19
+ Return only the <output> tag and no other text.`}function r(e){let{branches:t,hunks:n,instructions:r}=e,i=`Determine the optimal commit order within each branch:\n\n<branches>\n${JSON.stringify(t.map(e=>({id:e.id,name:e.name,commits:e.commits.map(e=>({id:e.id,message:e.message,explanation:e.explanation,hunkIndices:e.hunkIndices}))})))}\n</branches>`;if(n?.length){let e=JSON.stringify(n.map(e=>({index:e.index,fileName:e.fileName,hunkHeader:e.hunkHeader})));i+=`\n\nFor reference, here is the hunk index to file mapping:\n<hunk_map>\n${e}\n</hunk_map>`}return r&&(i+=`\n\nAdditional instructions:\n${r}`),i+=`
20
20
 
21
- Return the ordered commit IDs per branch. Return only the <output> tag and no other text.`,i}function a(e){let t=`Based on the branches and commits you created, determine the optimal order for the commits within each branch.
21
+ Return the ordered commit IDs per branch. Return only the <output> tag and no other text.`,i}function i(e){let t=`Based on the branches and commits you created, determine the optimal order for the commits within each branch.
22
22
 
23
23
  Ordering principles:
24
24
  1. Dependencies first — if commit A introduces something commit B relies on, A must come before B
@@ -42,4 +42,4 @@ Return the ordered commit IDs per branch as JSON inside <output> tags:
42
42
  }
43
43
  </output>
44
44
 
45
- Return only the <output> tag and no other text.`,t}export{a as buildOrderContinuationPrompt,r as buildOrderSystemPrompt,i as buildOrderUserPrompt};
45
+ Return only the <output> tag and no other text.`,t}export{i as buildOrderContinuationPrompt,n as buildOrderSystemPrompt,r as buildOrderUserPrompt};
@@ -1,12 +1,37 @@
1
- import { z } from "zod";
2
-
3
1
  //#region src/tasks/order/schema.d.ts
4
- declare const orderingResultSchema: z.ZodObject<{
5
- branches: z.ZodArray<z.ZodObject<{
6
- branchId: z.ZodString;
7
- orderedCommitIds: z.ZodArray<z.ZodString>;
8
- }, z.core.$strip>>;
9
- rationale: z.ZodString;
10
- }, z.core.$strip>;
2
+ declare const orderingResultJsonSchema: {
3
+ readonly $schema: "https://json-schema.org/draft/2020-12/schema";
4
+ readonly type: "object";
5
+ readonly properties: {
6
+ readonly branches: {
7
+ readonly type: "array";
8
+ readonly items: {
9
+ readonly type: "object";
10
+ readonly properties: {
11
+ readonly branchId: {
12
+ readonly type: "string";
13
+ readonly description: "ID of the branch being ordered";
14
+ };
15
+ readonly orderedCommitIds: {
16
+ readonly type: "array";
17
+ readonly items: {
18
+ readonly type: "string";
19
+ };
20
+ readonly description: "Commit IDs in optimal order";
21
+ };
22
+ };
23
+ readonly required: readonly ["branchId", "orderedCommitIds"];
24
+ readonly additionalProperties: false;
25
+ };
26
+ readonly description: "Ordered commits per branch";
27
+ };
28
+ readonly rationale: {
29
+ readonly type: "string";
30
+ readonly description: "Explanation of ordering decisions";
31
+ };
32
+ };
33
+ readonly required: readonly ["branches", "rationale"];
34
+ readonly additionalProperties: false;
35
+ };
11
36
  //#endregion
12
- export { orderingResultSchema };
37
+ export { orderingResultJsonSchema };
@@ -1 +1 @@
1
- import{z as e}from"zod";const t=e.object({branchId:e.string().describe(`ID of the branch being ordered`),orderedCommitIds:e.array(e.string()).describe(`Commit IDs in optimal order`)}),n=e.object({branches:e.array(t).describe(`Ordered commits per branch`),rationale:e.string().describe(`Explanation of ordering decisions`)});export{n as orderingResultSchema};
1
+ const e={$schema:`https://json-schema.org/draft/2020-12/schema`,type:`object`,properties:{branches:{type:`array`,items:{type:`object`,properties:{branchId:{type:`string`,description:`ID of the branch being ordered`},orderedCommitIds:{type:`array`,items:{type:`string`},description:`Commit IDs in optimal order`}},required:[`branchId`,`orderedCommitIds`],additionalProperties:!1},description:`Ordered commits per branch`},rationale:{type:`string`,description:`Explanation of ordering decisions`}},required:[`branches`,`rationale`],additionalProperties:!1};export{e as orderingResultJsonSchema};
@@ -1,4 +1,4 @@
1
- import{extractOutputTag as e}from"../ai-loop.js";import{orderingResultSchema 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(a));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(a));let l=c.data,u=[],d=new Set;for(let e of l.branches){d.add(e.branchId);let t=a.find(t=>t.branchId===e.branchId);if(!t)return r([`Unknown branch ID: '${e.branchId}'`],`Branch ID '${e.branchId}' doesn't match any known branch.\n\nKnown branches: ${a.map(e=>e.branchId).join(`, `)}`);let n=new Set(t.commitIds),i=new Set,o=[],s=[];for(let t of e.orderedCommitIds)i.has(t)&&o.push(t),n.has(t)||s.push(t),i.add(t);if(o.length>0)return r([`Branch '${e.branchId}': duplicate commit IDs: ${o.join(`, `)}`],`Branch '${e.branchId}' has duplicate commits. Each must appear exactly once.\n\nDuplicates: ${o.join(`, `)}`);if(s.length>0)return r([`Branch '${e.branchId}': unknown commit IDs: ${s.join(`, `)}`],`Branch '${e.branchId}' references unknown commits.\n\nUnknown: ${s.join(`, `)}\nValid: ${t.commitIds.join(`, `)}`);let c=t.commitIds.filter(e=>!i.has(e));if(c.length>0)return r([`Branch '${e.branchId}': missing commit IDs: ${c.join(`, `)}`],`Branch '${e.branchId}' is missing some commits.\n\nMissing: ${c.join(`, `)}`);u.push({branchId:e.branchId,orderedCommitIds:e.orderedCommitIds})}let f=a.filter(e=>!d.has(e.branchId));return f.length>0?r([`Missing branches: ${f.map(e=>e.branchId).join(`, `)}`],`Some branches are not included in the ordering.\n\nMissing: ${f.map(e=>e.branchId).join(`, `)}`):{valid:!0,result:{branches:u,rationale:l.rationale}}}catch(e){let t=e instanceof Error?e.message:String(e),s=(o??n).slice(-300);return r([`Failed to parse response as valid JSON: ${t}`,`Response tail: ${s}`],i(a))}}function r(e,t){return{valid:!1,errors:e,retryFeedback:t}}function i(e){return`Your response has an invalid structure. Return JSON inside <output> tags:
1
+ import{extractOutputTag as e}from"../ai-loop.js";import{validateJsonSchema as t}from"../utils/json-schema.js";import{orderingResultJsonSchema as n}from"./schema.js";function r(r,o){let s=null;try{if(s=e(r),!s)return i([`No <output> tag found in response`],a(o));let c=t(n,JSON.parse(s));if(!c.success)return i(c.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`),a(o));let l=c.data,u=[],d=new Set;for(let e of l.branches){d.add(e.branchId);let t=o.find(t=>t.branchId===e.branchId);if(!t)return i([`Unknown branch ID: '${e.branchId}'`],`Branch ID '${e.branchId}' doesn't match any known branch.\n\nKnown branches: ${o.map(e=>e.branchId).join(`, `)}`);let n=new Set(t.commitIds),r=new Set,a=[],s=[];for(let t of e.orderedCommitIds)r.has(t)&&a.push(t),n.has(t)||s.push(t),r.add(t);if(a.length>0)return i([`Branch '${e.branchId}': duplicate commit IDs: ${a.join(`, `)}`],`Branch '${e.branchId}' has duplicate commits. Each must appear exactly once.\n\nDuplicates: ${a.join(`, `)}`);if(s.length>0)return i([`Branch '${e.branchId}': unknown commit IDs: ${s.join(`, `)}`],`Branch '${e.branchId}' references unknown commits.\n\nUnknown: ${s.join(`, `)}\nValid: ${t.commitIds.join(`, `)}`);let c=t.commitIds.filter(e=>!r.has(e));if(c.length>0)return i([`Branch '${e.branchId}': missing commit IDs: ${c.join(`, `)}`],`Branch '${e.branchId}' is missing some commits.\n\nMissing: ${c.join(`, `)}`);u.push({branchId:e.branchId,orderedCommitIds:e.orderedCommitIds})}let f=o.filter(e=>!d.has(e.branchId));return f.length>0?i([`Missing branches: ${f.map(e=>e.branchId).join(`, `)}`],`Some branches are not included in the ordering.\n\nMissing: ${f.map(e=>e.branchId).join(`, `)}`):{valid:!0,result:{branches:u,rationale:l.rationale}}}catch(e){let t=e instanceof Error?e.message:String(e),n=(s??r).slice(-300);return i([`Failed to parse response as valid JSON: ${t}`,`Response tail: ${n}`],a(o))}}function i(e,t){return{valid:!1,errors:e,retryFeedback:t}}function a(e){return`Your response has an invalid structure. Return JSON inside <output> tags:
2
2
  <output>
3
3
  {
4
4
  "branches": [
@@ -9,4 +9,4 @@ ${e.map(e=>` { "branchId": "${e.branchId}", "orderedCommitIds": ${JSON.string
9
9
  }
10
10
  </output>
11
11
 
12
- All branches must be present with all their commits ordered. Return only the <output> tag and no other text.`}export{n as validateOrderResponse};
12
+ All branches must be present with all their commits ordered. Return only the <output> tag and no other text.`}export{r as validateOrderResponse};