@gitkraken/compose-tools 0.5.0 → 0.6.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 (48) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/errors/task.d.ts +11 -1
  3. package/dist/errors/task.js +1 -1
  4. package/dist/git/utils/conventions.d.ts +7 -0
  5. package/dist/git/utils/conventions.js +8 -9
  6. package/dist/index.d.ts +4 -2
  7. package/dist/index.js +1 -1
  8. package/dist/tasks/analyze/analyze.js +1 -1
  9. package/dist/tasks/analyze/prompt.d.ts +4 -1
  10. package/dist/tasks/analyze/prompt.js +5 -5
  11. package/dist/tasks/analyze/schema.d.ts +127 -36
  12. package/dist/tasks/analyze/schema.js +1 -1
  13. package/dist/tasks/analyze/types.d.ts +8 -0
  14. package/dist/tasks/analyze/validator.js +2 -2
  15. package/dist/tasks/compose-group/compose-group.js +1 -1
  16. package/dist/tasks/compose-group/prompt.d.ts +4 -1
  17. package/dist/tasks/compose-group/prompt.js +23 -29
  18. package/dist/tasks/compose-group/types.d.ts +5 -0
  19. package/dist/tasks/group/group.js +1 -1
  20. package/dist/tasks/group/prompt.d.ts +4 -1
  21. package/dist/tasks/group/prompt.js +15 -21
  22. package/dist/tasks/group/schema.d.ts +89 -17
  23. package/dist/tasks/group/schema.js +1 -1
  24. package/dist/tasks/group/types.d.ts +8 -0
  25. package/dist/tasks/group/validator.js +6 -6
  26. package/dist/tasks/order/prompt.js +5 -5
  27. package/dist/tasks/order/schema.d.ts +35 -10
  28. package/dist/tasks/order/schema.js +1 -1
  29. package/dist/tasks/order/validator.js +2 -2
  30. package/dist/tasks/partition/prompt.js +9 -9
  31. package/dist/tasks/partition/schema.d.ts +46 -12
  32. package/dist/tasks/partition/schema.js +1 -1
  33. package/dist/tasks/partition/validator.js +2 -2
  34. package/dist/tasks/quick-compose/prompt.js +8 -9
  35. package/dist/tasks/quick-compose/schema.d.ts +142 -25
  36. package/dist/tasks/quick-compose/schema.js +1 -1
  37. package/dist/tasks/quick-compose/validator.js +5 -5
  38. package/dist/tasks/utils/budget.d.ts +40 -0
  39. package/dist/tasks/utils/budget.js +1 -0
  40. package/dist/tasks/utils/json-schema.d.ts +61 -0
  41. package/dist/tasks/utils/json-schema.js +1 -0
  42. package/dist/tasks/utils/noise.d.ts +21 -0
  43. package/dist/tasks/utils/noise.js +2 -0
  44. package/dist/tasks/utils/redact.d.ts +7 -1
  45. package/dist/tasks/utils/redact.js +1 -1
  46. package/dist/workflows/compose.d.ts +36 -0
  47. package/dist/workflows/compose.js +3 -3
  48. package/package.json +2 -2
@@ -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};
@@ -1,4 +1,4 @@
1
- import{partitionResultSchema as e}from"./schema.js";import{z as t}from"zod";const n=JSON.stringify(t.toJSONSchema(e),null,2);function r(e,t){let r=e?.branches&&e.branches.format!==`unknown`?`For each sub-branch, name it following the repository's branch naming conventions.\n\n${e.promptFragment}`:`For each sub-branch, provide a branch name derived from the source branch name (e.g., "feature/part-1-description").`;return`You are an advanced AI programming assistant tasked with determining whether an ordered list of commits on a single branch should be subdivided into stacked branches for a pull request workflow.
1
+ import{partitionResultJsonSchema as e}from"./schema.js";const t=JSON.stringify(e,null,2);function n(e,n){let i=e?.branches&&e.branches.format!==`unknown`?`For each sub-branch, name it following the repository's branch naming conventions.\n\n${e.promptFragment}`:`For each sub-branch, provide a branch name derived from the source branch name (e.g., "feature/part-1-description").`;return`You are an advanced AI programming assistant tasked with determining whether an ordered list of commits on a single branch should be subdivided into stacked branches for a pull request workflow.
2
2
 
3
3
  Your goal is to examine the commits and decide whether they form one cohesive unit or whether there are natural subdivision points that would benefit from stacked PRs.
4
4
 
@@ -9,9 +9,9 @@ Rules:
9
9
  4. A single sub-branch (no subdivision) is a perfectly valid and common outcome
10
10
  5. A single-commit sub-branch is valid if that commit is independently mergeable
11
11
 
12
- ${i(t?.splitting)}
12
+ ${r(n?.splitting)}
13
13
 
14
- ${r}
14
+ ${i}
15
15
  - A short PR-style title (under 72 characters, imperative mood)
16
16
  - A description of what the sub-branch accomplishes
17
17
  - The commit IDs it contains (in order)
@@ -21,10 +21,10 @@ ${r}
21
21
  Output your partition as JSON inside a <output> tag matching this schema:
22
22
 
23
23
  \`\`\`json
24
- ${n}
24
+ ${t}
25
25
  \`\`\`
26
26
 
27
- Return only the <output> tag and no other text.`}function i(e){let t=`Consider merge-order preference: even if sub-groups compile independently, keep them together if merging one without the other would be incomplete or confusing to reviewers.`;return e===`conservative`?`Keep commits as a single branch unless the sub-groups address completely different concerns with no merge-order preference between them.
27
+ Return only the <output> tag and no other text.`}function r(e){let t=`Consider merge-order preference: even if sub-groups compile independently, keep them together if merging one without the other would be incomplete or confusing to reviewers.`;return e===`conservative`?`Keep commits as a single branch unless the sub-groups address completely different concerns with no merge-order preference between them.
28
28
  ${t}
29
29
  Changes addressing the same pattern across different files should stay together. When in doubt, do not partition.`:e===`aggressive`?`Subdivide whenever there is a clear concern boundary between contiguous groups of commits. Each sub-group should be as focused as possible.
30
30
  ${t}
@@ -34,10 +34,10 @@ Do NOT partition just because commits are distinct. A branch with a refactor com
34
34
 
35
35
  Changes addressing the same pattern across different files should stay together. For example, applying a similar fix to multiple functions belongs in one branch, not split across multiple stacked PRs.
36
36
 
37
- ${t}`}function a(e){let{commits:t,orderedCommitIds:n,sourceBranchName:r,baseBranch:i,instructions:a}=e,o=n.map(e=>{let n=t.find(t=>t.id===e);return n?{id:n.id,message:n.message,explanation:n.explanation}:{id:e,message:`unknown`,explanation:``}}),s=`Determine whether these ordered commits should be subdivided into stacked branches:\n\n<commits>\n${JSON.stringify(o)}\n</commits>`,c=[];return r&&c.push(`Source branch: ${r}`),i&&c.push(`Base branch: ${i}`),c.length>0&&(s+=`\n\n<context>\n${c.join(`
37
+ ${t}`}function i(e){let{commits:t,orderedCommitIds:n,sourceBranchName:r,baseBranch:i,instructions:a}=e,o=n.map(e=>{let n=t.find(t=>t.id===e);return n?{id:n.id,message:n.message,explanation:n.explanation}:{id:e,message:`unknown`,explanation:``}}),s=`Determine whether these ordered commits should be subdivided into stacked branches:\n\n<commits>\n${JSON.stringify(o)}\n</commits>`,c=[];return r&&c.push(`Source branch: ${r}`),i&&c.push(`Base branch: ${i}`),c.length>0&&(s+=`\n\n<context>\n${c.join(`
38
38
  `)}\n</context>`),a&&(s+=`\n\nAdditional instructions:\n${a}`),s+=`
39
39
 
40
- Return the partition. Return only the <output> tag and no other text.`,s}function o(e){let t=s(e.strategy);return e.sourceBranchName&&(t+=`\n\nSource branch: ${e.sourceBranchName}`),e.baseBranch&&(t+=`\nBase branch: ${e.baseBranch}`),e.conventions?.promptFragment&&(t+=`\n\n${e.conventions.promptFragment}`),e.instructions&&(t+=`\n\nAdditional instructions:\n${e.instructions}`),t+=`
40
+ Return the partition. Return only the <output> tag and no other text.`,s}function a(e){let t=o(e.strategy);return e.sourceBranchName&&(t+=`\n\nSource branch: ${e.sourceBranchName}`),e.baseBranch&&(t+=`\nBase branch: ${e.baseBranch}`),e.conventions?.promptFragment&&(t+=`\n\n${e.conventions.promptFragment}`),e.instructions&&(t+=`\n\nAdditional instructions:\n${e.instructions}`),t+=`
41
41
 
42
42
  Return the partition as JSON inside <output> tags:
43
43
  <output>
@@ -49,7 +49,7 @@ Return the partition as JSON inside <output> tags:
49
49
  }
50
50
  </output>
51
51
 
52
- Return only the <output> tag and no other text.`,t}function s(e){let t=e?.splitting??`balanced`,n=`Based on the ordered commits for this branch, determine whether they should be subdivided into stacked sub-branches. Each sub-branch must be a contiguous range of commits.`,r=`Consider merge-order preference: even if sub-groups compile independently, keep them together if merging one without the other would be incomplete or confusing.`;return t===`conservative`?`${n}
52
+ Return only the <output> tag and no other text.`,t}function o(e){let t=e?.splitting??`balanced`,n=`Based on the ordered commits for this branch, determine whether they should be subdivided into stacked sub-branches. Each sub-branch must be a contiguous range of commits.`,r=`Consider merge-order preference: even if sub-groups compile independently, keep them together if merging one without the other would be incomplete or confusing.`;return t===`conservative`?`${n}
53
53
 
54
54
  Keep as a single branch unless sub-groups address completely different concerns with no merge-order preference. ${r}
55
55
  When in doubt, do not partition.`:t===`aggressive`?`${n}
@@ -58,4 +58,4 @@ Subdivide whenever there is a clear concern boundary. Each stacked PR should tel
58
58
 
59
59
  Only subdivide when the sub-groups are independently reviewable AND independently mergeable, and a reviewer would genuinely benefit from separate PRs. Do NOT partition just because commits are distinct — a refactor followed by a feature using it is a coherent single PR.
60
60
 
61
- ${r}`}export{o as buildPartitionContinuationPrompt,r as buildPartitionSystemPrompt,a as buildPartitionUserPrompt};
61
+ ${r}`}export{a as buildPartitionContinuationPrompt,n as buildPartitionSystemPrompt,i as buildPartitionUserPrompt};
@@ -1,14 +1,48 @@
1
- import { z } from "zod";
2
-
3
1
  //#region src/tasks/partition/schema.d.ts
4
- declare const partitionResultSchema: z.ZodObject<{
5
- partitions: z.ZodArray<z.ZodObject<{
6
- branchName: z.ZodString;
7
- title: z.ZodString;
8
- description: z.ZodString;
9
- commitIds: z.ZodArray<z.ZodString>;
10
- }, z.core.$strip>>;
11
- rationale: z.ZodString;
12
- }, z.core.$strip>;
2
+ declare const partitionResultJsonSchema: {
3
+ readonly $schema: "https://json-schema.org/draft/2020-12/schema";
4
+ readonly type: "object";
5
+ readonly properties: {
6
+ readonly partitions: {
7
+ readonly minItems: 1;
8
+ readonly type: "array";
9
+ readonly items: {
10
+ readonly type: "object";
11
+ readonly properties: {
12
+ readonly branchName: {
13
+ readonly type: "string";
14
+ readonly minLength: 1;
15
+ readonly description: "Valid git branch name";
16
+ };
17
+ readonly title: {
18
+ readonly type: "string";
19
+ readonly description: "Short PR-style title for this sub-branch (under 72 chars, imperative mood, e.g. 'Refactor authentication middleware')";
20
+ };
21
+ readonly description: {
22
+ readonly type: "string";
23
+ readonly description: "What this sub-branch accomplishes";
24
+ };
25
+ readonly commitIds: {
26
+ readonly minItems: 1;
27
+ readonly type: "array";
28
+ readonly items: {
29
+ readonly type: "string";
30
+ };
31
+ readonly description: "Commit IDs in this partition (contiguous range)";
32
+ };
33
+ };
34
+ readonly required: readonly ["branchName", "title", "description", "commitIds"];
35
+ readonly additionalProperties: false;
36
+ };
37
+ readonly description: "Contiguous partitions of commits into stacked branches";
38
+ };
39
+ readonly rationale: {
40
+ readonly type: "string";
41
+ readonly description: "Explanation of why this partition was chosen";
42
+ };
43
+ };
44
+ readonly required: readonly ["partitions", "rationale"];
45
+ readonly additionalProperties: false;
46
+ };
13
47
  //#endregion
14
- export { partitionResultSchema };
48
+ export { partitionResultJsonSchema };
@@ -1 +1 @@
1
- import{z as e}from"zod";const t=e.object({branchName:e.string().min(1).describe(`Valid git branch name`),title:e.string().describe(`Short PR-style title for this sub-branch (under 72 chars, imperative mood, e.g. 'Refactor authentication middleware')`),description:e.string().describe(`What this sub-branch accomplishes`),commitIds:e.array(e.string()).min(1).describe(`Commit IDs in this partition (contiguous range)`)}),n=e.object({partitions:e.array(t).min(1).describe(`Contiguous partitions of commits into stacked branches`),rationale:e.string().describe(`Explanation of why this partition was chosen`)});export{n as partitionResultSchema};
1
+ const e={$schema:`https://json-schema.org/draft/2020-12/schema`,type:`object`,properties:{partitions:{minItems:1,type:`array`,items:{type:`object`,properties:{branchName:{type:`string`,minLength:1,description:`Valid git branch name`},title:{type:`string`,description:`Short PR-style title for this sub-branch (under 72 chars, imperative mood, e.g. 'Refactor authentication middleware')`},description:{type:`string`,description:`What this sub-branch accomplishes`},commitIds:{minItems:1,type:`array`,items:{type:`string`},description:`Commit IDs in this partition (contiguous range)`}},required:[`branchName`,`title`,`description`,`commitIds`],additionalProperties:!1},description:`Contiguous partitions of commits into stacked branches`},rationale:{type:`string`,description:`Explanation of why this partition was chosen`}},required:[`partitions`,`rationale`],additionalProperties:!1};export{e as partitionResultJsonSchema};
@@ -1,4 +1,4 @@
1
- import{extractOutputTag as e}from"../ai-loop.js";import{partitionResultSchema as t}from"./schema.js";function n(n,s,c){let l=null;try{if(l=e(n),!l)return a([`No <output> tag found in response`],o());let u=JSON.parse(l),d=t.safeParse(u);if(!d.success)return a(d.error.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`),o());let f=d.data,p=[],m=[],h=new Set;for(let e=0;e<f.partitions.length;e++){let t=f.partitions[e];if(!i(t.branchName))return a([`Partition ${e}: '${t.branchName}' is not a valid git branch name`],`The branch name '${t.branchName}' is invalid. Git branch names cannot contain spaces, '..', '~', '^', ':', '\\', or end with '.lock'. Please provide a valid name.`);if(h.has(t.branchName))return a([`Duplicate partition branch name: '${t.branchName}'`],`Multiple partitions use the branch name '${t.branchName}'. Each partition must have a unique branch name. Please rename one of them.`);if(h.add(t.branchName),c?.has(t.branchName))return a([`Branch name '${t.branchName}' already exists in the repository`],`The branch name '${t.branchName}' already exists in the repository. Please choose a different name for partition ${e}.`);for(let e of t.commitIds)m.push(e);p.push({branchName:t.branchName,title:t.title,description:t.description,commitIds:t.commitIds})}let g=new Set,_=[];for(let e of m)g.has(e)&&_.push(e),g.add(e);if(_.length>0)return a([`Duplicate commit IDs: ${_.join(`, `)}`],`Some commits appear in multiple partitions. Each must be in exactly one.\n\nDuplicates: ${_.join(`, `)}`);let v=new Set(s),y=s.filter(e=>!g.has(e)),b=m.filter(e=>!v.has(e));return y.length>0?a([`Missing commits: ${y.join(`, `)}`],`Some commits are not assigned to any partition.\n\nMissing: ${y.join(`, `)}`):b.length>0?a([`Unknown commits: ${b.join(`, `)}`],`Some commit IDs don't match any known commit.\n\nUnknown: ${b.join(`, `)}`):r(p,s)?{valid:!0,result:{partitions:p,rationale:f.rationale}}:a([`Partitions are not contiguous`],`Each partition must contain a contiguous range of commits in the provided order. Please regroup into contiguous ranges.`)}catch(e){let t=e instanceof Error?e.message:String(e),r=(l??n).slice(-300);return a([`Failed to parse response as valid JSON: ${t}`,`Response tail: ${r}`],o())}}function r(e,t){let n=new Map(t.map((e,t)=>[e,t])),r=0;for(let t of e)for(let e of t.commitIds){let t=n.get(e);if(t===void 0||t!==r)return!1;r++}return r===t.length}function i(e){return e.startsWith(`-`)||e.startsWith(`.`)||e.endsWith(`.lock`)||e.endsWith(`.`)||e.endsWith(`/`)||e.includes(`..`)||e.includes(`~`)||e.includes(`^`)||e.includes(`:`)||e.includes(`\\`)||e.includes(` `)||e.includes(`?`)||e.includes(`*`)||e.includes(`[`)||e.includes(`@{`)?!1:e.length>0}function a(e,t){return{valid:!1,errors:e,retryFeedback:t}}function o(){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{partitionResultJsonSchema as n}from"./schema.js";function r(r,c,l){let u=null;try{if(u=e(r),!u)return o([`No <output> tag found in response`],s());let d=t(n,JSON.parse(u));if(!d.success)return o(d.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`),s());let f=d.data,p=[],m=[],h=new Set;for(let e=0;e<f.partitions.length;e++){let t=f.partitions[e];if(!a(t.branchName))return o([`Partition ${e}: '${t.branchName}' is not a valid git branch name`],`The branch name '${t.branchName}' is invalid. Git branch names cannot contain spaces, '..', '~', '^', ':', '\\', or end with '.lock'. Please provide a valid name.`);if(h.has(t.branchName))return o([`Duplicate partition branch name: '${t.branchName}'`],`Multiple partitions use the branch name '${t.branchName}'. Each partition must have a unique branch name. Please rename one of them.`);if(h.add(t.branchName),l?.has(t.branchName))return o([`Branch name '${t.branchName}' already exists in the repository`],`The branch name '${t.branchName}' already exists in the repository. Please choose a different name for partition ${e}.`);for(let e of t.commitIds)m.push(e);p.push({branchName:t.branchName,title:t.title,description:t.description,commitIds:t.commitIds})}let g=new Set,_=[];for(let e of m)g.has(e)&&_.push(e),g.add(e);if(_.length>0)return o([`Duplicate commit IDs: ${_.join(`, `)}`],`Some commits appear in multiple partitions. Each must be in exactly one.\n\nDuplicates: ${_.join(`, `)}`);let v=new Set(c),y=c.filter(e=>!g.has(e)),b=m.filter(e=>!v.has(e));return y.length>0?o([`Missing commits: ${y.join(`, `)}`],`Some commits are not assigned to any partition.\n\nMissing: ${y.join(`, `)}`):b.length>0?o([`Unknown commits: ${b.join(`, `)}`],`Some commit IDs don't match any known commit.\n\nUnknown: ${b.join(`, `)}`):i(p,c)?{valid:!0,result:{partitions:p,rationale:f.rationale}}:o([`Partitions are not contiguous`],`Each partition must contain a contiguous range of commits in the provided order. Please regroup into contiguous ranges.`)}catch(e){let t=e instanceof Error?e.message:String(e),n=(u??r).slice(-300);return o([`Failed to parse response as valid JSON: ${t}`,`Response tail: ${n}`],s())}}function i(e,t){let n=new Map(t.map((e,t)=>[e,t])),r=0;for(let t of e)for(let e of t.commitIds){let t=n.get(e);if(t===void 0||t!==r)return!1;r++}return r===t.length}function a(e){return e.startsWith(`-`)||e.startsWith(`.`)||e.endsWith(`.lock`)||e.endsWith(`.`)||e.endsWith(`/`)||e.includes(`..`)||e.includes(`~`)||e.includes(`^`)||e.includes(`:`)||e.includes(`\\`)||e.includes(` `)||e.includes(`?`)||e.includes(`*`)||e.includes(`[`)||e.includes(`@{`)?!1:e.length>0}function o(e,t){return{valid:!1,errors:e,retryFeedback:t}}function s(){return`Your response has an invalid structure. Return JSON inside <output> tags:
2
2
  <output>
3
3
  {
4
4
  "partitions": [
@@ -8,4 +8,4 @@ import{extractOutputTag as e}from"../ai-loop.js";import{partitionResultSchema as
8
8
  }
9
9
  </output>
10
10
 
11
- Return only the <output> tag and no other text.`}export{n as validatePartitionResponse};
11
+ Return only the <output> tag and no other text.`}export{r as validatePartitionResponse};