@gitkraken/compose-tools 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/errors/task.d.ts +11 -1
  3. package/dist/errors/task.js +1 -1
  4. package/dist/git/diff-collector.js +5 -5
  5. package/dist/git/utils/conventions.d.ts +7 -0
  6. package/dist/git/utils/conventions.js +8 -9
  7. package/dist/index.d.ts +5 -2
  8. package/dist/index.js +1 -1
  9. package/dist/tasks/analyze/analyze.js +1 -1
  10. package/dist/tasks/analyze/prompt.d.ts +4 -1
  11. package/dist/tasks/analyze/prompt.js +5 -5
  12. package/dist/tasks/analyze/schema.d.ts +127 -36
  13. package/dist/tasks/analyze/schema.js +1 -1
  14. package/dist/tasks/analyze/types.d.ts +8 -0
  15. package/dist/tasks/analyze/validator.js +2 -2
  16. package/dist/tasks/compose-group/compose-group.js +1 -1
  17. package/dist/tasks/compose-group/prompt.d.ts +4 -1
  18. package/dist/tasks/compose-group/prompt.js +23 -29
  19. package/dist/tasks/compose-group/types.d.ts +5 -0
  20. package/dist/tasks/group/group.js +1 -1
  21. package/dist/tasks/group/prompt.d.ts +4 -1
  22. package/dist/tasks/group/prompt.js +15 -21
  23. package/dist/tasks/group/schema.d.ts +89 -17
  24. package/dist/tasks/group/schema.js +1 -1
  25. package/dist/tasks/group/types.d.ts +8 -0
  26. package/dist/tasks/group/validator.js +6 -6
  27. package/dist/tasks/order/prompt.js +5 -5
  28. package/dist/tasks/order/schema.d.ts +35 -10
  29. package/dist/tasks/order/schema.js +1 -1
  30. package/dist/tasks/order/validator.js +2 -2
  31. package/dist/tasks/partition/prompt.js +9 -9
  32. package/dist/tasks/partition/schema.d.ts +46 -12
  33. package/dist/tasks/partition/schema.js +1 -1
  34. package/dist/tasks/partition/validator.js +2 -2
  35. package/dist/tasks/quick-compose/prompt.js +8 -9
  36. package/dist/tasks/quick-compose/schema.d.ts +142 -25
  37. package/dist/tasks/quick-compose/schema.js +1 -1
  38. package/dist/tasks/quick-compose/validator.js +5 -5
  39. package/dist/tasks/utils/budget.d.ts +40 -0
  40. package/dist/tasks/utils/budget.js +1 -0
  41. package/dist/tasks/utils/json-schema.d.ts +61 -0
  42. package/dist/tasks/utils/json-schema.js +1 -0
  43. package/dist/tasks/utils/noise.d.ts +21 -0
  44. package/dist/tasks/utils/noise.js +2 -0
  45. package/dist/tasks/utils/redact.d.ts +7 -1
  46. package/dist/tasks/utils/redact.js +1 -1
  47. package/dist/workflows/compose.d.ts +36 -0
  48. package/dist/workflows/compose.js +3 -3
  49. package/dist/workflows/split.js +2 -2
  50. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -6,6 +6,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/) and this
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.7.0] - 2026-07-30
10
+
11
+ ### Added
12
+
13
+ - Export `parseUnifiedDiff`, so a consumer can turn a unified diff into the same `ComposeHunk[]` the composer scores. Needed by out-of-repo consumers (the commit-composer eval) that must index hunks exactly as `composeGroup` does.
14
+
15
+ ## [0.6.0] - 2026-07-24
16
+
17
+ ### Added
18
+
19
+ - 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.
20
+ - 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.
21
+
22
+ ### Changed
23
+
24
+ - 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).
25
+ - 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.
26
+
27
+ ### Fixed
28
+
29
+ - 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.
30
+
9
31
  ## [0.5.0] - 2026-07-16
10
32
 
11
33
  ### Added
@@ -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 };
@@ -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};
@@ -1,7 +1,7 @@
1
- import{GitError as e}from"../errors/git.js";import{ComposeWorkflowInputError as t}from"../errors/workflow.js";import{applyPatchToIndex as n,createBranch as r,currentBranch as i,diffTree as a,gitDir as o,listUntracked as s,log as c,mergeBase as l,mergeBaseIsAncestor as u,readTree as d,revParse as f,revParseVerify as p,stageAll as m,writeTree as h}from"./port-dispatch.js";import{getAuthorAndCoAuthorsForCombinedDiffHunk as g}from"./utils/authorship.js";import{sha256 as _}from"./utils/crypto.js";import{parseUnifiedDiff as v}from"./utils/hunk-parser.js";import{withUntrackedInTempIndex as y}from"./utils/temp-index.js";import{randomUUID as b}from"node:crypto";import{copyFileSync as x,unlinkSync as S}from"node:fs";import{tmpdir as C}from"node:os";import{join as w}from"node:path";async function T(e,t){if(t.type===`workdir`){let n=!t.stagedOnly&&!t.noUntracked;return{...await E(e,{includeUnstaged:!t.stagedOnly,includeUntracked:n}),untrackedIncluded:n}}if(t.type===`commit-range`){if(t.includeWorkdir){let n=t.includeWorkdir.includeStaged??!0,r=t.includeWorkdir.includeUnstaged??!0,i=t.includeWorkdir.includeUntracked??!0,a=await L(e,t.branch,t.from,t.to,{includeStaged:n,includeUnstaged:r,includeUntracked:i});return{hunks:a.hunks,snapshot:a.snapshot,combinedDiff:a.combinedDiff,commitMessages:a.commits.map(e=>e.message),commitOrder:a.commits.map(e=>e.sha),branchName:t.branch,baseBranch:void 0,branchSha:a.snapshot.branchSha,mergeBaseSha:a.snapshot.baseSha,untrackedIncluded:i}}let n=await M(e,t.branch,t.from,t.to);return{hunks:n.hunks,snapshot:n.snapshot,combinedDiff:n.combinedDiff,commitMessages:n.commits.map(e=>e.message),commitOrder:n.commits.map(e=>e.sha),branchName:t.branch,baseBranch:void 0,branchSha:n.snapshot.branchSha,mergeBaseSha:n.snapshot.baseSha,untrackedIncluded:!1}}let n=t.mergeTarget??`main`,r=await j(e,t.name,n);return{hunks:r.hunks,snapshot:r.snapshot,combinedDiff:r.combinedDiff,commitMessages:r.commits.map(e=>e.message),commitOrder:r.commits.map(e=>e.sha),branchName:t.name,baseBranch:n,branchSha:r.snapshot.branchSha,mergeBaseSha:r.snapshot.baseSha,untrackedIncluded:!1}}async function E(e,t){let n=t.includeUnstaged||t.includeUntracked;return t.includeUntracked?k(e,n):D(e,n)}async function D(e,t){let n=await f(e,`HEAD`),r=await O(e,n,t);return{hunks:v(r),snapshot:V(r,n,void 0,void 0,await H(e,{includeUnstaged:t,includeUntracked:!1})),combinedDiff:r}}async function O(e,t,n){if(!n)return a(e,t,await h(e));let r=w(await o(e),`index`),i=w(C(),`compose-snapshot-idx-${b()}`);try{x(r,i);let n={GIT_INDEX_FILE:i};return await m(e,{trackedOnly:!0,indexEnv:n}),a(e,t,await h(e,{indexEnv:n}))}finally{try{S(i)}catch{}}}async function k(e,t){return y(e,async n=>{await A(e,n,t);let r=await h(e,{indexEnv:n}),i=await f(e,`HEAD`),o=await a(e,i,r);return{hunks:v(o),snapshot:V(o,i,void 0,void 0,await H(e,{includeUnstaged:t,includeUntracked:!0})),combinedDiff:o}})}async function A(e,t,n){n&&await m(e,{trackedOnly:!0,indexEnv:t}),await m(e,{indexEnv:t})}async function j(e,t,n){let r=await B(e,t);return P(e,await l(e,await B(e,n),r),await f(e,r))}async function M(e,t,n,r){await B(e,t);let i=await f(e,n),a=await f(e,r);return P(e,await N(e,i),a)}async function N(e,n){try{return await f(e,`${n}^`)}catch{throw new t(`Cannot recompose commit ${n.slice(0,8)} — it has no parent (root commit). Recomposing the root commit is not currently supported.`)}}async function P(n,r,i){if(!await u(n,r,i))throw new t(`Cannot collect range ${r.slice(0,8)}..${i.slice(0,8)}: '${r.slice(0,8)}' is not an ancestor of '${i.slice(0,8)}'.`);let o=await a(n,r,i),s=v(o),l=I(await c(n,{format:`%H%n%an%n%ae%n%aI%n%B%n---COMMIT-END---`,range:`${r}..${i}`,reverse:!0}));if(l.length===0)throw new e(`NO_CHANGES`,`No commits in range '${r.slice(0,8)}..${i.slice(0,8)}'.`);let d=await F(n,l);for(let e of s){let{author:t,coAuthors:n}=g(d,e);t!=null&&(e.author=t),n.length>0&&(e.coAuthors=n)}let p=V(o,await f(n,`HEAD`),i,r);return{hunks:s,commits:l.map(e=>({sha:e.sha,message:e.message})),snapshot:p,combinedDiff:o}}async function F(e,t){let n=[];for(let r of t){let t;try{t=await a(e,`${r.sha}^`,r.sha)}catch{continue}let i=v(t,0);for(let e of i)e.author={name:r.authorName,email:r.authorEmail,date:r.authorDate};n.push(...i)}return n}function I(e){let t=[],n=e.split(`---COMMIT-END---`).filter(e=>e.trim());for(let e of n){let n=e.replace(/^\n+/,``).split(`
1
+ import{GitError as e}from"../errors/git.js";import{ComposeWorkflowInputError as t}from"../errors/workflow.js";import{applyPatchToIndex as n,createBranch as r,currentBranch as i,diffTree as a,gitDir as o,listUntracked as s,log as c,mergeBase as l,mergeBaseIsAncestor as u,readTree as d,revParse as f,revParseVerify as p,stageAll as m,writeTree as h}from"./port-dispatch.js";import{parseUnifiedDiff as g}from"./utils/hunk-parser.js";import{getAuthorAndCoAuthorsForCombinedDiffHunk as _}from"./utils/authorship.js";import{sha256 as v}from"./utils/crypto.js";import{withUntrackedInTempIndex as y}from"./utils/temp-index.js";import{randomUUID as b}from"node:crypto";import{copyFileSync as x,unlinkSync as S}from"node:fs";import{tmpdir as C}from"node:os";import{join as w}from"node:path";async function T(e,t){if(t.type===`workdir`){let n=!t.stagedOnly&&!t.noUntracked;return{...await E(e,{includeUnstaged:!t.stagedOnly,includeUntracked:n}),untrackedIncluded:n}}if(t.type===`commit-range`){if(t.includeWorkdir){let n=t.includeWorkdir.includeStaged??!0,r=t.includeWorkdir.includeUnstaged??!0,i=t.includeWorkdir.includeUntracked??!0,a=await L(e,t.branch,t.from,t.to,{includeStaged:n,includeUnstaged:r,includeUntracked:i});return{hunks:a.hunks,snapshot:a.snapshot,combinedDiff:a.combinedDiff,commitMessages:a.commits.map(e=>e.message),commitOrder:a.commits.map(e=>e.sha),branchName:t.branch,baseBranch:void 0,branchSha:a.snapshot.branchSha,mergeBaseSha:a.snapshot.baseSha,untrackedIncluded:i}}let n=await M(e,t.branch,t.from,t.to);return{hunks:n.hunks,snapshot:n.snapshot,combinedDiff:n.combinedDiff,commitMessages:n.commits.map(e=>e.message),commitOrder:n.commits.map(e=>e.sha),branchName:t.branch,baseBranch:void 0,branchSha:n.snapshot.branchSha,mergeBaseSha:n.snapshot.baseSha,untrackedIncluded:!1}}let n=t.mergeTarget??`main`,r=await j(e,t.name,n);return{hunks:r.hunks,snapshot:r.snapshot,combinedDiff:r.combinedDiff,commitMessages:r.commits.map(e=>e.message),commitOrder:r.commits.map(e=>e.sha),branchName:t.name,baseBranch:n,branchSha:r.snapshot.branchSha,mergeBaseSha:r.snapshot.baseSha,untrackedIncluded:!1}}async function E(e,t){let n=t.includeUnstaged||t.includeUntracked;return t.includeUntracked?k(e,n):D(e,n)}async function D(e,t){let n=await f(e,`HEAD`),r=await O(e,n,t);return{hunks:g(r),snapshot:V(r,n,void 0,void 0,await H(e,{includeUnstaged:t,includeUntracked:!1})),combinedDiff:r}}async function O(e,t,n){if(!n)return a(e,t,await h(e));let r=w(await o(e),`index`),i=w(C(),`compose-snapshot-idx-${b()}`);try{x(r,i);let n={GIT_INDEX_FILE:i};return await m(e,{trackedOnly:!0,indexEnv:n}),a(e,t,await h(e,{indexEnv:n}))}finally{try{S(i)}catch{}}}async function k(e,t){return y(e,async n=>{await A(e,n,t);let r=await h(e,{indexEnv:n}),i=await f(e,`HEAD`),o=await a(e,i,r);return{hunks:g(o),snapshot:V(o,i,void 0,void 0,await H(e,{includeUnstaged:t,includeUntracked:!0})),combinedDiff:o}})}async function A(e,t,n){n&&await m(e,{trackedOnly:!0,indexEnv:t}),await m(e,{indexEnv:t})}async function j(e,t,n){let r=await B(e,t);return P(e,await l(e,await B(e,n),r),await f(e,r))}async function M(e,t,n,r){await B(e,t);let i=await f(e,n),a=await f(e,r);return P(e,await N(e,i),a)}async function N(e,n){try{return await f(e,`${n}^`)}catch{throw new t(`Cannot recompose commit ${n.slice(0,8)} — it has no parent (root commit). Recomposing the root commit is not currently supported.`)}}async function P(n,r,i){if(!await u(n,r,i))throw new t(`Cannot collect range ${r.slice(0,8)}..${i.slice(0,8)}: '${r.slice(0,8)}' is not an ancestor of '${i.slice(0,8)}'.`);let o=await a(n,r,i),s=g(o),l=I(await c(n,{format:`%H%n%an%n%ae%n%aI%n%B%n---COMMIT-END---`,range:`${r}..${i}`,reverse:!0}));if(l.length===0)throw new e(`NO_CHANGES`,`No commits in range '${r.slice(0,8)}..${i.slice(0,8)}'.`);let d=await F(n,l);for(let e of s){let{author:t,coAuthors:n}=_(d,e);t!=null&&(e.author=t),n.length>0&&(e.coAuthors=n)}let p=V(o,await f(n,`HEAD`),i,r);return{hunks:s,commits:l.map(e=>({sha:e.sha,message:e.message})),snapshot:p,combinedDiff:o}}async function F(e,t){let n=[];for(let r of t){let t;try{t=await a(e,`${r.sha}^`,r.sha)}catch{continue}let i=g(t,0);for(let e of i)e.author={name:r.authorName,email:r.authorEmail,date:r.authorDate};n.push(...i)}return n}function I(e){let t=[],n=e.split(`---COMMIT-END---`).filter(e=>e.trim());for(let e of n){let n=e.replace(/^\n+/,``).split(`
2
2
  `);if(n.length<5)continue;let r=n[0],i=n[1],a=n[2],o=n[3],s=n.slice(4).join(`
3
- `).trimEnd(),c=o?new Date(o):void 0,l=c&&!Number.isNaN(c.getTime())?c:void 0;t.push({sha:r,authorName:i,authorEmail:a,authorDate:l,message:s})}return t}async function L(e,n,r,o,s){let l=await f(e,await B(e,n)),d=await f(e,r),p=await f(e,o),m=await f(e,`HEAD`);if(p!==l)throw new t(`commit-range with includeWorkdir requires 'to' to equal branch tip. Branch '${n}' tip is ${l}; got 'to'=${p}.`);if(m!==p)throw new t(`commit-range with includeWorkdir requires HEAD to equal 'to'. HEAD=${m}, 'to'=${p}.`);if(await i(e)==null)throw new t(`commit-range with includeWorkdir requires HEAD to be on a branch (non-detached). Check out the branch holding the range before composing.`);let h=await N(e,d),{finalTreeSha:_}=await R(e,m,s),y=await a(e,h,_);if(!await u(e,h,p))throw new t(`Cannot collect range ${h.slice(0,8)}..${p.slice(0,8)}: '${h.slice(0,8)}' is not an ancestor of '${p.slice(0,8)}'.`);let b=v(y),x=I(await c(e,{format:`%H%n%an%n%ae%n%aI%n%B%n---COMMIT-END---`,range:`${h}..${p}`,reverse:!0})),S=await F(e,x);for(let e of b){let{author:t,coAuthors:n}=g(S,e);t!=null&&(e.author=t),n.length>0&&(e.coAuthors=n)}let C=V(y,m,p,h,await H(e,s));return{hunks:b,commits:x.map(e=>({sha:e.sha,message:e.message})),snapshot:C,combinedDiff:y}}async function R(e,t,r){let i=w(C(),`compose-workdir-${b()}`),c={GIT_INDEX_FILE:i};try{if(await d(e,t,{indexEnv:c}),r.includeStaged){let r=await a(e,t,await h(e));r.trim()&&await n(e,r,{indexEnv:c})}if(r.includeUnstaged){let t=await h(e),r=w(C(),`compose-workdir-u-${b()}`),i={GIT_INDEX_FILE:r};try{x(w(await o(e),`index`),r),await m(e,{trackedOnly:!0,indexEnv:i});let s=await a(e,t,await h(e,{indexEnv:i}));s.trim()&&await n(e,s,{indexEnv:c})}finally{try{S(r)}catch{}}}if(r.includeUntracked){let t=await s(e);t.length>0&&(await m(e,{intentToAdd:!0,paths:t,indexEnv:c}),await m(e,{paths:t,indexEnv:c}))}return{finalTreeSha:await h(e,{indexEnv:c})}}finally{try{S(i)}catch{}}}async function z(e,t,n){let r=await B(e,t),i=await l(e,await B(e,n),r),o=await f(e,r),s=(await c(e,{format:`%H`,range:`${i}..${r}`,reverse:!0})).trim().split(`
3
+ `).trimEnd(),c=o?new Date(o):void 0,l=c&&!Number.isNaN(c.getTime())?c:void 0;t.push({sha:r,authorName:i,authorEmail:a,authorDate:l,message:s})}return t}async function L(e,n,r,o,s){let l=await f(e,await B(e,n)),d=await f(e,r),p=await f(e,o),m=await f(e,`HEAD`);if(p!==l)throw new t(`commit-range with includeWorkdir requires 'to' to equal branch tip. Branch '${n}' tip is ${l}; got 'to'=${p}.`);if(m!==p)throw new t(`commit-range with includeWorkdir requires HEAD to equal 'to'. HEAD=${m}, 'to'=${p}.`);if(await i(e)==null)throw new t(`commit-range with includeWorkdir requires HEAD to be on a branch (non-detached). Check out the branch holding the range before composing.`);let h=await N(e,d),{finalTreeSha:v}=await R(e,m,s),y=await a(e,h,v);if(!await u(e,h,p))throw new t(`Cannot collect range ${h.slice(0,8)}..${p.slice(0,8)}: '${h.slice(0,8)}' is not an ancestor of '${p.slice(0,8)}'.`);let b=g(y),x=I(await c(e,{format:`%H%n%an%n%ae%n%aI%n%B%n---COMMIT-END---`,range:`${h}..${p}`,reverse:!0})),S=await F(e,x);for(let e of b){let{author:t,coAuthors:n}=_(S,e);t!=null&&(e.author=t),n.length>0&&(e.coAuthors=n)}let C=V(y,m,p,h,await H(e,s));return{hunks:b,commits:x.map(e=>({sha:e.sha,message:e.message})),snapshot:C,combinedDiff:y}}async function R(e,t,r){let i=w(C(),`compose-workdir-${b()}`),c={GIT_INDEX_FILE:i};try{if(await d(e,t,{indexEnv:c}),r.includeStaged){let r=await a(e,t,await h(e));r.trim()&&await n(e,r,{indexEnv:c})}if(r.includeUnstaged){let t=await h(e),r=w(C(),`compose-workdir-u-${b()}`),i={GIT_INDEX_FILE:r};try{x(w(await o(e),`index`),r),await m(e,{trackedOnly:!0,indexEnv:i});let s=await a(e,t,await h(e,{indexEnv:i}));s.trim()&&await n(e,s,{indexEnv:c})}finally{try{S(r)}catch{}}}if(r.includeUntracked){let t=await s(e);t.length>0&&(await m(e,{intentToAdd:!0,paths:t,indexEnv:c}),await m(e,{paths:t,indexEnv:c}))}return{finalTreeSha:await h(e,{indexEnv:c})}}finally{try{S(i)}catch{}}}async function z(e,t,n){let r=await B(e,t),i=await l(e,await B(e,n),r),o=await f(e,r),s=(await c(e,{format:`%H`,range:`${i}..${r}`,reverse:!0})).trim().split(`
4
4
  `).filter(Boolean),u=[];for(let t of s){let[n,r,i]=await Promise.all([c(e,{format:`%B`,sha:t}).then(e=>e.trim()),a(e,`${t}^`,t),a(e,`${t}^`,t,{binary:!1,nameOnly:!0})]),o=i.trim().split(`
5
- `).filter(Boolean);u.push({sha:t,message:n,diff:r,filesChanged:o})}return{commits:u,mergeBaseSha:i,branchSha:o}}async function B(t,n){if(await p(t,`refs/heads/${n}`)||await p(t,n))return n;if(await p(t,`origin/${n}`))return await r(t,n,`origin/${n}`),n;throw new e(`BRANCH_NOT_FOUND`,`Branch '${n}' not found locally or on origin. Run 'git fetch' if the branch exists on the remote.`)}function V(e,t,n,r,i){return{diffHash:_(e),headSha:t,branchSha:n,baseSha:r,sourceHashes:i}}async function H(e,t){if(!e.exec)return;let n=t.includeStaged??!0,r={};if(n)try{r.staged=_(await e.exec([`diff`,`--cached`,`--binary`,`HEAD`]))}catch{}if(t.includeUnstaged)try{r.unstaged=_(await e.exec([`diff`,`--binary`]))}catch{}if(t.includeUntracked)try{let t=(await e.exec([`ls-files`,`--others`,`--exclude-standard`])).trim().split(`
6
- `).filter(Boolean);if(t.length>0){let n=[];for(let r of t)try{let t=await e.exec([`hash-object`,`--`,r]);n.push(`${r}:${t.trim()}`)}catch{}r.untracked=_(n.join(`
7
- `))}else r.untracked=_(``)}catch{}return Object.keys(r).length>0?r:void 0}export{j as collectBranchDiffs,P as collectCombinedDiffRange,M as collectCommitRangeDiffs,L as collectCommitRangeWithWorkdir,z as collectCommitsForSplit,T as collectFromSource,H as collectSourceHashes,R as collectWorkdirPortionForRange,E as collectWorkingDiffs};
5
+ `).filter(Boolean);u.push({sha:t,message:n,diff:r,filesChanged:o})}return{commits:u,mergeBaseSha:i,branchSha:o}}async function B(t,n){if(await p(t,`refs/heads/${n}`)||await p(t,n))return n;if(await p(t,`origin/${n}`))return await r(t,n,`origin/${n}`),n;throw new e(`BRANCH_NOT_FOUND`,`Branch '${n}' not found locally or on origin. Run 'git fetch' if the branch exists on the remote.`)}function V(e,t,n,r,i){return{diffHash:v(e),headSha:t,branchSha:n,baseSha:r,sourceHashes:i}}async function H(e,t){if(!e.exec)return;let n=t.includeStaged??!0,r={};if(n)try{r.staged=v(await e.exec([`diff`,`--cached`,`--binary`,`HEAD`]))}catch{}if(t.includeUnstaged)try{r.unstaged=v(await e.exec([`diff`,`--binary`]))}catch{}if(t.includeUntracked)try{let t=(await e.exec([`ls-files`,`--others`,`--exclude-standard`])).trim().split(`
6
+ `).filter(Boolean);if(t.length>0){let n=[];for(let r of t)try{let t=await e.exec([`hash-object`,`--`,r]);n.push(`${r}:${t.trim()}`)}catch{}r.untracked=v(n.join(`
7
+ `))}else r.untracked=v(``)}catch{}return Object.keys(r).length>0?r:void 0}export{j as collectBranchDiffs,P as collectCombinedDiffRange,M as collectCommitRangeDiffs,L as collectCommitRangeWithWorkdir,z as collectCommitsForSplit,T as collectFromSource,H as collectSourceHashes,R as collectWorkdirPortionForRange,E as collectWorkingDiffs};
@@ -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`]);async function l(e){let{git:t,commitLimit:n=30,branchLimit:r=15}=e,i=[],a=e.author??await T(t),[o,s,c]=await Promise.all([u(t,i),f(t,n,a),p(t,r)]),l=m(s,o),d=h(c);return{commits:l,branches:d,sources:i,promptFragment:b(l,d)}}async function u(e,t){let n={hasConventionalCommitsDep:!1,hasCommitizen:!1},r=await C(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 C(e,r);if(i){t.push(r);let e=d(i);e.length>0&&(n.commitlintTypes=e);break}}for(let r of[`commitlint.config.js`,`commitlint.config.cjs`,`commitlint.config.mjs`])if(await w(e,r)){t.push(r),n.commitlintTypes||(n.hasConventionalCommitsDep=!0);break}let i=await C(e,`.gitmessage`);return i&&(n.gitMessageTemplate=i.trim(),t.push(`.gitmessage`)),n}function d(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 f(e,t,r){try{let i=(await n(e,{format:`%s`,noMerges:!0,limit:t,author:r})).trim().split(`
2
- `).filter(Boolean);return r&&i.length<5?(await n(e,{format:`%s`,noMerges:!0,limit:t})).trim().split(`
3
- `).filter(Boolean):i}catch{return[]}}async function p(t,n){try{return(await e(t,{format:`%(refname:short)`,prefix:`refs/heads/`,sort:`-committerdate`,limit:n+5})).trim().split(`
4
- `).filter(Boolean).filter(e=>!c.has(e)&&!e.startsWith(`release/`)).slice(0,n)}catch{return[]}}function m(e,t){if(e.length===0)return{format:t.hasConventionalCommitsDep||t.hasCommitizen?`conventional-commits`:`unknown`,prefixes:t.commitlintTypes,samples:[]};let n=e.filter(e=>i.test(e)),r=n.length/e.length>.6||t.hasConventionalCommitsDep||t.hasCommitizen,a,o;if(r){let e=new Set,r=0;for(let t of n){let n=t.match(i);n&&(e.add(n[1]),n[2]&&r++)}a=t.commitlintTypes??[...e].sort(),o=r/Math.max(n.length,1)>.3?`parenthesized`:`none`}let s=g(e,r),c=_(e),l=v(e,r?a:void 0);return{format:r?`conventional-commits`:`plain`,prefixes:a,scopeStyle:o,casing:s,issueRefPattern:c,samples:l}}function h(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:y(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 g(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 _(e){for(let{re:t,label:n}of a)if(e.filter(e=>t.test(e)).length/e.length>.2)return n}function v(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 y(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 b(e,t){let n=[`## Repository conventions
5
- `];return n.push(x(e)),n.push(S(t)),n.push(`Match these conventions in your output.`),n.join(`
6
- `)}function x(e){if(e.format===`unknown`&&e.samples.length===0)return`Commit messages: No clear convention detected. Use clear, concise messages.
7
- `;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(`Examples:`);for(let n of e.samples)t.push(`- ${n}`)}return`${t.join(`
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 C(e,t){try{return await r(e,`HEAD`,t)}catch{return null}}async function w(e,t){try{return await r(e,`HEAD`,t),!0}catch{return!1}}async function T(e){let n=await t(e,`user.email`);if(n)return n;let r=await t(e,`user.name`);if(r)return r}export{l as extractConventions};
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";
@@ -18,6 +18,7 @@ import { ComposeContext, ComposeSource, ComposeTarget, OverrideSafetyFailures }
18
18
  import { StashConflict } from "./git/types.js";
19
19
  import { ComposeBranchPlan } from "./git/apply-target.js";
20
20
  import { InteriorRefViolation, detectInteriorRefs } from "./git/safety-range.js";
21
+ import { parseUnifiedDiff } from "./git/utils/hunk-parser.js";
21
22
  import { analyze } from "./tasks/analyze/analyze.js";
22
23
  import { ComposeGroupInput, ComposeGroupOutput } from "./tasks/compose-group/types.js";
23
24
  import { composeGroup } from "./tasks/compose-group/compose-group.js";
@@ -25,9 +26,11 @@ import { group } from "./tasks/group/group.js";
25
26
  import { order } from "./tasks/order/order.js";
26
27
  import { partition } from "./tasks/partition/partition.js";
27
28
  import { quickCompose } from "./tasks/quick-compose/quick-compose.js";
29
+ import { DEFAULT_INPUT_TOKEN_BUDGET } from "./tasks/utils/budget.js";
30
+ import { isNoiseHunk, withDefaultNoiseRedaction } from "./tasks/utils/noise.js";
28
31
  import { ApplyComposePlanInput, ApplyComposePlanResult, ComposeApplyPlan, ComposeDepth, ComposeDiffStats, ComposeInput, ComposePlan, ComposePlanInput, ComposePlanResult, ComposeProgressEvent, ComposeResult, ComposeStrategy, applyComposePlan, compose, composePlan, defaultPhaseMessages } from "./workflows/compose.js";
29
32
  import { RefinePlanInput, RefinePlanResult, RefineProgressEvent, refinePlan } from "./workflows/refine.js";
30
33
  import { SplitInput, SplitPlan, SplitProgressEvent, SplitResult, split } from "./workflows/split.js";
31
34
  import { UndoComposeInput, ValidateUndoComposeInput, listUndoIds, undoCompose, validateUndoCompose } from "./workflows/undo.js";
32
35
  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 };
36
+ 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, parseUnifiedDiff, 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 n}from"./errors/task.js";import{ComposeWorkflowError as r,ComposeWorkflowInputError as i}from"./errors/workflow.js";import{ComposeGitPortMissingOpError as a}from"./ports/git.js";import{detectInteriorRefs as o}from"./git/safety-range.js";import{estimatedCharactersPerToken as s}from"./tasks/ai-loop.js";import{REDACTED_HUNK_CONTENT as c}from"./tasks/utils/redact.js";import{analyze as l}from"./tasks/analyze/analyze.js";import{composeGroup as u}from"./tasks/compose-group/compose-group.js";import{group as d}from"./tasks/group/group.js";import{order as f}from"./tasks/order/order.js";import{partition as p}from"./tasks/partition/partition.js";import{quickCompose as m}from"./tasks/quick-compose/quick-compose.js";import{applyComposePlan as h,compose as g,composePlan as _,defaultPhaseMessages as v}from"./workflows/compose.js";import{refinePlan as y}from"./workflows/refine.js";import{split as b}from"./workflows/split.js";import{listUndoIds as x,undoCompose as S,validateUndoCompose as C}from"./workflows/undo.js";export{e as AIError,a as ComposeGitPortMissingOpError,r as ComposeWorkflowError,i as ComposeWorkflowInputError,t as GitError,c as REDACTED_HUNK_CONTENT,n as TaskInputError,l as analyze,h as applyComposePlan,g as compose,u as composeGroup,_ as composePlan,v as defaultPhaseMessages,o as detectInteriorRefs,s as estimatedCharactersPerToken,d as group,x as listUndoIds,f as order,p as partition,m as quickCompose,y as refinePlan,b as split,S as undoCompose,C as validateUndoCompose};
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{parseUnifiedDiff as c}from"./git/utils/hunk-parser.js";import{estimatedCharactersPerToken as l}from"./tasks/ai-loop.js";import{REDACTED_HUNK_CONTENT as u}from"./tasks/utils/redact.js";import{DEFAULT_INPUT_TOKEN_BUDGET as d}from"./tasks/utils/budget.js";import{analyze as f}from"./tasks/analyze/analyze.js";import{composeGroup as p}from"./tasks/compose-group/compose-group.js";import{group as m}from"./tasks/group/group.js";import{order as h}from"./tasks/order/order.js";import{partition as g}from"./tasks/partition/partition.js";import{quickCompose as _}from"./tasks/quick-compose/quick-compose.js";import{isNoiseHunk as v,withDefaultNoiseRedaction as y}from"./tasks/utils/noise.js";import{applyComposePlan as b,compose as x,composePlan as S,defaultPhaseMessages as C}from"./workflows/compose.js";import{refinePlan as w}from"./workflows/refine.js";import{split as T}from"./workflows/split.js";import{listUndoIds as E,undoCompose as D,validateUndoCompose as O}from"./workflows/undo.js";export{e as AIError,o as ComposeGitPortMissingOpError,i as ComposeWorkflowError,a as ComposeWorkflowInputError,d as DEFAULT_INPUT_TOKEN_BUDGET,n as DiffTooLargeError,t as GitError,u as REDACTED_HUNK_CONTENT,r as TaskInputError,f as analyze,b as applyComposePlan,x as compose,p as composeGroup,S as composePlan,C as defaultPhaseMessages,s as detectInteriorRefs,l as estimatedCharactersPerToken,m as group,v as isNoiseHunk,E as listUndoIds,h as order,c as parseUnifiedDiff,g as partition,_ as quickCompose,w as refinePlan,T as split,D as undoCompose,O as validateUndoCompose,y 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),l=s.length,u=await t({model:o.model,systemPrompt:o.session?void 0:o.promptOverride?.system??r(),userPrompt:c,session:o.session,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>a(e,l),taskName:`analyze`}),d=o.session?.completedSteps??{},f={...u.session,completedSteps:{...d,analyze:u.result}};return{result:u.result,session:f,usage:u.usage,attempts:u.attempts,durationMs:u.durationMs,callIds:u.callIds}}export{o as analyze};
1
+ 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{hunkContentForPrompt as e}from"../utils/redact.js";import{analysisResultSchema as t}from"./schema.js";import{z as n}from"zod";const r=JSON.stringify(n.toJSONSchema(t),null,2);function i(){return`You are an advanced AI code analysis assistant. Your task is to deeply analyze a set of code changes (provided as diff hunks) and produce a structured understanding of what the changes accomplish, what concerns they address, and how hunks relate to each other.
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
- ${r}
28
+ ${n}
29
29
  \`\`\`
30
30
 
31
- Return only the <output> tag and no other text.`}function a(t){let{hunks:n,commitMessages:r,commitOrder:i,branchName:a,baseBranch:o,issueUrl:s,pullRequestUrl:c,instructions:l,redactHunkContent:u}=t,d=`Analyze the following code changes:\n\n<hunks>\n${JSON.stringify(n.map(t=>({index:t.index,fileName:t.fileName,hunkHeader:t.hunkHeader,content:e(t,u),...t.isBinary?{isBinary:!0}:{},...t.isRename?{isRename:!0}:{},...t.originalFileName?{originalFileName:t.originalFileName}:{}})))}\n</hunks>`,f=[];if(a&&f.push(`Branch: ${a}`),o&&f.push(`Base branch: ${o}`),s&&f.push(`Issue: ${s}`),c&&f.push(`Pull request: ${c}`),f.length>0&&(d+=`\n\n<context>\n${f.join(`
32
- `)}\n</context>`),r?.length){let e=r.map((e,t)=>({index:t,sha:i?.[t],message:e}));d+=`\n\n<commit_messages>\n${JSON.stringify(e)}\n</commit_messages>`}return l&&(d+=`\n\nCarefully follow these additional instructions, but do not deviate from the output structure:\n${l}`),d+=`
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{i as buildAnalyzeSystemPrompt,a as buildAnalyzeUserPrompt};
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 analysisResultSchema: z.ZodObject<{
5
- title: z.ZodString;
6
- summary: z.ZodString;
7
- concerns: z.ZodArray<z.ZodObject<{
8
- id: z.ZodString;
9
- name: z.ZodString;
10
- description: z.ZodString;
11
- hunkIndices: z.ZodArray<z.ZodNumber>;
12
- }, z.core.$strip>>;
13
- dependencies: z.ZodDefault<z.ZodArray<z.ZodObject<{
14
- from: z.ZodString;
15
- to: z.ZodString;
16
- reason: z.ZodString;
17
- }, z.core.$strip>>>;
18
- hunkAnnotations: z.ZodArray<z.ZodObject<{
19
- hunkIndex: z.ZodNumber;
20
- concernId: z.ZodString;
21
- purpose: z.ZodString;
22
- }, z.core.$strip>>;
23
- orderingConstraints: z.ZodDefault<z.ZodArray<z.ZodObject<{
24
- provider: z.ZodNumber;
25
- consumer: z.ZodNumber;
26
- kind: z.ZodEnum<{
27
- "file-reference": "file-reference";
28
- import: "import";
29
- "function-call": "function-call";
30
- "type-reference": "type-reference";
31
- "config-reference": "config-reference";
32
- other: "other";
33
- }>;
34
- reason: z.ZodString;
35
- }, z.core.$strip>>>;
36
- }, z.core.$strip>;
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 { analysisResultSchema };
129
+ export { analysisResultJsonSchema };
@@ -1 +1 @@
1
- import{z as e}from"zod";const t=e.enum([`file-reference`,`import`,`function-call`,`type-reference`,`config-reference`,`other`]),n=e.object({title:e.string().min(1).max(72).describe(`Short PR-style title for the changes (under 72 chars, imperative mood, e.g. 'Add user authentication and session management')`),summary:e.string().min(1).describe(`High-level summary of all changes`),concerns:e.array(e.object({id:e.string().describe(`Unique concern ID (e.g. c1, c2)`),name:e.string().describe(`Short concern name`),description:e.string().describe(`What this concern accomplishes`),hunkIndices:e.array(e.number()).describe(`Indices of hunks belonging to this concern`)})).min(1).describe(`Distinct themes, features, or purposes within the changes`),dependencies:e.array(e.object({from:e.string().describe(`Concern ID`),to:e.string().describe(`Concern ID that 'from' depends on`),reason:e.string().describe(`Why the dependency exists`)})).default([]).describe(`Relationships between concerns`),hunkAnnotations:e.array(e.object({hunkIndex:e.number().describe(`Index of the hunk`),concernId:e.string().describe(`Concern this hunk belongs to`),purpose:e.string().describe(`What this hunk does in context of its concern`)})).describe(`Per-hunk concern assignment and purpose`),orderingConstraints:e.array(e.object({provider:e.number().describe(`Hunk index that defines/creates something`),consumer:e.number().describe(`Hunk index that references/uses it`),kind:t.describe(`Type of provider/consumer relationship`),reason:e.string().describe(`Brief explanation of the relationship`)})).default([]).describe(`Provider/consumer ordering between hunks`)});export{n as analysisResultSchema};
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{analysisResultSchema as t}from"./schema.js";function n(n,a){let o=null;try{if(o=e(n),!o)return r([`No <output> tag found in response`],i());let s=JSON.parse(o),c=t.safeParse(s);if(!c.success)return r(c.error.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`),i());let l=c.data,u=new Set(l.hunkAnnotations.map(e=>e.hunkIndex)),d=[...new Set(Array.from({length:a},(e,t)=>t))].filter(e=>!u.has(e));return d.length>0?r([`Missing annotations for hunks: ${d.join(`, `)}`],`Your response is missing annotations for some hunks. Every hunk must be annotated.\n\nMissing: ${d.join(`, `)}\n\nPlease provide a corrected response.`):{valid:!0,result:{title:l.title,summary:l.summary,concerns:l.concerns,dependencies:l.dependencies,hunkAnnotations:l.hunkAnnotations,orderingConstraints:l.orderingConstraints}}}catch(e){let t=e instanceof Error?e.message:String(e),a=(o??n).slice(-300);return r([`Failed to parse response as valid JSON: ${t}`,`Response tail: ${a}`],i())}}function r(e,t){return{valid:!1,errors:e,retryFeedback:t}}function i(){return`Your response has an invalid structure. Please return JSON inside <output> tags with this structure:
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{n as validateAnalyzeResponse};
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),d=await t({model:o.model,systemPrompt:l,userPrompt:u,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>r(e,s.length,c,o.existingBranchNames),taskName:`compose-group`}),f=o.session?.completedSteps??{},p={...d.session,completedSteps:{...f,composeGroup:d.result}};return{result:d.result,session:p,usage:d.usage,attempts:d.attempts,durationMs:d.durationMs,callIds:d.callIds}}export{o as composeGroup};
1
+ 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 };