@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.
- package/CHANGELOG.md +16 -0
- package/dist/errors/task.d.ts +11 -1
- package/dist/errors/task.js +1 -1
- package/dist/git/utils/conventions.d.ts +7 -0
- package/dist/git/utils/conventions.js +8 -9
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/tasks/analyze/analyze.js +1 -1
- package/dist/tasks/analyze/prompt.d.ts +4 -1
- package/dist/tasks/analyze/prompt.js +5 -5
- package/dist/tasks/analyze/schema.d.ts +127 -36
- package/dist/tasks/analyze/schema.js +1 -1
- package/dist/tasks/analyze/types.d.ts +8 -0
- package/dist/tasks/analyze/validator.js +2 -2
- package/dist/tasks/compose-group/compose-group.js +1 -1
- package/dist/tasks/compose-group/prompt.d.ts +4 -1
- package/dist/tasks/compose-group/prompt.js +23 -29
- package/dist/tasks/compose-group/types.d.ts +5 -0
- package/dist/tasks/group/group.js +1 -1
- package/dist/tasks/group/prompt.d.ts +4 -1
- package/dist/tasks/group/prompt.js +15 -21
- package/dist/tasks/group/schema.d.ts +89 -17
- package/dist/tasks/group/schema.js +1 -1
- package/dist/tasks/group/types.d.ts +8 -0
- package/dist/tasks/group/validator.js +6 -6
- package/dist/tasks/order/prompt.js +5 -5
- package/dist/tasks/order/schema.d.ts +35 -10
- package/dist/tasks/order/schema.js +1 -1
- package/dist/tasks/order/validator.js +2 -2
- package/dist/tasks/partition/prompt.js +9 -9
- package/dist/tasks/partition/schema.d.ts +46 -12
- package/dist/tasks/partition/schema.js +1 -1
- package/dist/tasks/partition/validator.js +2 -2
- package/dist/tasks/quick-compose/prompt.js +8 -9
- package/dist/tasks/quick-compose/schema.d.ts +142 -25
- package/dist/tasks/quick-compose/schema.js +1 -1
- package/dist/tasks/quick-compose/validator.js +5 -5
- package/dist/tasks/utils/budget.d.ts +40 -0
- package/dist/tasks/utils/budget.js +1 -0
- package/dist/tasks/utils/json-schema.d.ts +61 -0
- package/dist/tasks/utils/json-schema.js +1 -0
- package/dist/tasks/utils/noise.d.ts +21 -0
- package/dist/tasks/utils/noise.js +2 -0
- package/dist/tasks/utils/redact.d.ts +7 -1
- package/dist/tasks/utils/redact.js +1 -1
- package/dist/workflows/compose.d.ts +36 -0
- package/dist/workflows/compose.js +3 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/) and this
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.6.0] - 2026-07-24
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Adds `inputTokenBudget` on `ComposeInput`, `ComposePlanInput`, and the hunk-bearing task inputs (`AnalyzeInput`, `GroupInput`, `ComposeGroupInput`) — a ceiling on estimated input tokens for each hunk-bearing prompt, defaulting to the exported `DEFAULT_INPUT_TOKEN_BUDGET` (180k). When a prompt would exceed the budget, hunks are progressively condensed — biggest-first — to metadata only (index, file name, hunk header, line counts); a diff that cannot fit even fully condensed throws the new `DiffTooLargeError` (exported), which carries `estimatedTokens` and `budgetTokens` so consumers can surface an "exclude generated files or commit in parts" hint. New `hunksCondensedCount` fields on `ComposeResult` / `ComposePlanResult` (and on `AnalyzeOutput` / `GroupOutput`) report how many hunks were condensed — non-zero means the AI reasoned over less than the full diff and grouping quality may degrade.
|
|
14
|
+
- Exports `isNoiseHunk` and `withDefaultNoiseRedaction` — the built-in noise detector behind the new on-by-default redaction (see Changed). Flags dependency lockfiles by basename (`package-lock.json`, `pnpm-lock.yaml`, `cargo.lock`, ...), vendored/build path segments (`node_modules`, `vendor`, `dist`, `__generated__`, ...), minified/source-map/generated filename patterns, codegen body markers, and binary hunks. `withDefaultNoiseRedaction` combines the detector with an optional consumer predicate for reuse in custom task-level flows.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- AI prompts now redact noise-hunk bodies by default — the built-in noise detector is combined (OR) with any consumer `redactHunkContent`, so a caller predicate extends the default rather than replacing it, and matching hunks reach the AI as metadata plus a placeholder instead of full patch text. Prevents pathological diffs (lockfile- or generated-content-dominated) from sending megabytes of zero-signal content verbatim. Opt out with the new `disableDefaultNoiseReduction` on `ComposeInput` / `ComposePlanInput`; audit what was masked via the new `hunksRedactedCount` on `ComposeResult` / `ComposePlanResult` (undefined when no redaction predicate ran).
|
|
19
|
+
- Removed `zod` as a runtime dependency. Task output schemas are now hand-written JSON Schema literals validated by a small internal validator; the JSON Schema text embedded in prompts is byte-identical to what `zod` previously emitted (snapshot-enforced). No public API change.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Commit-convention detection no longer degrades generated commit messages to bare subject lines. Detection previously sampled only commit subjects, so it never learned whether a repository writes descriptive bodies, and the detected conventions then replaced the default full-message instruction in the `group` / `compose-group` / `quick-compose` prompts. Convention gathering now samples commit bodies as well and records a `bodyStyle` on `CommitConventions`; the prompt builders always keep the summary-and-body structure and let detected conventions augment it rather than replace it. Full messages are the default and collapse to a single subject line only when the repository's history clearly favors one-liners.
|
|
24
|
+
|
|
9
25
|
## [0.5.0] - 2026-07-16
|
|
10
26
|
|
|
11
27
|
### Added
|
package/dist/errors/task.d.ts
CHANGED
|
@@ -2,5 +2,15 @@
|
|
|
2
2
|
declare class TaskInputError extends Error {
|
|
3
3
|
constructor(message: string);
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Thrown when a diff is too large to fit the input-token budget even after every hunk
|
|
7
|
+
* has been reduced to metadata-only. Consumers should surface a "commit in parts /
|
|
8
|
+
* exclude generated files" hint. Carries the estimate and the budget it exceeded.
|
|
9
|
+
*/
|
|
10
|
+
declare class DiffTooLargeError extends Error {
|
|
11
|
+
readonly estimatedTokens: number;
|
|
12
|
+
readonly budgetTokens: number;
|
|
13
|
+
constructor(estimatedTokens: number, budgetTokens: number);
|
|
14
|
+
}
|
|
5
15
|
//#endregion
|
|
6
|
-
export { TaskInputError };
|
|
16
|
+
export { DiffTooLargeError, TaskInputError };
|
package/dist/errors/task.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=class extends Error{constructor(e){super(e),this.name=`TaskInputError`}};export{e as TaskInputError};
|
|
1
|
+
var e=class extends Error{constructor(e){super(e),this.name=`TaskInputError`}},t=class extends Error{estimatedTokens;budgetTokens;constructor(e,t){super(`Diff too large to compose: an estimated ${e} input tokens exceeds the ${t}-token budget even after reducing every hunk to metadata. Exclude generated files or commit in parts.`),this.name=`DiffTooLargeError`,this.estimatedTokens=e,this.budgetTokens=t}};export{t as DiffTooLargeError,e as TaskInputError};
|
|
@@ -7,6 +7,13 @@ interface CommitConventions {
|
|
|
7
7
|
scopeStyle?: 'parenthesized' | 'none';
|
|
8
8
|
casing?: 'lowercase' | 'capitalized';
|
|
9
9
|
issueRefPattern?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Whether the repository's commits typically carry a descriptive body.
|
|
12
|
+
* `'detailed'` — most commits include a body; `'minimal'` — commits are
|
|
13
|
+
* almost always a lone subject line. `undefined` when the sample is too
|
|
14
|
+
* small or mixed to call, which callers treat as "write full messages".
|
|
15
|
+
*/
|
|
16
|
+
bodyStyle?: 'detailed' | 'minimal';
|
|
10
17
|
samples: string[];
|
|
11
18
|
}
|
|
12
19
|
interface BranchConventions {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import{forEachRef as e,getConfig as t,log as n,showFile as r}from"../port-dispatch.js";const i=/^(feat|fix|refactor|chore|docs|style|test|perf|ci|build|revert)(\(.+?\))?!?:\s/,a=[{re:/\(#\d+\)/,label:`(#NNN)`},{re:/#\d+/,label:`#NNN`},{re:/[A-Z]{2,}-\d+/,label:`PROJ-NNN`}],o=/^(feat|fix|chore|feature|hotfix|bugfix|refactor|release|support)\//,s=/^(\d+)-|^([A-Z]{2,}-\d+)-/,c=new Set([`main`,`master`,`develop`,`development`,`staging`,`production`])
|
|
2
|
-
`).filter(Boolean)
|
|
3
|
-
`).filter(Boolean)
|
|
4
|
-
`
|
|
5
|
-
`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
`)}\n`}function S(e){if(e.format===`unknown`&&e.samples.length===0)return`Branch names: No clear convention detected. Use descriptive kebab-case names.
|
|
1
|
+
import{forEachRef as e,getConfig as t,log as n,showFile as r}from"../port-dispatch.js";const i=/^(feat|fix|refactor|chore|docs|style|test|perf|ci|build|revert)(\(.+?\))?!?:\s/,a=[{re:/\(#\d+\)/,label:`(#NNN)`},{re:/#\d+/,label:`#NNN`},{re:/[A-Z]{2,}-\d+/,label:`PROJ-NNN`}],o=/^(feat|fix|chore|feature|hotfix|bugfix|refactor|release|support)\//,s=/^(\d+)-|^([A-Z]{2,}-\d+)-/,c=new Set([`main`,`master`,`develop`,`development`,`staging`,`production`]),l=`%x1e%s%x1f%b`,u=/^[A-Za-z][A-Za-z-]*:\s/;async function d(e){let{git:t,commitLimit:n=30,branchLimit:r=15}=e,i=[],a=e.author??await A(t),[o,s,c]=await Promise.all([f(t,i),m(t,n,a),v(t,r)]),l=y(s,o),u=b(c);return{commits:l,branches:u,sources:i,promptFragment:T(l,u)}}async function f(e,t){let n={hasConventionalCommitsDep:!1,hasCommitizen:!1},r=await O(e,`package.json`);if(r)try{let e=JSON.parse(r),i={...e.dependencies??{},...e.devDependencies??{}};i[`@commitlint/config-conventional`]&&(n.hasConventionalCommitsDep=!0,t.push(`package.json (@commitlint/config-conventional)`)),(e.config?.commitizen||i.commitizen)&&(n.hasCommitizen=!0,t.push(`package.json (commitizen)`))}catch{}for(let r of[`.commitlintrc.json`,`.commitlintrc.yml`,`.commitlintrc.yaml`]){let i=await O(e,r);if(i){t.push(r);let e=p(i);e.length>0&&(n.commitlintTypes=e);break}}for(let r of[`commitlint.config.js`,`commitlint.config.cjs`,`commitlint.config.mjs`])if(await k(e,r)){t.push(r),n.commitlintTypes||(n.hasConventionalCommitsDep=!0);break}let i=await O(e,`.gitmessage`);return i&&(n.gitMessageTemplate=i.trim(),t.push(`.gitmessage`)),n}function p(e){try{let t=JSON.parse(e)?.rules?.[`type-enum`];if(Array.isArray(t)){let e=t.find(e=>Array.isArray(e));if(Array.isArray(e))return e.filter(e=>typeof e==`string`)}}catch{let t=e.match(/type-enum[^[]*\[([^\]]+)\]/);if(t)return t[1].split(`,`).map(e=>e.trim().replace(/['"]/g,``)).filter(Boolean)}return[]}async function m(e,t,r){try{let i=h(await n(e,{format:l,noMerges:!0,limit:t,author:r}));return r&&i.length<5?h(await n(e,{format:l,noMerges:!0,limit:t})):i}catch{return[]}}function h(e){return e.split(``).filter(e=>e.trim().length>0).map(e=>{let t=e.indexOf(``);return t===-1?{subject:e.trim(),body:``}:{subject:e.slice(0,t).trim(),body:e.slice(t+1).trim()}})}function g(e){if(e.length<5)return;let t=e.filter(e=>_(e.body)).length/e.length;if(t>=.3)return`detailed`;if(t<=.1)return`minimal`}function _(e){return e?e.split(`
|
|
2
|
+
`).map(e=>e.trim()).filter(Boolean).some(e=>!u.test(e)):!1}async function v(t,n){try{return(await e(t,{format:`%(refname:short)`,prefix:`refs/heads/`,sort:`-committerdate`,limit:n+5})).trim().split(`
|
|
3
|
+
`).filter(Boolean).filter(e=>!c.has(e)&&!e.startsWith(`release/`)).slice(0,n)}catch{return[]}}function y(e,t){let n=e.map(e=>e.subject);if(n.length===0)return{format:t.hasConventionalCommitsDep||t.hasCommitizen?`conventional-commits`:`unknown`,prefixes:t.commitlintTypes,samples:[]};let r=n.filter(e=>i.test(e)),a=r.length/n.length>.6||t.hasConventionalCommitsDep||t.hasCommitizen,o,s;if(a){let e=new Set,n=0;for(let t of r){let r=t.match(i);r&&(e.add(r[1]),r[2]&&n++)}o=t.commitlintTypes??[...e].sort(),s=n/Math.max(r.length,1)>.3?`parenthesized`:`none`}let c=x(n,a),l=S(n),u=g(e),d=C(n,a?o:void 0);return{format:a?`conventional-commits`:`plain`,prefixes:o,scopeStyle:s,casing:c,issueRefPattern:l,bodyStyle:u,samples:d}}function b(e){if(e.length===0)return{format:`unknown`,samples:[]};let t=e.filter(e=>o.test(e)),n=e.filter(e=>s.test(e));if(t.length/e.length>.5){let n=new Set;for(let e of t){let t=e.match(o);t&&n.add(t[1])}return{format:`prefixed`,prefixes:[...n].sort(),separator:`/`,samples:w(e,[...n])}}return n.length/e.length>.5?{format:`issue-number`,separator:`-`,samples:e.slice(0,5)}:{format:e.length>=3?`plain`:`unknown`,samples:e.slice(0,5)}}function x(e,t){let n=0,r=0;for(let a of e){let e=(t?a.replace(i,``):a).trim()[0];e&&(e===e.toUpperCase()&&e!==e.toLowerCase()?r++:n++)}if(!(n+r<3)){if(n/(n+r)>.7)return`lowercase`;if(r/(n+r)>.7)return`capitalized`}}function S(e){for(let{re:t,label:n}of a)if(e.filter(e=>t.test(e)).length/e.length>.2)return n}function C(e,t){if(!t||t.length===0)return e.slice(0,5);let n=[],r=new Set;for(let i of t){let t=e.find(e=>e.startsWith(`${i}(`)||e.startsWith(`${i}:`));if(t&&!r.has(t)&&(n.push(t),r.add(t),n.length>=5))break}for(let t of e){if(n.length>=5)break;r.has(t)||(n.push(t),r.add(t))}return n}function w(e,t){let n=[],r=new Set;for(let i of t){let t=e.find(e=>e.startsWith(`${i}/`));if(t&&!r.has(t)&&(n.push(t),r.add(t),n.length>=5))break}for(let t of e){if(n.length>=5)break;r.has(t)||(n.push(t),r.add(t))}return n}function T(e,t){let n=[`## Repository conventions
|
|
4
|
+
`];return n.push(E(e)),n.push(D(t)),n.push(`Match these conventions in your output.`),n.join(`
|
|
5
|
+
`)}function E(e){if(e.format===`unknown`&&e.samples.length===0)return`Commit messages: No clear convention detected. Use clear, concise messages.
|
|
6
|
+
`;let t=[];if(e.format===`conventional-commits`){let n=e.scopeStyle===`parenthesized`?` with scope in parentheses`:``;t.push(`Commit messages follow Conventional Commits format${n}: type${e.scopeStyle===`parenthesized`?`(scope)`:``}: description`),e.prefixes?.length&&t.push(`Allowed types: ${e.prefixes.join(`, `)}`)}else t.push(`Commit messages use plain English format.`);if(e.casing&&t.push(`${e.casing===`capitalized`?`Capitalized`:`Lowercase`} first word after any prefix.`),e.issueRefPattern&&t.push(`Issues often referenced as ${e.issueRefPattern}.`),e.samples.length>0){t.push(`Example subject lines:`);for(let n of e.samples)t.push(`- ${n}`)}return`${t.join(`
|
|
7
|
+
`)}\n`}function D(e){if(e.format===`unknown`&&e.samples.length===0)return`Branch names: No clear convention detected. Use descriptive kebab-case names.
|
|
9
8
|
`;let t=[];if(e.format===`prefixed`){let n=e.separator??`/`;t.push(`Branch names use type${n}kebab-case-slug format.`),e.prefixes?.length&&t.push(`Common prefixes: ${e.prefixes.join(`, `)}`)}else e.format===`issue-number`?t.push(`Branch names start with an issue number followed by a kebab-case description.`):t.push(`Branch names use descriptive kebab-case format.`);if(e.samples.length>0){t.push(`Examples:`);for(let n of e.samples)t.push(`- ${n}`)}return`${t.join(`
|
|
10
|
-
`)}\n`}async function
|
|
9
|
+
`)}\n`}async function O(e,t){try{return await r(e,`HEAD`,t)}catch{return null}}async function k(e,t){try{return await r(e,`HEAD`,t),!0}catch{return!1}}async function A(e){let n=await t(e,`user.email`);if(n)return n;let r=await t(e,`user.name`);if(r)return r}export{d as extractConventions};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PhaseDetail, ProgressEvent, TokenUsage } from "./ai/types.js";
|
|
2
2
|
import { AIError, AIErrorCode } from "./errors/ai.js";
|
|
3
3
|
import { GitError, GitErrorCode } from "./errors/git.js";
|
|
4
|
-
import { TaskInputError } from "./errors/task.js";
|
|
4
|
+
import { DiffTooLargeError, TaskInputError } from "./errors/task.js";
|
|
5
5
|
import { ComposeWorkflowError, ComposeWorkflowErrorCode, ComposeWorkflowInputError } from "./errors/workflow.js";
|
|
6
6
|
import { ApplyPatchOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitOps, ComposeGitPort, ComposeGitPortMissingOpError, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, GitCommitIdentity, IndexScopedOptions, LogOptions, OpOptions, SigningConfig, StageAllOptions, StashPushOptions, UpdateRefOptions } from "./ports/git.js";
|
|
7
7
|
import { BranchGroup, BranchOrdering, BranchPartition, CommitDetail, CommitSuggestion, ComposeHunk, ComposeTaskName, PromptOverride } from "./tasks/types.js";
|
|
@@ -25,9 +25,11 @@ import { group } from "./tasks/group/group.js";
|
|
|
25
25
|
import { order } from "./tasks/order/order.js";
|
|
26
26
|
import { partition } from "./tasks/partition/partition.js";
|
|
27
27
|
import { quickCompose } from "./tasks/quick-compose/quick-compose.js";
|
|
28
|
+
import { DEFAULT_INPUT_TOKEN_BUDGET } from "./tasks/utils/budget.js";
|
|
29
|
+
import { isNoiseHunk, withDefaultNoiseRedaction } from "./tasks/utils/noise.js";
|
|
28
30
|
import { ApplyComposePlanInput, ApplyComposePlanResult, ComposeApplyPlan, ComposeDepth, ComposeDiffStats, ComposeInput, ComposePlan, ComposePlanInput, ComposePlanResult, ComposeProgressEvent, ComposeResult, ComposeStrategy, applyComposePlan, compose, composePlan, defaultPhaseMessages } from "./workflows/compose.js";
|
|
29
31
|
import { RefinePlanInput, RefinePlanResult, RefineProgressEvent, refinePlan } from "./workflows/refine.js";
|
|
30
32
|
import { SplitInput, SplitPlan, SplitProgressEvent, SplitResult, split } from "./workflows/split.js";
|
|
31
33
|
import { UndoComposeInput, ValidateUndoComposeInput, listUndoIds, undoCompose, validateUndoCompose } from "./workflows/undo.js";
|
|
32
34
|
import { UndoForceOptions } from "@gitkraken/shared-tools";
|
|
33
|
-
export { AIError, type AIErrorCode, type AfterModelCallInfo, type AnalysisResult, type AnalyzeInput, type AnalyzeOutput, type ApplyComposePlanInput, type ApplyComposePlanResult, type ApplyPatchOptions, type BeforeModelCallInfo, type BeforePromptInfo, type BranchGroup, type BranchOrdering, type BranchPartition, type CheckoutOptions, type CleanOptions, type CommitDetail, type CommitSuggestion, type CommitTreeOptions, type ComposeApplyPlan, type ComposeBranchPlan, type ComposeContext, type ComposeDepth, type ComposeDiffStats, type ComposeGitOps, type ComposeGitPort, ComposeGitPortMissingOpError, type ComposeGroupInput, type ComposeGroupOutput, type ComposeHunk, type ComposeInput, type ComposePlan, type ComposePlanInput, type ComposePlanResult, type ComposeProgressEvent, type ComposeResult, type ComposeSession, type ComposeSource, type ComposeStrategy, type ComposeTarget, type ComposeTaskName, ComposeWorkflowError, type ComposeWorkflowErrorCode, ComposeWorkflowInputError, type Concern, type ConcernDependency, type ConventionDescriptor, type DeleteBranchOptions, type DiffTreeOptions, type ForEachRefOptions, type GitCommitIdentity, GitError, type GitErrorCode, type GroupInput, type GroupOutput, type GroupingResult, type GroupingStrategy, type HunkAnnotation, type HunkOrderingConstraint, type IndexScopedOptions, type InteriorRefViolation, type LogOptions, type OnAfterModelCall, type OnBeforeModelCall, type OnBeforePrompt, type OpOptions, type OrderInput, type OrderOutput, type OrderingResult, type OverrideSafetyFailures, type PartitionInput, type PartitionOutput, type PartitionResult, type PartitionStrategy, type PhaseDetail, type ProgressEvent, type PromptOverride, type QuickComposeInput, type QuickComposeOutput, type QuickComposeResult, REDACTED_HUNK_CONTENT, type RedactHunkContent, type RefinePlanInput, type RefinePlanResult, type RefineProgressEvent, type SigningConfig, type SplitInput, type SplitPlan, type SplitProgressEvent, type SplitResult, type StageAllOptions, type StashConflict, type StashPushOptions, TaskInputError, type TokenUsage, type UndoComposeInput, type UndoForceOptions, type UpdateRefOptions, type ValidateUndoComposeInput, analyze, applyComposePlan, compose, composeGroup, composePlan, defaultPhaseMessages, detectInteriorRefs, estimatedCharactersPerToken, group, listUndoIds, order, partition, quickCompose, refinePlan, split, undoCompose, validateUndoCompose };
|
|
35
|
+
export { AIError, type AIErrorCode, type AfterModelCallInfo, type AnalysisResult, type AnalyzeInput, type AnalyzeOutput, type ApplyComposePlanInput, type ApplyComposePlanResult, type ApplyPatchOptions, type BeforeModelCallInfo, type BeforePromptInfo, type BranchGroup, type BranchOrdering, type BranchPartition, type CheckoutOptions, type CleanOptions, type CommitDetail, type CommitSuggestion, type CommitTreeOptions, type ComposeApplyPlan, type ComposeBranchPlan, type ComposeContext, type ComposeDepth, type ComposeDiffStats, type ComposeGitOps, type ComposeGitPort, ComposeGitPortMissingOpError, type ComposeGroupInput, type ComposeGroupOutput, type ComposeHunk, type ComposeInput, type ComposePlan, type ComposePlanInput, type ComposePlanResult, type ComposeProgressEvent, type ComposeResult, type ComposeSession, type ComposeSource, type ComposeStrategy, type ComposeTarget, type ComposeTaskName, ComposeWorkflowError, type ComposeWorkflowErrorCode, ComposeWorkflowInputError, type Concern, type ConcernDependency, type ConventionDescriptor, DEFAULT_INPUT_TOKEN_BUDGET, type DeleteBranchOptions, DiffTooLargeError, type DiffTreeOptions, type ForEachRefOptions, type GitCommitIdentity, GitError, type GitErrorCode, type GroupInput, type GroupOutput, type GroupingResult, type GroupingStrategy, type HunkAnnotation, type HunkOrderingConstraint, type IndexScopedOptions, type InteriorRefViolation, type LogOptions, type OnAfterModelCall, type OnBeforeModelCall, type OnBeforePrompt, type OpOptions, type OrderInput, type OrderOutput, type OrderingResult, type OverrideSafetyFailures, type PartitionInput, type PartitionOutput, type PartitionResult, type PartitionStrategy, type PhaseDetail, type ProgressEvent, type PromptOverride, type QuickComposeInput, type QuickComposeOutput, type QuickComposeResult, REDACTED_HUNK_CONTENT, type RedactHunkContent, type RefinePlanInput, type RefinePlanResult, type RefineProgressEvent, type SigningConfig, type SplitInput, type SplitPlan, type SplitProgressEvent, type SplitResult, type StageAllOptions, type StashConflict, type StashPushOptions, TaskInputError, type TokenUsage, type UndoComposeInput, type UndoForceOptions, type UpdateRefOptions, type ValidateUndoComposeInput, analyze, applyComposePlan, compose, composeGroup, composePlan, defaultPhaseMessages, detectInteriorRefs, estimatedCharactersPerToken, group, isNoiseHunk, listUndoIds, order, partition, quickCompose, refinePlan, split, undoCompose, validateUndoCompose, withDefaultNoiseRedaction };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AIError as e}from"./errors/ai.js";import{GitError as t}from"./errors/git.js";import{TaskInputError as
|
|
1
|
+
import{AIError as e}from"./errors/ai.js";import{GitError as t}from"./errors/git.js";import{DiffTooLargeError as n,TaskInputError as r}from"./errors/task.js";import{ComposeWorkflowError as i,ComposeWorkflowInputError as a}from"./errors/workflow.js";import{ComposeGitPortMissingOpError as o}from"./ports/git.js";import{detectInteriorRefs as s}from"./git/safety-range.js";import{estimatedCharactersPerToken as c}from"./tasks/ai-loop.js";import{REDACTED_HUNK_CONTENT as l}from"./tasks/utils/redact.js";import{DEFAULT_INPUT_TOKEN_BUDGET as u}from"./tasks/utils/budget.js";import{analyze as d}from"./tasks/analyze/analyze.js";import{composeGroup as f}from"./tasks/compose-group/compose-group.js";import{group as p}from"./tasks/group/group.js";import{order as m}from"./tasks/order/order.js";import{partition as h}from"./tasks/partition/partition.js";import{quickCompose as g}from"./tasks/quick-compose/quick-compose.js";import{isNoiseHunk as _,withDefaultNoiseRedaction as v}from"./tasks/utils/noise.js";import{applyComposePlan as y,compose as b,composePlan as x,defaultPhaseMessages as S}from"./workflows/compose.js";import{refinePlan as C}from"./workflows/refine.js";import{split as w}from"./workflows/split.js";import{listUndoIds as T,undoCompose as E,validateUndoCompose as D}from"./workflows/undo.js";export{e as AIError,o as ComposeGitPortMissingOpError,i as ComposeWorkflowError,a as ComposeWorkflowInputError,u as DEFAULT_INPUT_TOKEN_BUDGET,n as DiffTooLargeError,t as GitError,l as REDACTED_HUNK_CONTENT,r as TaskInputError,d as analyze,y as applyComposePlan,b as compose,f as composeGroup,x as composePlan,S as defaultPhaseMessages,s as detectInteriorRefs,c as estimatedCharactersPerToken,p as group,_ as isNoiseHunk,T as listUndoIds,m as order,h as partition,g as quickCompose,C as refinePlan,w as split,E as undoCompose,D as validateUndoCompose,v as withDefaultNoiseRedaction};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{buildAnalyzeSystemPrompt as r,buildAnalyzeUserPrompt as i}from"./prompt.js";import{validateAnalyzeResponse as a}from"./validator.js";async function o(o){let s=o.hunks;if(!s||s.length===0)throw new e(`No hunks provided: supply 'hunks'`);if(o.session?.completedSteps.analyze)return{result:o.session.completedSteps.analyze,session:o.session,attempts:0,durationMs:0,callIds:[]};let c=n(i({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions,redactHunkContent:o.redactHunkContent}),o.promptOverride),
|
|
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=s.length,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};
|
|
@@ -12,7 +12,10 @@ declare function buildAnalyzeUserPrompt(params: {
|
|
|
12
12
|
issueUrl?: string;
|
|
13
13
|
pullRequestUrl?: string;
|
|
14
14
|
instructions?: string;
|
|
15
|
-
redactHunkContent?: RedactHunkContent;
|
|
15
|
+
redactHunkContent?: RedactHunkContent; /** Input-token ceiling that triggers progressive hunk reduction. Defaults to the shared budget. */
|
|
16
|
+
inputTokenBudget?: number; /** Char count of the system prompt, folded into the budget estimate. Defaults to 0. */
|
|
17
|
+
systemPromptChars?: number; /** Reports how many hunks were condensed for budget (full to condensed), for consumer telemetry. */
|
|
18
|
+
onBudgetReduction?: (hunksCondensedForBudget: number) => void;
|
|
16
19
|
}): string;
|
|
17
20
|
//#endregion
|
|
18
21
|
export { buildAnalyzeSystemPrompt, buildAnalyzeUserPrompt };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{assembleHunkPrompt as e}from"../utils/budget.js";import{analysisResultJsonSchema as t}from"./schema.js";const n=JSON.stringify(t,null,2);function r(){return`You are an advanced AI code analysis assistant. Your task is to deeply analyze a set of code changes (provided as diff hunks) and produce a structured understanding of what the changes accomplish, what concerns they address, and how hunks relate to each other.
|
|
2
2
|
|
|
3
3
|
Your analysis must produce:
|
|
4
4
|
1. **Title** — a short, PR-style title for the changes (under 72 characters, imperative mood, e.g. "Add user authentication and session management"). This should capture the essence of what the changes accomplish.
|
|
@@ -25,10 +25,10 @@ Rules:
|
|
|
25
25
|
Output your analysis as JSON inside a <output> tag matching this schema:
|
|
26
26
|
|
|
27
27
|
\`\`\`json
|
|
28
|
-
${
|
|
28
|
+
${n}
|
|
29
29
|
\`\`\`
|
|
30
30
|
|
|
31
|
-
Return only the <output> tag and no other text.`}function
|
|
32
|
-
`)}\n</context>`),r?.length){let e=r.map((e,t)=>({index:t,sha:i?.[t],message:e}));
|
|
31
|
+
Return only the <output> tag and no other text.`}function i(t){let{hunks:n,commitMessages:r,commitOrder:i,branchName:a,baseBranch:o,issueUrl:s,pullRequestUrl:c,instructions:l,redactHunkContent:u,inputTokenBudget:d,systemPromptChars:f,onBudgetReduction:p}=t,{prompt:m,hunksCondensedForBudget:h}=e({hunks:n,assemble:e=>{let t=`Analyze the following code changes:\n\n<hunks>\n${e}\n</hunks>`,n=[];if(a&&n.push(`Branch: ${a}`),o&&n.push(`Base branch: ${o}`),s&&n.push(`Issue: ${s}`),c&&n.push(`Pull request: ${c}`),n.length>0&&(t+=`\n\n<context>\n${n.join(`
|
|
32
|
+
`)}\n</context>`),r?.length){let e=r.map((e,t)=>({index:t,sha:i?.[t],message:e}));t+=`\n\n<commit_messages>\n${JSON.stringify(e)}\n</commit_messages>`}return l&&(t+=`\n\nCarefully follow these additional instructions, but do not deviate from the output structure:\n${l}`),t+=`
|
|
33
33
|
|
|
34
|
-
Now analyze the changes and return your structured analysis. Return only the <output> tag and no other text.`,d}export{
|
|
34
|
+
Now analyze the changes and return your structured analysis. Return only the <output> tag and no other text.`,t},redactHunkContent:u,inputTokenBudget:d,systemPromptChars:f});return p?.(h),m}export{r as buildAnalyzeSystemPrompt,i as buildAnalyzeUserPrompt};
|
|
@@ -1,38 +1,129 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
1
|
//#region src/tasks/analyze/schema.d.ts
|
|
4
|
-
declare const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
2
|
+
declare const analysisResultJsonSchema: {
|
|
3
|
+
readonly $schema: "https://json-schema.org/draft/2020-12/schema";
|
|
4
|
+
readonly type: "object";
|
|
5
|
+
readonly properties: {
|
|
6
|
+
readonly title: {
|
|
7
|
+
readonly type: "string";
|
|
8
|
+
readonly minLength: 1;
|
|
9
|
+
readonly maxLength: 72;
|
|
10
|
+
readonly description: "Short PR-style title for the changes (under 72 chars, imperative mood, e.g. 'Add user authentication and session management')";
|
|
11
|
+
};
|
|
12
|
+
readonly summary: {
|
|
13
|
+
readonly type: "string";
|
|
14
|
+
readonly minLength: 1;
|
|
15
|
+
readonly description: "High-level summary of all changes";
|
|
16
|
+
};
|
|
17
|
+
readonly concerns: {
|
|
18
|
+
readonly minItems: 1;
|
|
19
|
+
readonly type: "array";
|
|
20
|
+
readonly items: {
|
|
21
|
+
readonly type: "object";
|
|
22
|
+
readonly properties: {
|
|
23
|
+
readonly id: {
|
|
24
|
+
readonly type: "string";
|
|
25
|
+
readonly description: "Unique concern ID (e.g. c1, c2)";
|
|
26
|
+
};
|
|
27
|
+
readonly name: {
|
|
28
|
+
readonly type: "string";
|
|
29
|
+
readonly description: "Short concern name";
|
|
30
|
+
};
|
|
31
|
+
readonly description: {
|
|
32
|
+
readonly type: "string";
|
|
33
|
+
readonly description: "What this concern accomplishes";
|
|
34
|
+
};
|
|
35
|
+
readonly hunkIndices: {
|
|
36
|
+
readonly type: "array";
|
|
37
|
+
readonly items: {
|
|
38
|
+
readonly type: "number";
|
|
39
|
+
};
|
|
40
|
+
readonly description: "Indices of hunks belonging to this concern";
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
readonly required: readonly ["id", "name", "description", "hunkIndices"];
|
|
44
|
+
readonly additionalProperties: false;
|
|
45
|
+
};
|
|
46
|
+
readonly description: "Distinct themes, features, or purposes within the changes";
|
|
47
|
+
};
|
|
48
|
+
readonly dependencies: {
|
|
49
|
+
readonly default: readonly [];
|
|
50
|
+
readonly description: "Relationships between concerns";
|
|
51
|
+
readonly type: "array";
|
|
52
|
+
readonly items: {
|
|
53
|
+
readonly type: "object";
|
|
54
|
+
readonly properties: {
|
|
55
|
+
readonly from: {
|
|
56
|
+
readonly type: "string";
|
|
57
|
+
readonly description: "Concern ID";
|
|
58
|
+
};
|
|
59
|
+
readonly to: {
|
|
60
|
+
readonly type: "string";
|
|
61
|
+
readonly description: "Concern ID that 'from' depends on";
|
|
62
|
+
};
|
|
63
|
+
readonly reason: {
|
|
64
|
+
readonly type: "string";
|
|
65
|
+
readonly description: "Why the dependency exists";
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
readonly required: readonly ["from", "to", "reason"];
|
|
69
|
+
readonly additionalProperties: false;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
readonly hunkAnnotations: {
|
|
73
|
+
readonly type: "array";
|
|
74
|
+
readonly items: {
|
|
75
|
+
readonly type: "object";
|
|
76
|
+
readonly properties: {
|
|
77
|
+
readonly hunkIndex: {
|
|
78
|
+
readonly type: "number";
|
|
79
|
+
readonly description: "Index of the hunk";
|
|
80
|
+
};
|
|
81
|
+
readonly concernId: {
|
|
82
|
+
readonly type: "string";
|
|
83
|
+
readonly description: "Concern this hunk belongs to";
|
|
84
|
+
};
|
|
85
|
+
readonly purpose: {
|
|
86
|
+
readonly type: "string";
|
|
87
|
+
readonly description: "What this hunk does in context of its concern";
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
readonly required: readonly ["hunkIndex", "concernId", "purpose"];
|
|
91
|
+
readonly additionalProperties: false;
|
|
92
|
+
};
|
|
93
|
+
readonly description: "Per-hunk concern assignment and purpose";
|
|
94
|
+
};
|
|
95
|
+
readonly orderingConstraints: {
|
|
96
|
+
readonly default: readonly [];
|
|
97
|
+
readonly description: "Provider/consumer ordering between hunks";
|
|
98
|
+
readonly type: "array";
|
|
99
|
+
readonly items: {
|
|
100
|
+
readonly type: "object";
|
|
101
|
+
readonly properties: {
|
|
102
|
+
readonly provider: {
|
|
103
|
+
readonly type: "number";
|
|
104
|
+
readonly description: "Hunk index that defines/creates something";
|
|
105
|
+
};
|
|
106
|
+
readonly consumer: {
|
|
107
|
+
readonly type: "number";
|
|
108
|
+
readonly description: "Hunk index that references/uses it";
|
|
109
|
+
};
|
|
110
|
+
readonly kind: {
|
|
111
|
+
readonly type: "string";
|
|
112
|
+
readonly enum: readonly ["file-reference", "import", "function-call", "type-reference", "config-reference", "other"];
|
|
113
|
+
readonly description: "Type of provider/consumer relationship";
|
|
114
|
+
};
|
|
115
|
+
readonly reason: {
|
|
116
|
+
readonly type: "string";
|
|
117
|
+
readonly description: "Brief explanation of the relationship";
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
readonly required: readonly ["provider", "consumer", "kind", "reason"];
|
|
121
|
+
readonly additionalProperties: false;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
readonly required: readonly ["title", "summary", "concerns", "dependencies", "hunkAnnotations", "orderingConstraints"];
|
|
126
|
+
readonly additionalProperties: false;
|
|
127
|
+
};
|
|
37
128
|
//#endregion
|
|
38
|
-
export {
|
|
129
|
+
export { analysisResultJsonSchema };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
const e={$schema:`https://json-schema.org/draft/2020-12/schema`,type:`object`,properties:{title:{type:`string`,minLength:1,maxLength:72,description:`Short PR-style title for the changes (under 72 chars, imperative mood, e.g. 'Add user authentication and session management')`},summary:{type:`string`,minLength:1,description:`High-level summary of all changes`},concerns:{minItems:1,type:`array`,items:{type:`object`,properties:{id:{type:`string`,description:`Unique concern ID (e.g. c1, c2)`},name:{type:`string`,description:`Short concern name`},description:{type:`string`,description:`What this concern accomplishes`},hunkIndices:{type:`array`,items:{type:`number`},description:`Indices of hunks belonging to this concern`}},required:[`id`,`name`,`description`,`hunkIndices`],additionalProperties:!1},description:`Distinct themes, features, or purposes within the changes`},dependencies:{default:[],description:`Relationships between concerns`,type:`array`,items:{type:`object`,properties:{from:{type:`string`,description:`Concern ID`},to:{type:`string`,description:`Concern ID that 'from' depends on`},reason:{type:`string`,description:`Why the dependency exists`}},required:[`from`,`to`,`reason`],additionalProperties:!1}},hunkAnnotations:{type:`array`,items:{type:`object`,properties:{hunkIndex:{type:`number`,description:`Index of the hunk`},concernId:{type:`string`,description:`Concern this hunk belongs to`},purpose:{type:`string`,description:`What this hunk does in context of its concern`}},required:[`hunkIndex`,`concernId`,`purpose`],additionalProperties:!1},description:`Per-hunk concern assignment and purpose`},orderingConstraints:{default:[],description:`Provider/consumer ordering between hunks`,type:`array`,items:{type:`object`,properties:{provider:{type:`number`,description:`Hunk index that defines/creates something`},consumer:{type:`number`,description:`Hunk index that references/uses it`},kind:{type:`string`,enum:[`file-reference`,`import`,`function-call`,`type-reference`,`config-reference`,`other`],description:`Type of provider/consumer relationship`},reason:{type:`string`,description:`Brief explanation of the relationship`}},required:[`provider`,`consumer`,`kind`,`reason`],additionalProperties:!1}}},required:[`title`,`summary`,`concerns`,`dependencies`,`hunkAnnotations`,`orderingConstraints`],additionalProperties:!1};export{e as analysisResultJsonSchema};
|
|
@@ -17,6 +17,12 @@ interface AnalyzeInput {
|
|
|
17
17
|
instructions?: string;
|
|
18
18
|
/** See {@link RedactHunkContent}. */
|
|
19
19
|
redactHunkContent?: RedactHunkContent;
|
|
20
|
+
/**
|
|
21
|
+
* Input-token ceiling for the hunk prompt. When exceeded, hunks are progressively reduced
|
|
22
|
+
* (biggest-first) to metadata; a diff that can't fit even condensed throws `DiffTooLargeError`.
|
|
23
|
+
* Defaults to `DEFAULT_INPUT_TOKEN_BUDGET`.
|
|
24
|
+
*/
|
|
25
|
+
inputTokenBudget?: number;
|
|
20
26
|
model: AiModelPort;
|
|
21
27
|
session?: ComposeSession;
|
|
22
28
|
maxRetries?: number;
|
|
@@ -69,6 +75,8 @@ interface AnalyzeOutput {
|
|
|
69
75
|
attempts: number;
|
|
70
76
|
durationMs: number;
|
|
71
77
|
callIds: string[];
|
|
78
|
+
/** Hunks condensed to metadata to fit the input-token budget. Undefined when the AI call was skipped. */
|
|
79
|
+
hunksCondensedCount?: number;
|
|
72
80
|
}
|
|
73
81
|
//#endregion
|
|
74
82
|
export { AnalysisResult, AnalyzeInput, AnalyzeOutput, Concern, ConcernDependency, HunkAnnotation, HunkOrderingConstraint };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{extractOutputTag as e}from"../ai-loop.js";import{
|
|
1
|
+
import{extractOutputTag as e}from"../ai-loop.js";import{analysisResultJsonSchema as t}from"./schema.js";import{validateJsonSchema as n}from"../utils/json-schema.js";function r(r,o){let s=null;try{if(s=e(r),!s)return i([`No <output> tag found in response`],a());let c=n(t,JSON.parse(s));if(!c.success)return i(c.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`),a());let l=c.data,u=new Set(l.hunkAnnotations.map(e=>e.hunkIndex)),d=[...new Set(Array.from({length:o},(e,t)=>t))].filter(e=>!u.has(e));return d.length>0?i([`Missing annotations for hunks: ${d.join(`, `)}`],`Your response is missing annotations for some hunks. Every hunk must be annotated.\n\nMissing: ${d.join(`, `)}\n\nPlease provide a corrected response.`):{valid:!0,result:{title:l.title,summary:l.summary,concerns:l.concerns,dependencies:l.dependencies,hunkAnnotations:l.hunkAnnotations,orderingConstraints:l.orderingConstraints}}}catch(e){let t=e instanceof Error?e.message:String(e),n=(s??r).slice(-300);return i([`Failed to parse response as valid JSON: ${t}`,`Response tail: ${n}`],a())}}function i(e,t){return{valid:!1,errors:e,retryFeedback:t}}function a(){return`Your response has an invalid structure. Please return JSON inside <output> tags with this structure:
|
|
2
2
|
<output>
|
|
3
3
|
{
|
|
4
4
|
"title": "Short PR-style title (under 72 chars)",
|
|
@@ -10,4 +10,4 @@ import{extractOutputTag as e}from"../ai-loop.js";import{analysisResultSchema as
|
|
|
10
10
|
}
|
|
11
11
|
</output>
|
|
12
12
|
|
|
13
|
-
Return only the <output> tag and no other text.`}export{
|
|
13
|
+
Return only the <output> tag and no other text.`}export{r as validateAnalyzeResponse};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{validateGroupResponse as r}from"../group/validator.js";import{buildComposeGroupSystemPrompt as i,buildComposeGroupUserPrompt as a}from"./prompt.js";async function o(o){let s=o.hunks,c=o.mode??`branches`;if(!s||s.length===0)throw new e(`No hunks provided for compose-group`);if(o.session?.completedSteps.composeGroup)return{result:o.session.completedSteps.composeGroup,session:o.session,attempts:0,durationMs:0,callIds:[]};let l=o.promptOverride?.system??i(o.conventions,c,o.strategy,o.includeAnalysis,o.lightweight),u=n(a({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions,condensed:o.condensed,redactHunkContent:o.redactHunkContent}),o.promptOverride),
|
|
1
|
+
import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{validateGroupResponse as r}from"../group/validator.js";import{buildComposeGroupSystemPrompt as i,buildComposeGroupUserPrompt as a}from"./prompt.js";async function o(o){let s=o.hunks,c=o.mode??`branches`;if(!s||s.length===0)throw new e(`No hunks provided for compose-group`);if(o.session?.completedSteps.composeGroup)return{result:o.session.completedSteps.composeGroup,session:o.session,attempts:0,durationMs:0,callIds:[]};let l=o.promptOverride?.system??i(o.conventions,c,o.strategy,o.includeAnalysis,o.lightweight),u,d=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:l.length,onBudgetReduction:e=>{u=e}}),o.promptOverride),f=await t({model:o.model,systemPrompt:l,userPrompt:d,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>r(e,s.length,c,o.existingBranchNames),taskName:`compose-group`}),p=o.session?.completedSteps??{},m={...f.session,completedSteps:{...p,composeGroup:f.result}};return{result:f.result,session:m,usage:f.usage,attempts:f.attempts,durationMs:f.durationMs,callIds:f.callIds,hunksCondensedCount:u}}export{o as composeGroup};
|
|
@@ -22,7 +22,10 @@ declare function buildComposeGroupUserPrompt(params: {
|
|
|
22
22
|
pullRequestUrl?: string;
|
|
23
23
|
instructions?: string;
|
|
24
24
|
condensed?: boolean;
|
|
25
|
-
redactHunkContent?: RedactHunkContent;
|
|
25
|
+
redactHunkContent?: RedactHunkContent; /** Input-token ceiling that triggers progressive hunk reduction. Defaults to the shared budget. */
|
|
26
|
+
inputTokenBudget?: number; /** Char count of the system prompt, folded into the budget estimate. Defaults to 0. */
|
|
27
|
+
systemPromptChars?: number; /** Reports how many hunks were condensed for budget (full to condensed), for consumer telemetry. */
|
|
28
|
+
onBudgetReduction?: (hunksCondensedForBudget: number) => void;
|
|
26
29
|
}): string;
|
|
27
30
|
//#endregion
|
|
28
31
|
export { buildComposeGroupSystemPrompt, buildComposeGroupUserPrompt };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{assembleHunkPrompt as e}from"../utils/budget.js";import{groupingResultJsonSchema as t}from"../group/schema.js";const n=JSON.stringify(t,null,2),r=`
|
|
2
2
|
In addition to the \`branches\` field, include an \`analysis\` field at the top level:
|
|
3
3
|
|
|
4
4
|
\`\`\`json
|
|
@@ -10,15 +10,12 @@ In addition to the \`branches\` field, include an \`analysis\` field at the top
|
|
|
10
10
|
"branches": [ ... ]
|
|
11
11
|
}
|
|
12
12
|
\`\`\`
|
|
13
|
-
`,
|
|
13
|
+
`,i=`5. Write a full commit message for each commit containing both a summary and a description:
|
|
14
14
|
- **Summary** (first line): concise, 50 chars max, third-person singular present tense ("Fixes", "Adds", "Improves"). This line stands alone as the commit subject.
|
|
15
15
|
- **Blank line**: mandatory separator between summary and description.
|
|
16
16
|
- **Description** (body): one or more lines explaining *why* this change was made, not *what* it does. Focus on motivation, trade-offs, or context a reviewer needs. For non-trivial changes, always include a description — don't leave it at just the summary.
|
|
17
|
-
- Informal yet professional tone.`;function o(e,t=`branches`,n,r,i){return i?t===`commits`?d(e,n):f(e,n):t===`commits`?s(e,n,r):c(e,n,r)}function s(e,t,
|
|
18
|
-
6. Write a detailed explanation for each commit as if explaining to a reviewer.
|
|
19
|
-
|
|
20
|
-
${e.promptFragment}`:`${a}
|
|
21
|
-
6. Write a detailed explanation for each commit as if explaining to a reviewer`,s=u(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits.
|
|
17
|
+
- Informal yet professional tone.`,a=`5. Write a concise single-line commit message for each commit — a subject only, no body: 50 chars max, third-person singular present tense ("Fixes", "Adds", "Improves"). This repository's history overwhelmingly uses one-line commit messages, so do not add a description.`;function o(e,t=`branches`,n,r,i){return i?t===`commits`?d(e,n):f(e,n):t===`commits`?s(e,n,r):c(e,n,r)}function s(e,t,o){let s=`${e?.commits.bodyStyle===`minimal`?a:i}
|
|
18
|
+
6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`,c=u(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits.
|
|
22
19
|
|
|
23
20
|
## Critical constraints (output is rejected if violated)
|
|
24
21
|
|
|
@@ -29,37 +26,34 @@ ${e.promptFragment}`:`${a}
|
|
|
29
26
|
|
|
30
27
|
Before producing the \`<output>\` tag, do a self-check: count the total number of hunk indices across every commit's \`hunks\` array. That count MUST equal the input hunk count. If it does not match, you have either duplicated or skipped a hunk — find and fix it before outputting.
|
|
31
28
|
|
|
32
|
-
${
|
|
29
|
+
${o?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
|
|
33
30
|
|
|
34
31
|
Before grouping, analyze the changes to understand:
|
|
35
32
|
- **Concerns** — what distinct themes, features, or purposes exist across the hunks
|
|
36
33
|
- **Dependencies** — which hunks provide things (define functions, types, files) that other hunks consume (import, call, reference)
|
|
37
34
|
- **Ordering constraints** — if hunk A defines something hunk B uses, they should be in the same commit or A's commit should come before B's
|
|
38
35
|
|
|
39
|
-
${
|
|
36
|
+
${o?"Output this analysis as a JSON field named `analysis` alongside your `branches` field. The `analysis` field should contain:\n- `concerns`: array of strings, each describing a distinct theme or feature in the changes\n- `key_dependencies`: array of strings, each describing an ordering dependency between concerns":`Do NOT output this analysis. Use it internally to inform your grouping decisions.`}
|
|
40
37
|
|
|
41
38
|
## Step 2: Group hunks into commits
|
|
42
39
|
|
|
43
40
|
1. Group hunks into logical units that make sense together and can be applied atomically
|
|
44
41
|
2. Ensure each commit is self-contained and atomic
|
|
45
|
-
${o}
|
|
46
42
|
${s}
|
|
43
|
+
${c}
|
|
47
44
|
|
|
48
45
|
## Required Output Format
|
|
49
46
|
|
|
50
47
|
Output your organization as JSON inside a <output> tag matching this schema:
|
|
51
48
|
|
|
52
49
|
\`\`\`json
|
|
53
|
-
${
|
|
50
|
+
${n}
|
|
54
51
|
\`\`\`
|
|
55
|
-
${
|
|
52
|
+
${o?r:``}
|
|
56
53
|
Wrap all commits in a single branch object with id "default", name "", title "", and description "".
|
|
57
54
|
|
|
58
|
-
Return only the <output> tag and no other text.`}function c(e,t,
|
|
59
|
-
6. Write a detailed explanation for each commit as if explaining to a reviewer.
|
|
60
|
-
|
|
61
|
-
${e.promptFragment}`:`${a}
|
|
62
|
-
6. Write a detailed explanation for each commit as if explaining to a reviewer`,s=l(t?.branchSplitting,e),c=u(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits and branches.
|
|
55
|
+
Return only the <output> tag and no other text.`}function c(e,t,o){let s=`${e?.commits.bodyStyle===`minimal`?a:i}
|
|
56
|
+
6. Write a detailed explanation for each commit as if explaining to a reviewer.${e?.promptFragment?`\n\n${e.promptFragment}`:``}`,c=l(t?.branchSplitting,e),d=u(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits and branches.
|
|
63
57
|
|
|
64
58
|
## Critical constraints (output is rejected if violated)
|
|
65
59
|
|
|
@@ -70,7 +64,7 @@ ${e.promptFragment}`:`${a}
|
|
|
70
64
|
|
|
71
65
|
Before producing the \`<output>\` tag, do a self-check: count the total number of hunk indices across every commit's \`hunks\` array (across every branch). That count MUST equal the input hunk count. If it does not match, you have either duplicated or skipped a hunk — find and fix it before outputting.
|
|
72
66
|
|
|
73
|
-
${
|
|
67
|
+
${o?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
|
|
74
68
|
|
|
75
69
|
Before grouping, analyze the changes to understand:
|
|
76
70
|
- **Concerns** — what distinct themes, features, or purposes exist across the hunks
|
|
@@ -78,7 +72,7 @@ Before grouping, analyze the changes to understand:
|
|
|
78
72
|
- **Ordering constraints** — if hunk A defines something hunk B uses, they should be in the same commit or A's commit should come before B's
|
|
79
73
|
- **Merge-order preference** — even if two concerns compile independently, would merging one without the other be confusing or incomplete?
|
|
80
74
|
|
|
81
|
-
${
|
|
75
|
+
${o?"Output this analysis as a JSON field named `analysis` alongside your `branches` field. The `analysis` field should contain:\n- `concerns`: array of strings, each describing a distinct theme or feature in the changes\n- `key_dependencies`: array of strings, each describing an ordering dependency between concerns":`Do NOT output this analysis. Use it internally to inform your grouping decisions.`}
|
|
82
76
|
|
|
83
77
|
## Step 2: Organize hunks into commits and branches
|
|
84
78
|
|
|
@@ -91,21 +85,21 @@ Your job has two levels:
|
|
|
91
85
|
|
|
92
86
|
1. Group hunks into logical units that make sense together and can be applied atomically
|
|
93
87
|
2. Ensure each commit is self-contained and atomic
|
|
94
|
-
${
|
|
95
|
-
${
|
|
88
|
+
${s}
|
|
89
|
+
${d}
|
|
96
90
|
|
|
97
91
|
### Branch guidelines
|
|
98
92
|
|
|
99
|
-
${
|
|
93
|
+
${c}
|
|
100
94
|
|
|
101
95
|
## Required Output Format
|
|
102
96
|
|
|
103
97
|
Output your organization as JSON inside a <output> tag matching this schema:
|
|
104
98
|
|
|
105
99
|
\`\`\`json
|
|
106
|
-
${
|
|
100
|
+
${n}
|
|
107
101
|
\`\`\`
|
|
108
|
-
${
|
|
102
|
+
${o?r:``}
|
|
109
103
|
Return only the <output> tag and no other text.`}function l(e,t){let n=`Each branch must have a descriptive name, a short PR-style title (under 72 characters, imperative mood), and at least one commit.`,r=`- Each commit belongs to exactly one branch.
|
|
110
104
|
${t?.promptFragment?`- ${n} Follow the repository's branch naming conventions (see above).`:`- ${n}`}
|
|
111
105
|
- Changes addressing the same pattern across different files belong in ONE branch.`,i=`- Consider merge-order preference, not just code dependencies. Even if two concerns compile independently, they may need to be in the same branch if merging one without the other would be confusing or incomplete.`;return e===`conservative`?`${r}\n- Default to a single branch. Only create multiple branches when concerns are completely unrelated.\n${i}\n- When in doubt, use fewer branches.`:e===`aggressive`?`${r}\n- Create separate branches for each distinct concern.\n${i}\n- Prefer more branches over fewer.`:`${r}\n- Create separate branches when concerns are genuinely independent — meaning a reviewer would gain nothing from seeing them together.\n- Keep changes in the same branch when they form a coherent story.\n${i}\n- When in doubt: "Would a reviewer want to see these together?"`}function u(e){return e===`consolidated`?`
|
|
@@ -158,11 +152,11 @@ JSON inside <output> tags. Each branch needs a unique ID and non-empty name.
|
|
|
158
152
|
|
|
159
153
|
CRITICAL: Every hunk index must appear exactly once across all commits in all branches. Every commit must contain at least one hunk. Missing hunks, duplicate hunks, or empty commits will fail validation. Each branch must have a unique ID and non-empty name.
|
|
160
154
|
|
|
161
|
-
Return only the <output> tag and no other text.`}function p(t){let{hunks:n,commitMessages:r,commitOrder:i,branchName:a,baseBranch:o,issueUrl:s,pullRequestUrl:c,instructions:l,condensed:u,redactHunkContent:d
|
|
155
|
+
Return only the <output> tag and no other text.`}function p(t){let{hunks:n,commitMessages:r,commitOrder:i,branchName:a,baseBranch:o,issueUrl:s,pullRequestUrl:c,instructions:l,condensed:u,redactHunkContent:d,inputTokenBudget:f,systemPromptChars:p,onBudgetReduction:m}=t,{prompt:h,hunksCondensedForBudget:g}=e({hunks:n,assemble:e=>{let t=`Organize these ${n.length} code hunks (indices 0 to ${n.length-1}) into commits. Every index must appear exactly once.
|
|
162
156
|
|
|
163
157
|
<hunks>
|
|
164
|
-
${
|
|
165
|
-
</hunks>`,
|
|
166
|
-
`)}`),r?.length&&(
|
|
158
|
+
${e}
|
|
159
|
+
</hunks>`,u=[];return a&&u.push(`Branch: ${a}`),o&&u.push(`Base: ${o}`),s&&u.push(`Issue: ${s}`),c&&u.push(`PR: ${c}`),u.length>0&&(t+=`\n\nContext:\n${u.join(`
|
|
160
|
+
`)}`),r?.length&&(t+=`\n\nOriginal commit messages (match their style):\n<commit_messages>\n${JSON.stringify(r)}\n</commit_messages>`),i?.length&&(t+=`\n\nOriginal commit order:\n${JSON.stringify(i)}`),l&&(t+=`\n\nAdditional instructions:\n${l}`),t+=`
|
|
167
161
|
|
|
168
|
-
Return only the <output> tag and no other text.`,p}export{o as buildComposeGroupSystemPrompt,p as buildComposeGroupUserPrompt};
|
|
162
|
+
Return only the <output> tag and no other text.`,t},condensed:u,redactHunkContent:d,inputTokenBudget:f,systemPromptChars:p});return m?.(g),h}export{o as buildComposeGroupSystemPrompt,p as buildComposeGroupUserPrompt};
|
|
@@ -40,6 +40,11 @@ interface ComposeGroupInput {
|
|
|
40
40
|
condensed?: boolean;
|
|
41
41
|
/** No-op when `condensed: true`. See {@link RedactHunkContent}. */
|
|
42
42
|
redactHunkContent?: RedactHunkContent;
|
|
43
|
+
/**
|
|
44
|
+
* Input-token ceiling. When exceeded, hunks are condensed to metadata biggest-first;
|
|
45
|
+
* throws `DiffTooLargeError` if they still cannot fit. Defaults to `DEFAULT_INPUT_TOKEN_BUDGET`.
|
|
46
|
+
*/
|
|
47
|
+
inputTokenBudget?: number;
|
|
43
48
|
model: AiModelPort;
|
|
44
49
|
/** Prior session to resume from. When `completedSteps.composeGroup` is present, skip the AI call. */
|
|
45
50
|
session?: ComposeSession;
|