@gitkraken/compose-tools 0.8.0 → 0.9.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.
- package/CHANGELOG.md +28 -0
- package/README.md +8 -4
- package/dist/ai/types.d.ts +46 -1
- package/dist/context.d.ts +66 -0
- package/dist/context.js +1 -0
- package/dist/git/apply-target.js +1 -1
- package/dist/git/commit-chain.d.ts +14 -5
- package/dist/git/commit-chain.js +7 -7
- package/dist/git/diff-collector.d.ts +26 -12
- package/dist/git/diff-collector.js +4 -4
- package/dist/git/index.js +1 -1
- package/dist/git/safety.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/tasks/ai-loop.d.ts +10 -1
- package/dist/tasks/ai-loop.js +1 -1
- package/dist/tasks/analyze/analyze.js +1 -1
- package/dist/tasks/analyze/prompt.d.ts +6 -2
- package/dist/tasks/analyze/prompt.js +5 -5
- package/dist/tasks/analyze/types.d.ts +11 -0
- package/dist/tasks/compose-group/compose-group.js +1 -1
- package/dist/tasks/compose-group/prompt.d.ts +6 -1
- package/dist/tasks/compose-group/prompt.js +31 -31
- package/dist/tasks/compose-group/types.d.ts +19 -11
- package/dist/tasks/group/group.js +1 -1
- package/dist/tasks/group/prompt.d.ts +7 -1
- package/dist/tasks/group/prompt.js +21 -21
- package/dist/tasks/group/types.d.ts +22 -1
- package/dist/tasks/group/validator.d.ts +10 -2
- package/dist/tasks/group/validator.js +5 -5
- package/dist/tasks/quick-compose/prompt.d.ts +3 -1
- package/dist/tasks/quick-compose/prompt.js +11 -11
- package/dist/tasks/quick-compose/quick-compose.js +1 -1
- package/dist/tasks/quick-compose/types.d.ts +7 -0
- package/dist/tasks/refine/refine.js +1 -1
- package/dist/tasks/refine/types.d.ts +6 -1
- package/dist/tasks/refine/validator.js +1 -1
- package/dist/tasks/utils/analysis-prompt.d.ts +14 -0
- package/dist/tasks/utils/analysis-prompt.js +5 -0
- package/dist/tasks/utils/authoring-context.d.ts +47 -0
- package/dist/tasks/utils/authoring-context.js +9 -0
- package/dist/tasks/utils/hunk-summaries.d.ts +19 -0
- package/dist/tasks/utils/hunk-summaries.js +5 -0
- package/dist/workflows/author.d.ts +101 -0
- package/dist/workflows/author.js +16 -0
- package/dist/workflows/compose.d.ts +89 -1
- package/dist/workflows/compose.js +3 -3
- package/dist/workflows/refine.js +1 -1
- package/dist/workflows/split.js +2 -2
- package/dist/workflows/types.d.ts +31 -6
- package/package.json +1 -1
package/dist/tasks/ai-loop.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{AIError as e}from"../errors/ai.js";import{TaskInputError as t}from"../errors/task.js";import{appendAssistantMessage as n,appendUserMessage as r,createAiSession as i}from"../ai/session.js";import{addUsage as a}from"../ai/types.js";import{randomUUID as o}from"node:crypto";const s=2.8;async function c(a){let{model:s,userPrompt:c,maxRetries:d=3,maxTokens:f=16384,onProgress:p,validate:m,taskName:h,cancellation:g,phase:_=h,onBeforeModelCall:v,onAfterModelCall:y,promptVersion:b}=a;if(!a.session&&!a.systemPrompt)throw new t(`${h}: either 'session' or 'systemPrompt' must be provided`);let x=Date.now(),S;a.session?S=r(a.session,c):(S=i(a.systemPrompt),S=r(S,c));let C,w=[],T=d+1,E=[];for(let e=0;e<=d;e++){g?.throwIfAborted();let t=e+1;if(p?.({type:`attempt_start`,attempt:t,maxAttempts:T}),a.onBeforePrompt){let e=(S.systemPrompt?.length??0)+S.messages.reduce((e,t)=>e+t.content.length,0);if(!await a.onBeforePrompt({phase:_,attempt:t,maxAttempts:T,charCount:e,messageCount:S.messages.length,tokenEstimate:Math.ceil(e/2.8)}))throw new DOMException(`Prompt gate declined by caller (onBeforePrompt returned false)`,`AbortError`)}let i=o();E.push(i);let c=Date.now();v?.({phase:_,attempt:t,maxAttempts:T,callId:i});let h;try{h=await s.generate({system:S.systemPrompt,messages:[...S.messages],maxTokens:f,temperature:0,signal:g})}catch(e){let n=e instanceof Error?e.message:String(e);
|
|
1
|
+
import{AIError as e}from"../errors/ai.js";import{TaskInputError as t}from"../errors/task.js";import{appendAssistantMessage as n,appendUserMessage as r,createAiSession as i}from"../ai/session.js";import{addUsage as a}from"../ai/types.js";import{randomUUID as o}from"node:crypto";const s=2.8;async function c(a){let{model:s,userPrompt:c,maxRetries:d=3,maxTokens:f=16384,onProgress:p,validate:m,taskName:h,cancellation:g,phase:_=h,onBeforeModelCall:v,onAfterModelCall:y,promptVersion:b}=a;if(!a.session&&!a.systemPrompt)throw new t(`${h}: either 'session' or 'systemPrompt' must be provided`);let x=Date.now(),S;a.session?S=r(a.session,c):(S=i(a.systemPrompt),S=r(S,c));let C,w=[],T=d+1,E=[];for(let e=0;e<=d;e++){g?.throwIfAborted();let t=e+1;if(p?.({type:`attempt_start`,attempt:t,maxAttempts:T}),a.onBeforePrompt){let e=(S.systemPrompt?.length??0)+S.messages.reduce((e,t)=>e+t.content.length,0);if(!await a.onBeforePrompt({phase:_,attempt:t,maxAttempts:T,charCount:e,messageCount:S.messages.length,tokenEstimate:Math.ceil(e/2.8)}))throw new DOMException(`Prompt gate declined by caller (onBeforePrompt returned false)`,`AbortError`)}let i=o();E.push(i);let c=Date.now();v?.({phase:_,attempt:t,maxAttempts:T,callId:i});let h;try{h=await s.generate({system:S.systemPrompt,messages:[...S.messages],maxTokens:f,temperature:0,signal:g})}catch(e){let n=e instanceof Error?e.message:String(e);try{y?.({phase:_,attempt:t,callId:i,success:!1,durationMs:Date.now()-c,error:{code:e?.code??`UNKNOWN`,message:n},promptVersion:b})}catch{}throw l(e)||g?.aborted?new DOMException(n,`AbortError`):e}let D=Date.now()-c,O=e=>{try{y?.({phase:_,attempt:t,callId:i,success:!0,durationMs:D,usage:h.usage,promptVersion:b,repairs:e})}catch{}};C=u(C,h.usage);let k;try{k=m(h.text)}catch(e){throw O(),e}if(O(k.valid?k.repairs:void 0),k.valid&&k.result!==void 0)return S=n(S,h.text),{result:k.result,session:S,usage:C,attempts:t,durationMs:Date.now()-x,callIds:E,repairs:k.repairs};w=k.errors??[`Unknown validation error`],p?.({type:`validation_failed`,attempt:t,maxAttempts:T,errors:w}),e<d&&k.retryFeedback&&(S=n(S,h.text),S=r(S,k.retryFeedback))}throw new e(`VALIDATION_EXHAUSTED`,`Failed to get valid ${h} after ${T} attempts. Last errors:\n${w.join(`
|
|
2
2
|
`)}`)}function l(e){return e instanceof Error&&(e.name===`AbortError`||e.name===`DOMException`)}function u(e,t){if(!e)return t;if(!t)return e;let n={...e};return a(n,t),n}function d(e){let t=e.match(/<output>([\s\S]*?)(?:<\/output>|$)/)?.[1]?.trim()??null;return t?t.match(/^```(?:\w*)\n?([\s\S]*?)```$/)?.[1]?.trim()??t:null}export{s as estimatedCharactersPerToken,d as extractOutputTag,c as runAiLoop};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{buildAnalyzeSystemPrompt as r,buildAnalyzeUserPrompt as i}from"./prompt.js";import{validateAnalyzeResponse as a}from"./validator.js";async function o(o){let s=o.hunks;if(!s||s.length===0)throw new e(`No hunks provided: supply 'hunks'`);if(o.session?.completedSteps.analyze)return{result:o.session.completedSteps.analyze,session:o.session,attempts:0,durationMs:0,callIds:[]};let c=o.session?void 0:o.promptOverride?.system??r(),l,u=n(i({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions,redactHunkContent:o.redactHunkContent,inputTokenBudget:o.inputTokenBudget,systemPromptChars:c?.length,onBudgetReduction:e=>{l=e}}),o.promptOverride),d=new Set(s.map(e=>e.index)),f=await t({model:o.model,systemPrompt:c,userPrompt:u,session:o.session,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>a(e,d),taskName:`analyze`}),p=o.session?.completedSteps??{},m={...f.session,completedSteps:{...p,analyze:f.result}};return{result:f.result,session:m,usage:f.usage,attempts:f.attempts,durationMs:f.durationMs,callIds:f.callIds,hunksCondensedCount:l}}export{o as analyze};
|
|
1
|
+
import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{buildAnalyzeSystemPrompt as r,buildAnalyzeUserPrompt as i}from"./prompt.js";import{validateAnalyzeResponse as a}from"./validator.js";async function o(o){let s=o.hunks;if(!s||s.length===0)throw new e(`No hunks provided: supply 'hunks'`);if(o.session?.completedSteps.analyze)return{result:o.session.completedSteps.analyze,session:o.session,attempts:0,durationMs:0,callIds:[]};let c=o.session?void 0:o.promptOverride?.system??r(),l,u=n(i({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,authoring:o.authoring,hunkSummaries:o.hunkSummaries,condensed:o.condensed,instructions:o.instructions,redactHunkContent:o.redactHunkContent,inputTokenBudget:o.inputTokenBudget,systemPromptChars:c?.length,onBudgetReduction:e=>{l=e}}),o.promptOverride),d=new Set(s.map(e=>e.index)),f=await t({model:o.model,systemPrompt:c,userPrompt:u,session:o.session,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>a(e,d),taskName:`analyze`}),p=o.session?.completedSteps??{},m={...f.session,completedSteps:{...p,analyze:f.result}};return{result:f.result,session:m,usage:f.usage,attempts:f.attempts,durationMs:f.durationMs,callIds:f.callIds,hunksCondensedCount:l}}export{o as analyze};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AuthoringContext } from "../../context.js";
|
|
1
2
|
import { ComposeHunk } from "../types.js";
|
|
2
3
|
import { RedactHunkContent } from "../utils/redact.js";
|
|
3
4
|
|
|
@@ -10,8 +11,11 @@ declare function buildAnalyzeUserPrompt(params: {
|
|
|
10
11
|
branchName?: string;
|
|
11
12
|
baseBranch?: string;
|
|
12
13
|
issueUrl?: string;
|
|
13
|
-
pullRequestUrl?: string;
|
|
14
|
-
|
|
14
|
+
pullRequestUrl?: string; /** What the author knows that the hunks cannot show. Rendered as escaped, tagged data. */
|
|
15
|
+
authoring?: AuthoringContext; /** One-line summaries of what individual hunks do, keyed by index. See `ComposeInput.hunkSummaries`. */
|
|
16
|
+
hunkSummaries?: Record<number, string>;
|
|
17
|
+
instructions?: string; /** Send hunk metadata only, omitting every patch body. See `ComposeInput.condenseHunks`. */
|
|
18
|
+
condensed?: boolean;
|
|
15
19
|
redactHunkContent?: RedactHunkContent; /** Input-token ceiling that triggers progressive hunk reduction. Defaults to the shared budget. */
|
|
16
20
|
inputTokenBudget?: number; /** Char count of the system prompt, folded into the budget estimate. Defaults to 0. */
|
|
17
21
|
systemPromptChars?: number; /** Reports how many hunks were condensed for budget (full to condensed), for consumer telemetry. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{authoringContextBlock as e,resolveAuthoringUrls as t}from"../utils/authoring-context.js";import{assembleHunkPrompt as n}from"../utils/budget.js";import{renderHunkSummaries as r}from"../utils/hunk-summaries.js";import{analysisResultJsonSchema as i}from"./schema.js";const a=JSON.stringify(i,null,2);function o(){return`You are an advanced AI code analysis assistant. Your task is to deeply analyze a set of code changes (provided as diff hunks) and produce a structured understanding of what the changes accomplish, what concerns they address, and how hunks relate to each other.
|
|
2
2
|
|
|
3
3
|
Your analysis must produce:
|
|
4
4
|
1. **Title** — a short, PR-style title for the changes (under 72 characters, imperative mood, e.g. "Add user authentication and session management"). This should capture the essence of what the changes accomplish.
|
|
@@ -26,10 +26,10 @@ Rules:
|
|
|
26
26
|
Output your analysis as JSON inside a <output> tag matching this schema:
|
|
27
27
|
|
|
28
28
|
\`\`\`json
|
|
29
|
-
${
|
|
29
|
+
${a}
|
|
30
30
|
\`\`\`
|
|
31
31
|
|
|
32
|
-
Return only the <output> tag and no other text.`}function i
|
|
33
|
-
`)}\n</context>`),
|
|
32
|
+
Return only the <output> tag and no other text.`}function s(i){let{hunks:a,commitMessages:o,commitOrder:s,branchName:c,baseBranch:l,issueUrl:u,pullRequestUrl:d,authoring:f,hunkSummaries:p,instructions:m,condensed:h,redactHunkContent:g,inputTokenBudget:_,systemPromptChars:v,onBudgetReduction:y}=i,b=t(f,{issueUrl:u,pullRequestUrl:d}),x=e(f),S=r(p,a),{prompt:C,hunksCondensedForBudget:w}=n({hunks:a,assemble:e=>{let t=`Analyze the following code changes:\n\n<hunks>\n${e}\n</hunks>`,n=[];if(c&&n.push(`Branch: ${c}`),l&&n.push(`Base branch: ${l}`),b.issueUrl&&n.push(`Issue: ${b.issueUrl}`),b.pullRequestUrl&&n.push(`Pull request: ${b.pullRequestUrl}`),n.length>0&&(t+=`\n\n<context>\n${n.join(`
|
|
33
|
+
`)}\n</context>`),S&&(t+=`\n\n${S}`),x&&(t+=`\n\n${x}`),o?.length){let e=o.map((e,t)=>({index:t,sha:s?.[t],message:e}));t+=`\n\n<commit_messages>\n${JSON.stringify(e)}\n</commit_messages>`}return m&&(t+=`\n\nCarefully follow these additional instructions, but do not deviate from the output structure:\n${m}`),t+=`
|
|
34
34
|
|
|
35
|
-
Now analyze the changes and return your structured analysis. Return only the <output> tag and no other text.`,t},redactHunkContent:
|
|
35
|
+
Now analyze the changes and return your structured analysis. Return only the <output> tag and no other text.`,t},condensed:h,redactHunkContent:g,inputTokenBudget:_,systemPromptChars:v});return y?.(w),C}export{o as buildAnalyzeSystemPrompt,s as buildAnalyzeUserPrompt};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ProgressEvent, TokenUsage } from "../../ai/types.js";
|
|
2
|
+
import { AuthoringContext } from "../../context.js";
|
|
2
3
|
import { ComposeHunk, PromptOverride } from "../types.js";
|
|
3
4
|
import { OnAfterModelCall, OnBeforeModelCall, OnBeforePrompt } from "../ai-loop.js";
|
|
4
5
|
import { RedactHunkContent } from "../utils/redact.js";
|
|
@@ -14,7 +15,17 @@ interface AnalyzeInput {
|
|
|
14
15
|
baseBranch?: string;
|
|
15
16
|
issueUrl?: string;
|
|
16
17
|
pullRequestUrl?: string;
|
|
18
|
+
/**
|
|
19
|
+
* What the author knows that the hunks cannot show — intent, issue text, per-file rationale,
|
|
20
|
+
* ordering constraints. Rendered into the prompt as escaped, tagged data, never as instructions;
|
|
21
|
+
* for guidance the model should act on, use {@link instructions}.
|
|
22
|
+
*/
|
|
23
|
+
authoring?: AuthoringContext;
|
|
24
|
+
/** One-line summaries of what individual hunks do, keyed by index. See `ComposeInput.hunkSummaries`. */
|
|
25
|
+
hunkSummaries?: Record<number, string>;
|
|
17
26
|
instructions?: string;
|
|
27
|
+
/** Send hunk metadata only, omitting every patch body. See `ComposeInput.condenseHunks`. */
|
|
28
|
+
condensed?: boolean;
|
|
18
29
|
/** See {@link RedactHunkContent}. */
|
|
19
30
|
redactHunkContent?: RedactHunkContent;
|
|
20
31
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{validateGroupResponse as r}from"../group/validator.js";import{buildComposeGroupSystemPrompt as i,buildComposeGroupUserPrompt as a}from"./prompt.js";async function o(o){let s=o.hunks,c=o.mode??`branches`;if(!s||s.length===0)throw new e(`No hunks provided for compose-group`);let l=new Set(s.map(e=>e.index));if(o.session?.completedSteps.composeGroup)return{result:o.session.completedSteps.composeGroup,session:o.session,attempts:0,durationMs:0,callIds:[]};let u=o.promptOverride?.system??i(o.conventions,c,o.strategy,o.includeAnalysis,o.lightweight),d,f=n(a({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions,condensed:o.condensed,redactHunkContent:o.redactHunkContent,inputTokenBudget:o.inputTokenBudget,systemPromptChars:u.length,onBudgetReduction:e=>{d=e}}),o.promptOverride),p=await t({model:o.model,systemPrompt:u,userPrompt:f,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>r(e,l,c,o.existingBranchNames),taskName:`compose-group`}),m=o.session?.completedSteps??{},h={...p.session,completedSteps:{...m,composeGroup:p.result}};return{result:p.result,session:h,usage:p.usage,attempts:p.attempts,durationMs:p.durationMs,callIds:p.callIds,hunksCondensedCount:d}}export{o as composeGroup};
|
|
1
|
+
import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{validateGroupResponse as r}from"../group/validator.js";import{buildComposeGroupSystemPrompt as i,buildComposeGroupUserPrompt as a}from"./prompt.js";async function o(o){let s=o.hunks,c=o.mode??`branches`;if(!s||s.length===0)throw new e(`No hunks provided for compose-group`);let l=new Set(s.map(e=>e.index));if(o.session?.completedSteps.composeGroup)return{result:o.session.completedSteps.composeGroup,session:o.session,attempts:0,durationMs:0,callIds:[]};let u=o.promptOverride?.system??i(o.conventions,c,o.strategy,o.includeAnalysis,o.lightweight),d,f=n(a({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,authoring:o.authoring,analysis:o.analysis,hunkSummaries:o.hunkSummaries,instructions:o.instructions,condensed:o.condensed,redactHunkContent:o.redactHunkContent,inputTokenBudget:o.inputTokenBudget,systemPromptChars:u.length,onBudgetReduction:e=>{d=e}}),o.promptOverride),p=await t({model:o.model,systemPrompt:u,userPrompt:f,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>r(e,l,c,o.existingBranchNames),taskName:`compose-group`}),m=o.session?.completedSteps??{},h={...p.session,completedSteps:{...m,composeGroup:p.result}};return{result:p.result,session:h,usage:p.usage,attempts:p.attempts,durationMs:p.durationMs,callIds:p.callIds,hunksCondensedCount:d,repairs:p.repairs}}export{o as composeGroup};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { AuthoringContext } from "../../context.js";
|
|
1
2
|
import { ComposeHunk } from "../types.js";
|
|
2
3
|
import { ConventionDescriptor } from "../../git/utils/conventions.js";
|
|
3
4
|
import { RedactHunkContent } from "../utils/redact.js";
|
|
5
|
+
import { AnalysisResult } from "../analyze/types.js";
|
|
4
6
|
import { GroupingStrategy } from "../group/types.js";
|
|
5
7
|
|
|
6
8
|
//#region src/tasks/compose-group/prompt.d.ts
|
|
@@ -19,7 +21,10 @@ declare function buildComposeGroupUserPrompt(params: {
|
|
|
19
21
|
branchName?: string;
|
|
20
22
|
baseBranch?: string;
|
|
21
23
|
issueUrl?: string;
|
|
22
|
-
pullRequestUrl?: string;
|
|
24
|
+
pullRequestUrl?: string; /** What the author knows that the hunks cannot show. Rendered as escaped, tagged data. */
|
|
25
|
+
authoring?: AuthoringContext; /** Caller-supplied analysis, used in place of asking the model to derive one. */
|
|
26
|
+
analysis?: AnalysisResult; /** One-line summaries of what individual hunks do, keyed by index. See `ComposeInput.hunkSummaries`. */
|
|
27
|
+
hunkSummaries?: Record<number, string>;
|
|
23
28
|
instructions?: string;
|
|
24
29
|
condensed?: boolean;
|
|
25
30
|
redactHunkContent?: RedactHunkContent; /** Input-token ceiling that triggers progressive hunk reduction. Defaults to the shared budget. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{renderAuthoringContext as e}from"../utils/authoring-context.js";import{assembleHunkPrompt as t}from"../utils/budget.js";import{renderHunkSummaries as n}from"../utils/hunk-summaries.js";import{groupingResultJsonSchema as r}from"../group/schema.js";import{renderSuppliedAnalysis as i}from"../utils/analysis-prompt.js";const a=JSON.stringify(r,null,2),o=`
|
|
2
2
|
In addition to the \`branches\` field, include an \`analysis\` field at the top level:
|
|
3
3
|
|
|
4
4
|
\`\`\`json
|
|
@@ -10,12 +10,12 @@ In addition to the \`branches\` field, include an \`analysis\` field at the top
|
|
|
10
10
|
"branches": [ ... ]
|
|
11
11
|
}
|
|
12
12
|
\`\`\`
|
|
13
|
-
`,
|
|
13
|
+
`,s=`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.`,
|
|
18
|
-
6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`,
|
|
17
|
+
- Informal yet professional tone.`,c=`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 l(e,t=`branches`,n,r,i){return i?t===`commits`?m(e,n):h(e,n):t===`commits`?u(e,n,r):d(e,n,r)}function u(e,t,n){let r=`${e?.commits.bodyStyle===`minimal`?c:s}
|
|
18
|
+
6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`,i=p(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits.
|
|
19
19
|
|
|
20
20
|
## Critical constraints (output is rejected if violated)
|
|
21
21
|
|
|
@@ -26,34 +26,34 @@ In addition to the \`branches\` field, include an \`analysis\` field at the top
|
|
|
26
26
|
|
|
27
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.
|
|
28
28
|
|
|
29
|
-
${
|
|
29
|
+
${n?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
|
|
30
30
|
|
|
31
31
|
Before grouping, analyze the changes to understand:
|
|
32
32
|
- **Concerns** — what distinct themes, features, or purposes exist across the hunks
|
|
33
33
|
- **Dependencies** — which hunks provide things (define functions, types, files) that other hunks consume (import, call, reference)
|
|
34
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
|
|
35
35
|
|
|
36
|
-
${
|
|
36
|
+
${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.`}
|
|
37
37
|
|
|
38
38
|
## Step 2: Group hunks into commits
|
|
39
39
|
|
|
40
40
|
1. Group hunks into logical units that make sense together and can be applied atomically
|
|
41
41
|
2. Ensure each commit is self-contained and atomic
|
|
42
|
-
${
|
|
43
|
-
${
|
|
42
|
+
${r}
|
|
43
|
+
${i}
|
|
44
44
|
|
|
45
45
|
## Required Output Format
|
|
46
46
|
|
|
47
47
|
Output your organization as JSON inside a <output> tag matching this schema:
|
|
48
48
|
|
|
49
49
|
\`\`\`json
|
|
50
|
-
${
|
|
50
|
+
${a}
|
|
51
51
|
\`\`\`
|
|
52
|
-
${o
|
|
52
|
+
${n?o:``}
|
|
53
53
|
Wrap all commits in a single branch object with id "default", name "", title "", and description "".
|
|
54
54
|
|
|
55
|
-
Return only the <output> tag and no other text.`}function
|
|
56
|
-
6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`,
|
|
55
|
+
Return only the <output> tag and no other text.`}function d(e,t,n){let r=`${e?.commits.bodyStyle===`minimal`?c:s}
|
|
56
|
+
6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`,i=f(t?.branchSplitting,e),l=p(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits and branches.
|
|
57
57
|
|
|
58
58
|
## Critical constraints (output is rejected if violated)
|
|
59
59
|
|
|
@@ -64,7 +64,7 @@ Return only the <output> tag and no other text.`}function c(e,t,o){let s=`${e?.c
|
|
|
64
64
|
|
|
65
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.
|
|
66
66
|
|
|
67
|
-
${
|
|
67
|
+
${n?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
|
|
68
68
|
|
|
69
69
|
Before grouping, analyze the changes to understand:
|
|
70
70
|
- **Concerns** — what distinct themes, features, or purposes exist across the hunks
|
|
@@ -72,7 +72,7 @@ Before grouping, analyze the changes to understand:
|
|
|
72
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
|
|
73
73
|
- **Merge-order preference** — even if two concerns compile independently, would merging one without the other be confusing or incomplete?
|
|
74
74
|
|
|
75
|
-
${
|
|
75
|
+
${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.`}
|
|
76
76
|
|
|
77
77
|
## Step 2: Organize hunks into commits and branches
|
|
78
78
|
|
|
@@ -85,26 +85,26 @@ Your job has two levels:
|
|
|
85
85
|
|
|
86
86
|
1. Group hunks into logical units that make sense together and can be applied atomically
|
|
87
87
|
2. Ensure each commit is self-contained and atomic
|
|
88
|
-
${
|
|
89
|
-
${
|
|
88
|
+
${r}
|
|
89
|
+
${l}
|
|
90
90
|
|
|
91
91
|
### Branch guidelines
|
|
92
92
|
|
|
93
|
-
${
|
|
93
|
+
${i}
|
|
94
94
|
|
|
95
95
|
## Required Output Format
|
|
96
96
|
|
|
97
97
|
Output your organization as JSON inside a <output> tag matching this schema:
|
|
98
98
|
|
|
99
99
|
\`\`\`json
|
|
100
|
-
${
|
|
100
|
+
${a}
|
|
101
101
|
\`\`\`
|
|
102
|
-
${o
|
|
103
|
-
Return only the <output> tag and no other text.`}function
|
|
102
|
+
${n?o:``}
|
|
103
|
+
Return only the <output> tag and no other text.`}function f(e,t){let n=`Each branch must have a descriptive name, a short PR-style title (under 72 characters, imperative mood), and at least one commit.`,r=`- Each commit belongs to exactly one branch.
|
|
104
104
|
${t?.promptFragment?`- ${n} Follow the repository's branch naming conventions (see above).`:`- ${n}`}
|
|
105
|
-
- Changes addressing the same pattern across different files belong in ONE branch.`,i=`- Consider merge-order preference, not just code dependencies. Even if two concerns compile independently, they may need to be in the same branch if merging one without the other would be confusing or incomplete.`;return e===`conservative`?`${r}\n- Default to a single branch. Only create multiple branches when concerns are completely unrelated.\n${i}\n- When in doubt, use fewer branches.`:e===`aggressive`?`${r}\n- Create separate branches for each distinct concern.\n${i}\n- Prefer more branches over fewer.`:`${r}\n- Create separate branches when concerns are genuinely independent — meaning a reviewer would gain nothing from seeing them together.\n- Keep changes in the same branch when they form a coherent story.\n${i}\n- When in doubt: "Would a reviewer want to see these together?"`}function
|
|
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 p(e){return e===`consolidated`?`
|
|
106
106
|
Prefer fewer, larger commits. Combine related changes into a single commit when they serve the same purpose.`:e===`granular`?`
|
|
107
|
-
Prefer more, smaller commits. Each distinct change should be its own commit.`:``}function
|
|
107
|
+
Prefer more, smaller commits. Each distinct change should be its own commit.`:``}function m(e,t){return`Organize code hunks into clean, atomic commits.
|
|
108
108
|
|
|
109
109
|
## Rules
|
|
110
110
|
|
|
@@ -115,7 +115,7 @@ Prefer more, smaller commits. Each distinct change should be its own commit.`:``
|
|
|
115
115
|
5. ${e?.promptFragment?`Write commit messages following the repository's conventions (see below). Write a one-sentence explanation per commit.
|
|
116
116
|
|
|
117
117
|
${e.promptFragment}`:`Write commit messages: first line under 50 chars, imperative mood ("Add", "Fix", "Update"). Write a one-sentence explanation per commit.`}
|
|
118
|
-
${
|
|
118
|
+
${p(t?.commitGranularity)}
|
|
119
119
|
|
|
120
120
|
## Output
|
|
121
121
|
|
|
@@ -125,9 +125,9 @@ JSON inside <output> tags. Use one branch with id "default", name "", title "",
|
|
|
125
125
|
{"branches":[{"id":"default","name":"","title":"","description":"","commits":[{"id":"commit-1","message":"Add feature X","explanation":"Brief reason.","hunks":[0,1,2]}]}]}
|
|
126
126
|
\`\`\`
|
|
127
127
|
|
|
128
|
-
CRITICAL: Every hunk index must appear exactly once across all commits. Every commit must contain at least one hunk. Missing hunks, duplicate hunks, or empty commits will fail validation. Return only the <output> tag.`}function
|
|
128
|
+
CRITICAL: Every hunk index must appear exactly once across all commits. Every commit must contain at least one hunk. Missing hunks, duplicate hunks, or empty commits will fail validation. Return only the <output> tag.`}function h(e,t){let n=e?.promptFragment?`Write commit messages following the repository's conventions (see below). Write a one-sentence explanation per commit.
|
|
129
129
|
|
|
130
|
-
${e.promptFragment}`:`Write commit messages: first line under 50 chars, third-person singular present tense ("Fixes", "Adds", "Improves"). Write a one-sentence explanation per commit.`,r=
|
|
130
|
+
${e.promptFragment}`:`Write commit messages: first line under 50 chars, third-person singular present tense ("Fixes", "Adds", "Improves"). Write a one-sentence explanation per commit.`,r=f(t?.branchSplitting,e);return`Organize code hunks into clean, atomic commits and branches.
|
|
131
131
|
|
|
132
132
|
## Commit rules
|
|
133
133
|
|
|
@@ -136,7 +136,7 @@ ${e.promptFragment}`:`Write commit messages: first line under 50 chars, third-pe
|
|
|
136
136
|
3. Each commit needs a unique ID (e.g., "commit-1", "commit-2").
|
|
137
137
|
4. Every commit must have at least one hunk. No empty commits.
|
|
138
138
|
5. ${n}
|
|
139
|
-
${
|
|
139
|
+
${p(t?.commitGranularity)}
|
|
140
140
|
|
|
141
141
|
## Branch guidelines
|
|
142
142
|
|
|
@@ -152,11 +152,11 @@ JSON inside <output> tags. Each branch needs a unique ID and non-empty name.
|
|
|
152
152
|
|
|
153
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.
|
|
154
154
|
|
|
155
|
-
Return only the <output> tag and no other text.`}function
|
|
155
|
+
Return only the <output> tag and no other text.`}function g(r){let{hunks:a,commitMessages:o,commitOrder:s,branchName:c,baseBranch:l,issueUrl:u,pullRequestUrl:d,authoring:f,analysis:p,hunkSummaries:m,instructions:h,condensed:g,redactHunkContent:_,inputTokenBudget:v,systemPromptChars:y,onBudgetReduction:b}=r,x=i(p),S=n(m,a),C=a.map(e=>e.index).join(`, `),{prompt:w,hunksCondensedForBudget:T}=t({hunks:a,assemble:t=>{let n=`Organize these ${a.length} code hunks (indices: ${C}) into commits. Every listed index must appear exactly once.
|
|
156
156
|
|
|
157
157
|
<hunks>
|
|
158
|
-
${
|
|
159
|
-
</hunks>`,u=[];return
|
|
160
|
-
`)}`),r?.length&&(
|
|
158
|
+
${t}
|
|
159
|
+
</hunks>`,r=e(f,{issueUrl:u,pullRequestUrl:d}),i=[];return c&&i.push(`Branch: ${c}`),l&&i.push(`Base: ${l}`),i.push(...r.lines),i.length>0&&(n+=`\n\nContext:\n${i.join(`
|
|
160
|
+
`)}`),S&&(n+=`\n\n${S}`),x&&(n+=`\n\n${x}`),r.block&&(n+=`\n\n${r.block}`),o?.length&&(n+=`\n\nOriginal commit messages (match their style):\n<commit_messages>\n${JSON.stringify(o)}\n</commit_messages>`),s?.length&&(n+=`\n\nOriginal commit order:\n${JSON.stringify(s)}`),h&&(n+=`\n\nAdditional instructions:\n${h}`),n+=`
|
|
161
161
|
|
|
162
|
-
Return only the <output> tag and no other text.`,
|
|
162
|
+
Return only the <output> tag and no other text.`,n},condensed:g,redactHunkContent:_,inputTokenBudget:v,systemPromptChars:y});return b?.(T),w}export{l as buildComposeGroupSystemPrompt,g as buildComposeGroupUserPrompt};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { ProgressEvent
|
|
1
|
+
import { ProgressEvent } from "../../ai/types.js";
|
|
2
|
+
import { AuthoringContext } from "../../context.js";
|
|
2
3
|
import { ComposeHunk, PromptOverride } from "../types.js";
|
|
3
4
|
import { ConventionDescriptor } from "../../git/utils/conventions.js";
|
|
4
5
|
import { OnAfterModelCall, OnBeforeModelCall, OnBeforePrompt } from "../ai-loop.js";
|
|
5
6
|
import { RedactHunkContent } from "../utils/redact.js";
|
|
6
|
-
import {
|
|
7
|
+
import { AnalysisResult } from "../analyze/types.js";
|
|
8
|
+
import { GroupOutput, GroupingStrategy } from "../group/types.js";
|
|
7
9
|
import { ComposeSession } from "../session.js";
|
|
8
10
|
import { AiModelPort } from "@gitkraken/shared-tools";
|
|
9
11
|
|
|
@@ -24,6 +26,20 @@ interface ComposeGroupInput {
|
|
|
24
26
|
baseBranch?: string;
|
|
25
27
|
issueUrl?: string;
|
|
26
28
|
pullRequestUrl?: string;
|
|
29
|
+
/**
|
|
30
|
+
* What the author knows that the hunks cannot show — intent, issue text, per-file rationale,
|
|
31
|
+
* ordering constraints. Rendered into the prompt as escaped, tagged data, never as instructions;
|
|
32
|
+
* for guidance the model should act on, use {@link instructions}.
|
|
33
|
+
*/
|
|
34
|
+
authoring?: AuthoringContext;
|
|
35
|
+
/**
|
|
36
|
+
* Analysis supplied by the caller. Rendered into the prompt as the authoritative breakdown of the
|
|
37
|
+
* change, so the model reuses it instead of deriving one. Makes {@link includeAnalysis}
|
|
38
|
+
* unnecessary — there is nothing left for the model to produce.
|
|
39
|
+
*/
|
|
40
|
+
analysis?: AnalysisResult;
|
|
41
|
+
/** One-line summaries of what individual hunks do, keyed by index. See `ComposeInput.hunkSummaries`. */
|
|
42
|
+
hunkSummaries?: Record<number, string>;
|
|
27
43
|
instructions?: string;
|
|
28
44
|
conventions?: ConventionDescriptor;
|
|
29
45
|
/** "commits" = flat commit list. "branches" = AI decides branches (default). */
|
|
@@ -59,14 +75,6 @@ interface ComposeGroupInput {
|
|
|
59
75
|
/** Optional prompt override. When set, replaces or decorates the built-in prompts. */
|
|
60
76
|
promptOverride?: PromptOverride;
|
|
61
77
|
}
|
|
62
|
-
|
|
63
|
-
type ComposeGroupOutput = {
|
|
64
|
-
result: GroupingResult;
|
|
65
|
-
session: ComposeSession;
|
|
66
|
-
usage?: TokenUsage;
|
|
67
|
-
attempts: number;
|
|
68
|
-
durationMs: number;
|
|
69
|
-
callIds: string[];
|
|
70
|
-
};
|
|
78
|
+
type ComposeGroupOutput = GroupOutput;
|
|
71
79
|
//#endregion
|
|
72
80
|
export { ComposeGroupInput, ComposeGroupOutput };
|
|
@@ -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=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?new Set(c.map(e=>e.index)):new Set(s.session?.completedSteps.analyze?.hunkAnnotations.map(e=>e.hunkIndex)??[]);if(p.size===0)throw new e(`Cannot validate grouping: no hunk indices 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};
|
|
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,authoring:s.authoring,analysis:s.analysis,hunkSummaries:s.hunkSummaries,condensed:s.condensed,mode:l,redactHunkContent:s.redactHunkContent,inputTokenBudget:s.inputTokenBudget,systemPromptChars:u?.length,onBudgetReduction:e=>{d=e}}),s.promptOverride),p=c?.length?new Set(c.map(e=>e.index)):new Set(s.session?.completedSteps.analyze?.hunkAnnotations.map(e=>e.hunkIndex)??[]);if(p.size===0)throw new e(`Cannot validate grouping: no hunk indices 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,repairs:m.repairs}}export{s as group};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { AuthoringContext } from "../../context.js";
|
|
1
2
|
import { ComposeHunk } from "../types.js";
|
|
2
3
|
import { ConventionDescriptor } from "../../git/utils/conventions.js";
|
|
3
4
|
import { RedactHunkContent } from "../utils/redact.js";
|
|
5
|
+
import { AnalysisResult } from "../analyze/types.js";
|
|
4
6
|
import { GroupInput, GroupingStrategy } from "./types.js";
|
|
5
7
|
|
|
6
8
|
//#region src/tasks/group/prompt.d.ts
|
|
@@ -8,7 +10,11 @@ declare function buildGroupSystemPrompt(conventions?: ConventionDescriptor, mode
|
|
|
8
10
|
declare function buildGroupUserPrompt(params: {
|
|
9
11
|
hunks: ComposeHunk[];
|
|
10
12
|
commitMessages?: string[];
|
|
11
|
-
instructions?: string;
|
|
13
|
+
instructions?: string; /** What the author knows that the hunks cannot show. Rendered as escaped, tagged data. */
|
|
14
|
+
authoring?: AuthoringContext; /** Caller-supplied analysis, used in place of one derived by a prior `analyze` call. */
|
|
15
|
+
analysis?: AnalysisResult; /** One-line summaries of what individual hunks do, keyed by index. See `ComposeInput.hunkSummaries`. */
|
|
16
|
+
hunkSummaries?: Record<number, string>; /** Send hunk metadata only, omitting every patch body. See `ComposeInput.condenseHunks`. */
|
|
17
|
+
condensed?: boolean;
|
|
12
18
|
mode?: 'commits' | 'branches';
|
|
13
19
|
redactHunkContent?: RedactHunkContent; /** Input-token ceiling that triggers progressive hunk reduction. Defaults to the shared budget. */
|
|
14
20
|
inputTokenBudget?: number; /** Char count of the system prompt, folded into the budget estimate. Defaults to 0. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{authoringContextBlock as e}from"../utils/authoring-context.js";import{assembleHunkPrompt as t}from"../utils/budget.js";import{renderHunkSummaries as n}from"../utils/hunk-summaries.js";import{groupingResultJsonSchema as r}from"./schema.js";import{renderSuppliedAnalysis as i}from"../utils/analysis-prompt.js";const a=JSON.stringify(r,null,2),o=`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.`,
|
|
5
|
+
- Informal yet professional tone.`,s=`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.`,c=`Each branch must have a descriptive name, a short PR-style title (under 72 characters, imperative mood), and at least one commit.`;function l(e,t=`branches`,n){return t===`commits`?u(e,n):d(e,n)}function u(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,22 +17,22 @@ 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?.commits.bodyStyle===`minimal`?
|
|
20
|
+
${`${e?.commits.bodyStyle===`minimal`?s:o}
|
|
21
21
|
6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`}
|
|
22
|
-
${
|
|
22
|
+
${p(t?.commitGranularity)}
|
|
23
23
|
|
|
24
24
|
## Required Output Format
|
|
25
25
|
|
|
26
26
|
Output your organization as JSON inside a <output> tag matching this schema:
|
|
27
27
|
|
|
28
28
|
\`\`\`json
|
|
29
|
-
${
|
|
29
|
+
${a}
|
|
30
30
|
\`\`\`
|
|
31
31
|
|
|
32
32
|
Wrap all commits in a single branch object with id "default", name "", title "", and description "".
|
|
33
33
|
|
|
34
|
-
Return only the <output> tag and no other text.`}function
|
|
35
|
-
6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`,
|
|
34
|
+
Return only the <output> tag and no other text.`}function d(e,t){let n=`${e?.commits.bodyStyle===`minimal`?s:o}
|
|
35
|
+
6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`,r=f(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.
|
|
36
36
|
|
|
37
37
|
Your job has two levels:
|
|
38
38
|
|
|
@@ -52,23 +52,23 @@ Before producing the \`<output>\` tag, do a self-check: count the total number o
|
|
|
52
52
|
|
|
53
53
|
1. Group hunks into logical units that make sense together and can be applied atomically
|
|
54
54
|
2. Ensure each commit is self-contained and atomic
|
|
55
|
-
${
|
|
56
|
-
${
|
|
55
|
+
${n}
|
|
56
|
+
${p(t?.commitGranularity)}
|
|
57
57
|
|
|
58
58
|
## Branch guidelines
|
|
59
59
|
|
|
60
|
-
${
|
|
60
|
+
${r}
|
|
61
61
|
|
|
62
62
|
## Required Output Format
|
|
63
63
|
|
|
64
64
|
Output your organization as JSON inside a <output> tag matching this schema:
|
|
65
65
|
|
|
66
66
|
\`\`\`json
|
|
67
|
-
${
|
|
67
|
+
${a}
|
|
68
68
|
\`\`\`
|
|
69
69
|
|
|
70
|
-
Return only the <output> tag and no other text.`}function
|
|
71
|
-
${t?.promptFragment?`- ${
|
|
70
|
+
Return only the <output> tag and no other text.`}function f(e,t){let n=`- Each commit belongs to exactly one branch.
|
|
71
|
+
${t?.promptFragment?`- ${c} Follow the repository's branch naming conventions (see above).`:`- ${c}`}
|
|
72
72
|
- Changes addressing the same pattern across different files belong in ONE branch. For example, applying the same bug fix to multiple functions, or updating similar config across modules — these are the same concern even if the files are different.`,r=`- Consider merge-order preference, not just code dependencies. Even if two concerns compile independently, they may need to be in the same branch or stacked branches if merging one without the other would be confusing or incomplete (e.g., documentation that describes utilities introduced in other commits should be in the same branch or stacked after those utilities).`;return e===`conservative`?`${n}
|
|
73
73
|
- Default to a single branch. Only create multiple branches when concerns are completely unrelated — different domains, different purposes, and no merge-order preference between them.
|
|
74
74
|
${r}
|
|
@@ -79,22 +79,22 @@ ${r}
|
|
|
79
79
|
- Create separate branches when concerns are genuinely independent — meaning they address different problems or features, and a reviewer would gain nothing from seeing them together in one PR. Two commits that each add a separate utility function unrelated to each other are good candidates for independent branches.
|
|
80
80
|
- Keep changes in the same branch when they form a coherent story that benefits from being reviewed together, even if they could technically stand alone.
|
|
81
81
|
${r}
|
|
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
|
|
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 p(e){return e===`consolidated`?`
|
|
83
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`?`
|
|
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
|
|
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 m(r){let{hunks:a,commitMessages:o,instructions:s,authoring:c,analysis:l,hunkSummaries:u,condensed:d,mode:f=`branches`,redactHunkContent:p,inputTokenBudget:m,systemPromptChars:h,onBudgetReduction:g}=r,_=e(c),v=i(l),y=n(u,a),b=a.map(e=>e.index).join(`, `),x=f===`commits`?`Examine the following ${a.length} code hunks (indices: ${b}) and organize them into commits. Every listed index must appear exactly once.`:`Examine the following ${a.length} code hunks (indices: ${b}) and organize them into commits and branches. Every listed index must appear exactly once.`,{prompt:S,hunksCondensedForBudget:C}=t({hunks:a,assemble:e=>{let t=`${x}
|
|
85
85
|
|
|
86
86
|
<hunks>
|
|
87
87
|
${e}
|
|
88
|
-
</hunks>`;return
|
|
88
|
+
</hunks>`;return o?.length&&(t+=`
|
|
89
89
|
|
|
90
90
|
The following commit messages are from the original commits. Use them to understand intent and match their style:
|
|
91
91
|
|
|
92
92
|
<commit_messages>
|
|
93
|
-
${JSON.stringify(
|
|
94
|
-
</commit_messages>`),
|
|
93
|
+
${JSON.stringify(o)}
|
|
94
|
+
</commit_messages>`),y&&(t+=`\n\n${y}`),v&&(t+=`\n\n${v}`),_&&(t+=`\n\n${_}`),s&&(t+=`
|
|
95
95
|
|
|
96
96
|
Carefully follow these additional instructions, but do not deviate from the output structure:
|
|
97
|
-
${
|
|
97
|
+
${s}`),t+=`\n\n${f===`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},condensed:d,redactHunkContent:p,inputTokenBudget:m,systemPromptChars:h});return g?.(C),S}function h(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.`:g(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+=`
|
|
98
98
|
|
|
99
99
|
Return your grouping as JSON inside <output> tags. Wrap all commits in a single branch with id "default", name "", title "", description "":
|
|
100
100
|
<output>
|
|
@@ -142,7 +142,7 @@ Return your grouping as JSON inside <output> tags:
|
|
|
142
142
|
}
|
|
143
143
|
</output>
|
|
144
144
|
|
|
145
|
-
Return only the <output> tag and no other text.`,n}function
|
|
145
|
+
Return only the <output> tag and no other text.`,n}function g(e){let t=e?.branchSplitting??`balanced`,n=`Based on your analysis of the changes, now organize the hunks into commits and branches. Each hunk must be assigned to exactly one commit, and each commit belongs to exactly one branch.`,r=`Consider merge-order preference: even if two concerns compile independently, they should be in the same branch or stacked if merging one without the other would be incomplete or confusing (e.g., documentation for utilities should be with or after those utilities).`;return t===`conservative`?`${n}
|
|
146
146
|
|
|
147
147
|
Default to a single branch. Only create multiple branches when concerns are completely unrelated — different domains, different purposes, no merge-order preference.
|
|
148
148
|
${r}
|
|
@@ -155,4 +155,4 @@ Create separate branches when concerns are genuinely independent — different p
|
|
|
155
155
|
|
|
156
156
|
Keep changes in the same branch when they form a coherent story that benefits from joint review, or when there is a merge-order preference between them.
|
|
157
157
|
${r}
|
|
158
|
-
When in doubt, consider: "Would a reviewer want to see these together, or would separating them make review easier?"`}export{
|
|
158
|
+
When in doubt, consider: "Would a reviewer want to see these together, or would separating them make review easier?"`}export{h as buildGroupContinuationPrompt,l as buildGroupSystemPrompt,m as buildGroupUserPrompt};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ProgressEvent, TokenUsage } from "../../ai/types.js";
|
|
1
|
+
import { ProgressEvent, TokenUsage, ValidationRepair } from "../../ai/types.js";
|
|
2
|
+
import { AuthoringContext } from "../../context.js";
|
|
2
3
|
import { BranchGroup, ComposeHunk, PromptOverride } from "../types.js";
|
|
3
4
|
import { ConventionDescriptor } from "../../git/utils/conventions.js";
|
|
4
5
|
import { OnAfterModelCall, OnBeforeModelCall, OnBeforePrompt } from "../ai-loop.js";
|
|
@@ -26,9 +27,24 @@ interface GroupingStrategy {
|
|
|
26
27
|
}
|
|
27
28
|
interface GroupInput {
|
|
28
29
|
hunks?: ComposeHunk[];
|
|
30
|
+
/**
|
|
31
|
+
* Analysis to group against, in place of running `analyze` first. Rendered into the prompt as the
|
|
32
|
+
* authoritative breakdown of the change. Ignored in session mode, where the analysis is already
|
|
33
|
+
* in the conversation.
|
|
34
|
+
*/
|
|
29
35
|
analysis?: AnalysisResult;
|
|
36
|
+
/** One-line summaries of what individual hunks do, keyed by index. See `ComposeInput.hunkSummaries`. */
|
|
37
|
+
hunkSummaries?: Record<number, string>;
|
|
30
38
|
commitMessages?: string[];
|
|
31
39
|
instructions?: string;
|
|
40
|
+
/**
|
|
41
|
+
* What the author knows that the hunks cannot show — intent, issue text, per-file rationale,
|
|
42
|
+
* ordering constraints. Rendered into the prompt as escaped, tagged data, never as instructions;
|
|
43
|
+
* for guidance the model should act on, use {@link instructions}.
|
|
44
|
+
*/
|
|
45
|
+
authoring?: AuthoringContext;
|
|
46
|
+
/** Send hunk metadata only, omitting every patch body. See `ComposeInput.condenseHunks`. */
|
|
47
|
+
condensed?: boolean;
|
|
32
48
|
conventions?: ConventionDescriptor;
|
|
33
49
|
/** "commits" = flat commit list (no branch naming). "branches" = AI decides branches (default). */
|
|
34
50
|
mode?: 'commits' | 'branches';
|
|
@@ -69,6 +85,11 @@ interface GroupOutput {
|
|
|
69
85
|
callIds: string[];
|
|
70
86
|
/** Hunks condensed to metadata to fit the input-token budget. Undefined when the AI call was skipped. */
|
|
71
87
|
hunksCondensedCount?: number;
|
|
88
|
+
/**
|
|
89
|
+
* Deterministic repairs applied to the accepted AI response instead of spending a retry on it.
|
|
90
|
+
* Undefined when the response needed no repair.
|
|
91
|
+
*/
|
|
92
|
+
repairs?: ValidationRepair[];
|
|
72
93
|
}
|
|
73
94
|
//#endregion
|
|
74
95
|
export { GroupInput, GroupOutput, GroupingResult, GroupingStrategy };
|
|
@@ -2,6 +2,14 @@ import { ValidationResult } from "../ai-loop.js";
|
|
|
2
2
|
import { GroupingResult } from "./types.js";
|
|
3
3
|
|
|
4
4
|
//#region src/tasks/group/validator.d.ts
|
|
5
|
-
|
|
5
|
+
interface GroupValidationOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Commit ids whose hunks a later phase restores verbatim. A contested hunk is kept in the
|
|
8
|
+
* locked claimant: dropping it there would be undone by substitution, and the model would then
|
|
9
|
+
* be told the hunk is missing or duplicated when it did neither.
|
|
10
|
+
*/
|
|
11
|
+
lockedCommitIds?: ReadonlySet<string>;
|
|
12
|
+
}
|
|
13
|
+
declare function validateGroupResponse(responseText: string, expectedHunkIndices: ReadonlySet<number>, mode?: 'commits' | 'branches', existingBranchNames?: Set<string>, options?: GroupValidationOptions): ValidationResult<GroupingResult>;
|
|
6
14
|
//#endregion
|
|
7
|
-
export { validateGroupResponse };
|
|
15
|
+
export { GroupValidationOptions, validateGroupResponse };
|