@gitkraken/compose-tools 0.1.0 → 0.2.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 +32 -1
- package/README.md +80 -14
- package/dist/errors/workflow.d.ts +16 -6
- package/dist/errors/workflow.js +1 -1
- package/dist/git/apply-branches.d.ts +0 -2
- package/dist/git/apply-branches.js +1 -1
- package/dist/git/apply-target.d.ts +13 -2
- package/dist/git/apply-target.js +1 -1
- package/dist/git/commit-chain.d.ts +27 -7
- package/dist/git/commit-chain.js +7 -5
- package/dist/git/diff-collector.d.ts +37 -10
- package/dist/git/diff-collector.js +7 -7
- package/dist/git/port-dispatch.d.ts +9 -3
- package/dist/git/port-dispatch.js +3 -3
- package/dist/git/require-exec.js +1 -1
- package/dist/git/safety.d.ts +3 -3
- package/dist/git/safety.js +1 -1
- package/dist/git/types.d.ts +6 -1
- package/dist/git/utils/conventions.d.ts +0 -1
- package/dist/git/utils/conventions.js +4 -4
- package/dist/git/utils/temp-index.d.ts +1 -1
- package/dist/git/utils/temp-index.js +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -1
- package/dist/ports/git.d.ts +27 -7
- package/dist/tasks/ai-loop.d.ts +9 -15
- package/dist/tasks/ai-loop.js +2 -2
- package/dist/tasks/analyze/analyze.js +1 -1
- package/dist/tasks/analyze/types.d.ts +3 -1
- package/dist/tasks/analyze/validator.js +1 -1
- package/dist/tasks/compose-group/compose-group.js +1 -1
- package/dist/tasks/compose-group/prompt.js +27 -21
- package/dist/tasks/compose-group/types.d.ts +3 -1
- package/dist/tasks/group/group.js +1 -1
- package/dist/tasks/group/prompt.js +27 -21
- package/dist/tasks/group/types.d.ts +3 -1
- package/dist/tasks/group/validator.js +9 -4
- package/dist/tasks/order/order.js +1 -1
- package/dist/tasks/order/types.d.ts +3 -1
- package/dist/tasks/order/validator.js +1 -1
- package/dist/tasks/partition/partition.js +1 -1
- package/dist/tasks/partition/types.d.ts +3 -1
- package/dist/tasks/partition/validator.js +1 -1
- package/dist/tasks/quick-compose/prompt.js +14 -5
- package/dist/tasks/quick-compose/quick-compose.js +1 -1
- package/dist/tasks/quick-compose/types.d.ts +3 -1
- package/dist/tasks/quick-compose/validator.js +10 -2
- package/dist/utils/cancellation.d.ts +26 -0
- package/dist/utils/cancellation.js +1 -0
- package/dist/workflows/compose.d.ts +78 -10
- package/dist/workflows/compose.js +2 -2
- package/dist/workflows/split.d.ts +11 -3
- package/dist/workflows/split.js +3 -3
- package/dist/workflows/types.d.ts +5 -0
- package/dist/workflows/undo.d.ts +18 -11
- package/dist/workflows/undo.js +1 -1
- package/package.json +2 -2
- package/dist/cancellation.d.ts +0 -20
- package/dist/cancellation.js +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{GitError as e}from"../errors/git.js";import{
|
|
1
|
+
import{GitError as e}from"../errors/git.js";import{ComposeWorkflowInputError as t}from"../errors/workflow.js";import{applyPatchToIndex as n,createBranch as r,diffTree as i,gitDir as a,listUntracked as o,log as s,mergeBase as c,readTree as l,revParse as u,revParseVerify as d,stageAll as f,writeTree as p}from"./port-dispatch.js";import{sha256 as m}from"./utils/crypto.js";import{parseUnifiedDiff as h}from"./utils/hunk-parser.js";import{withUntrackedInTempIndex as g}from"./utils/temp-index.js";import{randomUUID as _}from"node:crypto";import{copyFileSync as v,unlinkSync as y}from"node:fs";import{tmpdir as b}from"node:os";import{join as x}from"node:path";async function S(e,t){if(t.type===`workdir`){let n=!t.stagedOnly&&!t.noUntracked;return{...await C(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 j(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 k(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 O(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 C(e,t){let n=t.includeUnstaged||t.includeUntracked;return t.includeUntracked?E(e,n):w(e,n)}async function w(e,t){let n=await u(e,`HEAD`),r=await T(e,n,t);return{hunks:h(r,`staged`),snapshot:F(r,n,void 0,void 0,await I(e,{includeUnstaged:t,includeUntracked:!1})),combinedDiff:r}}async function T(e,t,n){if(!n)return i(e,t,await p(e));let r=x(await a(e),`index`),o=x(b(),`compose-snapshot-idx-${_()}`);try{v(r,o);let n={GIT_INDEX_FILE:o};return await f(e,{trackedOnly:!0,indexEnv:n}),i(e,t,await p(e,{indexEnv:n}))}finally{try{y(o)}catch{}}}async function E(e,t){return g(e,async n=>{await D(e,n,t);let r=await p(e,{indexEnv:n}),a=await u(e,`HEAD`),o=await i(e,a,r);return{hunks:h(o,`staged`),snapshot:F(o,a,void 0,void 0,await I(e,{includeUnstaged:t,includeUntracked:!0})),combinedDiff:o}})}async function D(e,t,n){n&&await f(e,{trackedOnly:!0,indexEnv:t}),await f(e,{indexEnv:t})}async function O(e,t,n){let r=await P(e,t),a=await c(e,await P(e,n),r),o=await u(e,r),l=await i(e,a,r);return{hunks:h(l,`branch`),commits:L(await s(e,{format:`%H%n%s%n---END---`,range:`${a}..${r}`})),snapshot:F(l,await u(e,`HEAD`),o,a),combinedDiff:l}}async function k(t,n,r,a){await P(t,n);let o=await u(t,r),c=await u(t,a),l=A(await s(t,{format:`%H%n%an%n%ae%n%aI%n%B%n---COMMIT-END---`,range:`${o}..${c}`,reverse:!0}));if(l.length===0)throw new e(`NO_CHANGES`,`No commits in range '${r}..${a}' on branch '${n}'.`);let d=[],f=0;for(let e of l){let n=h(await i(t,`${e.sha}^`,e.sha),`branch`,f);for(let t of n)t.sourceCommitSha=e.sha,t.author={name:e.authorName,email:e.authorEmail,date:e.authorDate};d.push(...n),f+=n.length}let p=await i(t,o,c),m=F(p,await u(t,`HEAD`),c,o);return{hunks:d,commits:l.map(e=>({sha:e.sha,message:e.message})),snapshot:m,combinedDiff:p}}function A(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
|
|
4
|
-
`).filter(Boolean),
|
|
5
|
-
`).filter(Boolean);
|
|
6
|
-
`).filter(Boolean);if(t.length>0){let
|
|
7
|
-
`))}else r.untracked=
|
|
8
|
-
`);n.length>=2&&t.push({sha:n[0],message:n[1]})}return t}export{
|
|
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 j(e,n,r,a,o){let s=await u(e,await P(e,n)),c=await u(e,r),l=await u(e,a),d=await u(e,`HEAD`);if(l!==s)throw new t(`commit-range with includeWorkdir requires 'to' to equal branch tip. Branch '${n}' tip is ${s}; got 'to'=${l}.`);if(d!==l)throw new t(`commit-range with includeWorkdir requires HEAD to equal 'to'. HEAD=${d}, 'to'=${l}.`);let f=await k(e,n,r,a);if(!(o.includeStaged||o.includeUnstaged||o.includeUntracked))return f;let{workdirHunks:p,finalTreeSha:m}=await M(e,d,o),h=p.map((e,t)=>({...e,index:f.hunks.length+t})),g=await i(e,c,m),_=F(g,d,l,c,await I(e,o));return{hunks:[...f.hunks,...h],commits:f.commits,snapshot:_,combinedDiff:g}}async function M(e,t,r){let s=x(b(),`compose-workdir-${_()}`),c={GIT_INDEX_FILE:s};try{if(await l(e,t,{indexEnv:c}),r.includeStaged){let r=await i(e,t,await p(e));r.trim()&&await n(e,r,{indexEnv:c})}if(r.includeUnstaged){let t=await p(e),r=x(b(),`compose-workdir-u-${_()}`),o={GIT_INDEX_FILE:r};try{v(x(await a(e),`index`),r),await f(e,{trackedOnly:!0,indexEnv:o});let s=await i(e,t,await p(e,{indexEnv:o}));s.trim()&&await n(e,s,{indexEnv:c})}finally{try{y(r)}catch{}}}if(r.includeUntracked){let t=await o(e);t.length>0&&(await f(e,{intentToAdd:!0,paths:t,indexEnv:c}),await f(e,{paths:t,indexEnv:c}))}let s=await p(e,{indexEnv:c});return{workdirHunks:h(await i(e,t,s),`staged`,0),finalTreeSha:s}}finally{try{y(s)}catch{}}}async function N(e,t,n){let r=await P(e,t),a=await c(e,await P(e,n),r),o=await u(e,r),l=(await s(e,{format:`%H`,range:`${a}..${r}`,reverse:!0})).trim().split(`
|
|
4
|
+
`).filter(Boolean),d=[];for(let t of l){let[n,r,a]=await Promise.all([s(e,{format:`%B`,sha:t}).then(e=>e.trim()),i(e,`${t}^`,t),i(e,`${t}^`,t,{binary:!1,nameOnly:!0})]),o=a.trim().split(`
|
|
5
|
+
`).filter(Boolean);d.push({sha:t,message:n,diff:r,filesChanged:o})}return{commits:d,mergeBaseSha:a,branchSha:o}}async function P(t,n){if(await d(t,`refs/heads/${n}`)||await d(t,n))return n;if(await d(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 F(e,t,n,r,i){return{diffHash:m(e),headSha:t,branchSha:n,baseSha:r,sourceHashes:i}}async function I(e,t){if(!e.exec)return;let n=t.includeStaged??!0,r={};if(n)try{r.staged=m(await e.exec([`diff`,`--cached`,`--binary`,`HEAD`]))}catch{}if(t.includeUnstaged)try{r.unstaged=m(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=m(n.join(`
|
|
7
|
+
`))}else r.untracked=m(``)}catch{}return Object.keys(r).length>0?r:void 0}function L(e){let t=[],n=e.split(`---END---`).filter(e=>e.trim());for(let e of n){let n=e.trim().split(`
|
|
8
|
+
`);n.length>=2&&t.push({sha:n[0],message:n[1]})}return t}export{O as collectBranchDiffs,k as collectCommitRangeDiffs,j as collectCommitRangeWithWorkdir,N as collectCommitsForSplit,S as collectFromSource,I as collectSourceHashes,M as collectWorkdirPortionForRange,C as collectWorkingDiffs};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApplyPatchOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitPort, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, IndexScopedOptions, LogOptions, OpOptions, StageAllOptions, StashPushOptions, UpdateRefOptions } from "../ports/git.js";
|
|
1
|
+
import { ApplyPatchOptions, ApplyPatchToWorkdirOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitPort, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, IndexScopedOptions, LogOptions, OpOptions, StageAllOptions, StashPopOptions, StashPushOptions, UpdateRefOptions } from "../ports/git.js";
|
|
2
2
|
|
|
3
3
|
//#region src/git/port-dispatch.d.ts
|
|
4
4
|
declare function revParse(git: ComposeGitPort, ref: string, opts?: OpOptions): Promise<string>;
|
|
@@ -18,6 +18,7 @@ declare function showFile(git: ComposeGitPort, ref: string, path: string, opts?:
|
|
|
18
18
|
declare function getConfig(git: ComposeGitPort, key: string, opts?: OpOptions): Promise<string | null>;
|
|
19
19
|
declare function commitTree(git: ComposeGitPort, tree: string, parents: string[], message: string, opts?: CommitTreeOptions): Promise<string>;
|
|
20
20
|
declare function applyPatchToIndex(git: ComposeGitPort, patch: string, opts?: ApplyPatchOptions): Promise<void>;
|
|
21
|
+
declare function applyPatchToWorkdir(git: ComposeGitPort, patch: string, opts?: ApplyPatchToWorkdirOptions): Promise<void>;
|
|
21
22
|
declare function mergeTree(git: ComposeGitPort, a: string, b: string, opts?: OpOptions): Promise<string>;
|
|
22
23
|
declare function createBranch(git: ComposeGitPort, name: string, startPoint: string, opts?: OpOptions): Promise<void>;
|
|
23
24
|
declare function deleteBranch(git: ComposeGitPort, name: string, opts?: DeleteBranchOptions): Promise<void>;
|
|
@@ -27,8 +28,13 @@ declare function reset(git: ComposeGitPort, ref: string, mode: 'soft' | 'mixed'
|
|
|
27
28
|
declare function cleanWorkdir(git: ComposeGitPort, opts?: CleanOptions): Promise<void>;
|
|
28
29
|
declare function discardWorkdirChanges(git: ComposeGitPort, paths?: string[], opts?: OpOptions): Promise<void>;
|
|
29
30
|
declare function stashPush(git: ComposeGitPort, opts?: StashPushOptions): Promise<void>;
|
|
30
|
-
declare
|
|
31
|
+
declare const localeNeutralEnv: {
|
|
32
|
+
LANG: string;
|
|
33
|
+
LC_ALL: string;
|
|
34
|
+
};
|
|
35
|
+
declare function stashPop(git: ComposeGitPort, opts?: StashPopOptions): Promise<void>;
|
|
36
|
+
declare function stashDrop(git: ComposeGitPort, stashRef: string, opts?: OpOptions): Promise<void>;
|
|
31
37
|
declare function cherryPick(git: ComposeGitPort, sha: string, opts?: OpOptions): Promise<void>;
|
|
32
38
|
declare function cherryPickAbort(git: ComposeGitPort, opts?: OpOptions): Promise<void>;
|
|
33
39
|
//#endregion
|
|
34
|
-
export { applyPatchToIndex, checkout, cherryPick, cherryPickAbort, cleanWorkdir, commitTree, createBranch, currentBranch, deleteBranch, diffTree, discardWorkdirChanges, forEachRef, getConfig, gitDir, listUntracked, log, mergeBase, mergeTree, readTree, reset, revParse, revParseVerify, showFile, stageAll, stashPop, stashPush, statusPorcelain, updateRef, writeTree };
|
|
40
|
+
export { applyPatchToIndex, applyPatchToWorkdir, checkout, cherryPick, cherryPickAbort, cleanWorkdir, commitTree, createBranch, currentBranch, deleteBranch, diffTree, discardWorkdirChanges, forEachRef, getConfig, gitDir, listUntracked, localeNeutralEnv, log, mergeBase, mergeTree, readTree, reset, revParse, revParseVerify, showFile, stageAll, stashDrop, stashPop, stashPush, statusPorcelain, updateRef, writeTree };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{ComposeGitPortMissingOpError as e}from"../ports/git.js";function t(t,n){throw new e(t,n)}async function n(e,n,r){return e.revParse?e.revParse(n,r):e.exec?(await e.exec([`rev-parse`,n]
|
|
2
|
-
`).filter(Boolean):t(`listUntracked`)}async function h(e,n,r,i){return e.showFile?e.showFile(n,r,i):e.exec?e.exec([`show`,`${n}:${r}`]
|
|
3
|
-
`)[0]:t(`mergeTree`)}async function
|
|
1
|
+
import{ComposeGitPortMissingOpError as e}from"../ports/git.js";function t(t,n){throw new e(t,n)}async function n(e,n,r){return e.revParse?e.revParse(n,r):e.exec?(await e.exec([`rev-parse`,n])).trim():t(`revParse`)}async function r(e,n,r){if(e.revParseVerify)return e.revParseVerify(n,r);if(e.exec)try{return(await e.exec([`rev-parse`,`--verify`,n])).trim()}catch{return null}return t(`revParseVerify`)}async function i(e,n){return e.gitDir?e.gitDir(n):e.exec?(await e.exec([`rev-parse`,`--absolute-git-dir`])).trim():t(`gitDir`)}async function a(e,n){if(e.currentBranch)return e.currentBranch(n);if(e.exec)try{return(await e.exec([`symbolic-ref`,`--short`,`HEAD`])).trim()||null}catch{return null}return t(`currentBranch`)}async function o(e,n,r,i){return e.mergeBase?e.mergeBase(n,r,i):e.exec?(await e.exec([`merge-base`,n,r])).trim():t(`mergeBase`)}async function s(e,n,r,i){if(e.diffTree)return e.diffTree(n,r,i);if(e.exec){let t=[`diff`];return i?.binary!==!1&&t.push(`--binary`),i?.nameOnly&&t.push(`--name-only`),t.push(n,r),e.exec(t)}return t(`diffTree`)}async function c(e,n){if(e.log)return e.log(n);if(e.exec){let t=[`log`,`--format=${n.format}`];return n.noMerges&&t.push(`--no-merges`),n.reverse&&t.push(`--reverse`),n.limit!=null&&t.push(`-n`,String(n.limit)),n.author&&t.push(`--author=${n.author}`),n.sha?t.push(`-1`,n.sha):n.range&&t.push(n.range),e.exec(t)}return t(`log`)}async function l(e,n){if(e.forEachRef)return e.forEachRef(n);if(e.exec){let t=[`for-each-ref`,`--format=${n.format}`];return n.sort&&t.push(`--sort=${n.sort}`),n.prefix&&t.push(n.prefix),n.limit!=null&&t.push(`--count=${n.limit}`),e.exec(t)}return t(`forEachRef`)}async function u(e,n){return e.writeTree?e.writeTree(n):e.exec?(await e.exec([`write-tree`],{env:n?.indexEnv})).trim():t(`writeTree`)}async function d(e,n,r){if(e.readTree)return e.readTree(n,r);if(e.exec){await e.exec([`read-tree`,n],{env:r?.indexEnv});return}return t(`readTree`)}async function f(e,n){if(e.stageAll)return e.stageAll(n);if(e.exec){let t=[`add`];n?.intentToAdd?t.push(`-N`):n?.trackedOnly?t.push(`-u`):n?.paths||t.push(`-A`),n?.paths&&t.push(`--`,...n.paths),await e.exec(t,{env:n?.indexEnv});return}return t(`stageAll`)}async function p(e,n){return e.statusPorcelain?e.statusPorcelain(n):e.exec?e.exec([`status`,`--porcelain`]):t(`statusPorcelain`)}async function m(e,n){return e.listUntracked?e.listUntracked(n):e.exec?(await e.exec([`ls-files`,`--others`,`--exclude-standard`])).trim().split(`
|
|
2
|
+
`).filter(Boolean):t(`listUntracked`)}async function h(e,n,r,i){return e.showFile?e.showFile(n,r,i):e.exec?e.exec([`show`,`${n}:${r}`]):t(`showFile`)}async function g(e,n,r){if(e.getConfig)return e.getConfig(n,r);if(e.exec)try{return(await e.exec([`config`,n])).trim()||null}catch{return null}return t(`getConfig`)}async function _(e,n,r,i,a){if(e.commitTree)return e.commitTree(n,r,i,a);if(e.exec){let t=[`commit-tree`,n];for(let e of r)t.push(`-p`,e);t.push(`-m`,i),a?.signing?.enabled&&t.push(a.signing.signingKey?`-S${a.signing.signingKey}`:`-S`);let o={};return a?.author&&(o.GIT_AUTHOR_NAME=a.author.name,o.GIT_AUTHOR_EMAIL=a.author.email,o.GIT_COMMITTER_NAME=a.author.name,o.GIT_COMMITTER_EMAIL=a.author.email,a.author.date&&(o.GIT_AUTHOR_DATE=a.author.date.toISOString())),a?.signing?.gpgProgram&&(o.GIT_GPG_PROGRAM=a.signing.gpgProgram),(await e.exec(t,{env:Object.keys(o).length?o:void 0})).trim()}return t(`commitTree`)}async function v(e,n,r){if(e.applyPatchToIndex)return e.applyPatchToIndex(n,r);if(e.exec){let t=[`apply`,`--cached`];r?.allowBinaryReplacement!==!1&&t.push(`--allow-binary-replacement`),await e.exec(t,{env:r?.indexEnv,stdin:n});return}return t(`applyPatchToIndex`)}async function y(e,n,r){if(e.applyPatchToWorkdir)return e.applyPatchToWorkdir(n,r);if(e.exec){let t=[`apply`];r?.allowBinaryReplacement!==!1&&t.push(`--allow-binary-replacement`),await e.exec(t,{stdin:n});return}return t(`applyPatchToWorkdir`)}async function b(e,n,r,i){return e.mergeTree?e.mergeTree(n,r,i):e.exec?(await e.exec([`merge-tree`,`--write-tree`,n,r])).trim().split(`
|
|
3
|
+
`)[0]:t(`mergeTree`)}async function x(e,n,r,i){if(e.createBranch)return e.createBranch(n,r,i);if(e.exec){await e.exec([`branch`,n,r]);return}return t(`createBranch`)}async function S(e,n,r){if(e.deleteBranch)return e.deleteBranch(n,r);if(e.exec){await e.exec([`branch`,r?.force?`-D`:`-d`,n]);return}return t(`deleteBranch`)}async function C(e,n,r,i){if(e.updateRef)return e.updateRef(n,r,i);if(e.exec){if(i?.remove){await e.exec([`update-ref`,`-d`,n]);return}let t=[`update-ref`,n,r];i?.previousSha&&t.push(i.previousSha),await e.exec(t);return}return t(`updateRef`)}async function w(e,n,r){if(e.checkout)return e.checkout(n,r);if(e.exec){let t=[`checkout`];r?.createBranch&&t.push(`-b`),r?.detach&&t.push(`--detach`),t.push(n),r?.createBranch&&r?.startPoint&&t.push(r.startPoint),await e.exec(t);return}return t(`checkout`)}async function T(e,n,r,i){if(e.reset)return e.reset(n,r,i);if(e.exec){await e.exec([`reset`,`--${r}`,n]);return}return t(`reset`)}async function E(e,n){if(e.cleanWorkdir)return e.cleanWorkdir(n);if(e.exec){let t=`-`;n?.force!==!1&&(t+=`f`),n?.directories!==!1&&(t+=`d`),t===`-`&&(t=`-fd`),await e.exec([`clean`,t]);return}return t(`cleanWorkdir`)}async function D(e,n,r){if(e.discardWorkdirChanges)return e.discardWorkdirChanges(n,r);if(e.exec){let t=[`checkout`,`--`,...n?.length?n:[`.`]];await e.exec(t);return}return t(`discardWorkdirChanges`)}async function O(e,n){if(e.stashPush)return e.stashPush(n);if(e.exec){let t=[`stash`,`push`];n?.includeUntracked&&t.push(`--include-untracked`),n?.message&&t.push(`-m`,n.message),await e.exec(t);return}return t(`stashPush`)}const k={LANG:`C`,LC_ALL:`C`};async function A(e,n){if(e.stashPop)return e.stashPop(n);if(e.exec){let t=[`stash`,`pop`];n?.withIndex&&t.push(`--index`),n?.ref&&t.push(n.ref),await e.exec(t,{env:k});return}return t(`stashPop`)}async function j(e,n,r){if(e.stashDrop)return e.stashDrop(n,r);if(e.exec){await e.exec([`stash`,`drop`,n]);return}return t(`stashDrop`)}async function M(e,n,r){if(e.cherryPick)return e.cherryPick(n,r);if(e.exec){await e.exec([`cherry-pick`,n]);return}return t(`cherryPick`)}async function N(e,n){if(e.cherryPickAbort)return e.cherryPickAbort(n);if(e.exec){await e.exec([`cherry-pick`,`--abort`]);return}return t(`cherryPickAbort`)}export{v as applyPatchToIndex,y as applyPatchToWorkdir,w as checkout,M as cherryPick,N as cherryPickAbort,E as cleanWorkdir,_ as commitTree,x as createBranch,a as currentBranch,S as deleteBranch,s as diffTree,D as discardWorkdirChanges,l as forEachRef,g as getConfig,i as gitDir,m as listUntracked,k as localeNeutralEnv,c as log,o as mergeBase,b as mergeTree,d as readTree,T as reset,n as revParse,r as revParseVerify,h as showFile,f as stageAll,j as stashDrop,A as stashPop,O as stashPush,p as statusPorcelain,C as updateRef,u as writeTree};
|
package/dist/git/require-exec.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{ComposeWorkflowError as e}from"../errors/workflow.js";function t(t,n){if(!t.exec)throw new e(`INTERNAL`,`${n} requires 'exec' on the ComposeGitPort. Provide an exec implementation, or upgrade to a library version that routes this via high-level ops.`);return{exec:t.exec}}export{t as requireExec};
|
package/dist/git/safety.d.ts
CHANGED
|
@@ -3,18 +3,18 @@ import { SafetySnapshot } from "./types.js";
|
|
|
3
3
|
|
|
4
4
|
//#region src/git/safety.d.ts
|
|
5
5
|
declare function verifySnapshot(git: ComposeGitPort, snapshot: SafetySnapshot, options: {
|
|
6
|
+
includeStaged?: boolean;
|
|
6
7
|
includeUnstaged: boolean;
|
|
7
8
|
includeUntracked: boolean;
|
|
8
|
-
signal?: AbortSignal;
|
|
9
9
|
}): Promise<{
|
|
10
10
|
valid: boolean;
|
|
11
11
|
error?: string;
|
|
12
12
|
}>;
|
|
13
|
-
declare function verifyResultingDiff(git: ComposeGitPort, baseSha: string, newHeadSha: string, snapshot: SafetySnapshot
|
|
13
|
+
declare function verifyResultingDiff(git: ComposeGitPort, baseSha: string, newHeadSha: string, snapshot: SafetySnapshot): Promise<{
|
|
14
14
|
valid: boolean;
|
|
15
15
|
error?: string;
|
|
16
16
|
}>;
|
|
17
|
-
declare function verifyMultiBranchDiff(git: ComposeGitPort, baseSha: string, branchHeadShas: string[], snapshot: SafetySnapshot
|
|
17
|
+
declare function verifyMultiBranchDiff(git: ComposeGitPort, baseSha: string, branchHeadShas: string[], snapshot: SafetySnapshot): Promise<{
|
|
18
18
|
valid: boolean;
|
|
19
19
|
error?: string;
|
|
20
20
|
}>;
|
package/dist/git/safety.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{commitTree as e,diffTree as t,gitDir as n,mergeTree as r,revParse as i,stageAll as a,writeTree as o}from"./port-dispatch.js";import{sha256 as s}from"./utils/crypto.js";import{withUntrackedInTempIndex as c}from"./utils/temp-index.js";import{collectSourceHashes as l}from"./diff-collector.js";import{randomUUID as
|
|
1
|
+
import{commitTree as e,diffTree as t,gitDir as n,mergeTree as r,revParse as i,stageAll as a,writeTree as o}from"./port-dispatch.js";import{sha256 as s}from"./utils/crypto.js";import{withUntrackedInTempIndex as c}from"./utils/temp-index.js";import{collectSourceHashes as l,collectWorkdirPortionForRange as u}from"./diff-collector.js";import{randomUUID as d}from"node:crypto";import{copyFileSync as f,unlinkSync as p}from"node:fs";import{tmpdir as m}from"node:os";import{join as h}from"node:path";async function g(e,t,n){return t.branchSha&&t.baseSha?(n.includeStaged??!1)||n.includeUnstaged||n.includeUntracked?v(e,t,n):x(e,t):_(e,t,n)}async function _(e,n,r){let l=await i(e,`HEAD`);if(l!==n.headSha)return{valid:!1,error:`HEAD changed from ${n.headSha} to ${l}`};let u;if(u=r.includeUntracked?await c(e,async n=>(r.includeUnstaged&&await a(e,{trackedOnly:!0,indexEnv:n}),await a(e,{indexEnv:n}),t(e,l,await o(e,{indexEnv:n})))):r.includeUnstaged?await b(e,l):await t(e,l,await o(e)),s(u)!==n.diffHash){let t=await y(e,n,r);return t.length>0?{valid:!1,error:`Working directory ${t.join(` and `)} ${t.length===1?`has`:`have`} changed since the AI analysis`}:n.sourceHashes==null?{valid:!1,error:`Working directory changes have been modified since the AI analysis`}:{valid:!0}}return{valid:!0}}async function v(e,n,r){if(await i(e,n.branchSha)!==n.branchSha)return{valid:!1,error:`Branch has been modified since the AI analysis`};let a=await i(e,`HEAD`);if(a!==n.headSha)return{valid:!1,error:`HEAD changed from ${n.headSha} to ${a}`};let{finalTreeSha:o}=await u(e,a,{includeStaged:r.includeStaged??!0,includeUnstaged:r.includeUnstaged,includeUntracked:r.includeUntracked});if(s(await t(e,n.baseSha,o))!==n.diffHash){let t=await y(e,n,r);return t.length>0?{valid:!1,error:`Working directory ${t.join(` and `)} ${t.length===1?`has`:`have`} changed since the AI analysis`}:n.sourceHashes==null?{valid:!1,error:`Working directory or branch state has drifted since the AI analysis`}:{valid:!0}}return{valid:!0}}async function y(e,t,n){if(!t.sourceHashes)return[];let r=await l(e,{includeStaged:n.includeStaged,includeUnstaged:n.includeUnstaged,includeUntracked:n.includeUntracked});if(!r)return[];let i=[];return t.sourceHashes.staged!=null&&r.staged!==t.sourceHashes.staged&&i.push(`staged changes`),t.sourceHashes.unstaged!=null&&r.unstaged!==t.sourceHashes.unstaged&&i.push(`unstaged changes`),t.sourceHashes.untracked!=null&&r.untracked!==t.sourceHashes.untracked&&i.push(`untracked files`),i}async function b(e,r){let i=h(await n(e),`index`),s=h(m(),`compose-verify-idx-${d()}`);try{f(i,s);let n={GIT_INDEX_FILE:s};return await a(e,{trackedOnly:!0,indexEnv:n}),t(e,r,await o(e,{indexEnv:n}))}finally{try{p(s)}catch{}}}async function x(e,n){return await i(e,n.branchSha)===n.branchSha?s(await t(e,n.baseSha,n.branchSha))===n.diffHash?{valid:!0}:{valid:!1,error:`Branch diff has changed since the AI analysis`}:{valid:!1,error:`Branch has been modified since the AI analysis`}}async function S(e,n,r,i){return s(await t(e,n,r))===i.diffHash?{valid:!0}:{valid:!1,error:`Output diff does not match input — the commit chain does not reproduce the original changes`}}async function C(n,i,a,o){if(a.length===1)return S(n,i,a[0],o);let c=a[0];for(let t=1;t<a.length;t++)c=await e(n,await r(n,c,a[t]),[c,a[t]],`verify merge`);return s(await t(n,i,c))===o.diffHash?{valid:!0}:{valid:!1,error:`Combined diff of all branches does not match the original changes`}}export{C as verifyMultiBranchDiff,S as verifyResultingDiff,g as verifySnapshot};
|
package/dist/git/types.d.ts
CHANGED
|
@@ -39,5 +39,10 @@ interface ApplyMutations {
|
|
|
39
39
|
newSha: string;
|
|
40
40
|
}[];
|
|
41
41
|
}
|
|
42
|
+
/** Reported when the apply path's stash pop hit conflicts. The apply itself succeeded. */
|
|
43
|
+
interface StashConflict {
|
|
44
|
+
stashLabel: string;
|
|
45
|
+
conflictedFiles: string[];
|
|
46
|
+
}
|
|
42
47
|
//#endregion
|
|
43
|
-
export { ApplyMutations, SafetySnapshot };
|
|
48
|
+
export { ApplyMutations, SafetySnapshot, StashConflict };
|
|
@@ -1,10 +1,10 @@
|
|
|
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
|
|
2
|
-
`).filter(Boolean);return r&&
|
|
3
|
-
`).filter(Boolean):
|
|
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
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
5
|
`];return n.push(x(e)),n.push(S(t)),n.push(`Match these conventions in your output.`),n.join(`
|
|
6
6
|
`)}function x(e){if(e.format===`unknown`&&e.samples.length===0)return`Commit messages: No clear convention detected. Use clear, concise messages.
|
|
7
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
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.
|
|
9
9
|
`;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
|
|
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};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ComposeGitPort } from "../../ports/git.js";
|
|
2
2
|
|
|
3
3
|
//#region src/git/utils/temp-index.d.ts
|
|
4
|
-
declare function withUntrackedInTempIndex<T>(git: ComposeGitPort, fn: (indexEnv: Record<string, string>) => Promise<T
|
|
4
|
+
declare function withUntrackedInTempIndex<T>(git: ComposeGitPort, fn: (indexEnv: Record<string, string>) => Promise<T>): Promise<T>;
|
|
5
5
|
//#endregion
|
|
6
6
|
export { withUntrackedInTempIndex };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ComposeGitPortMissingOpError as e}from"../../ports/git.js";import{gitDir as t,listUntracked as n,stageAll as r}from"../port-dispatch.js";import{randomUUID as i}from"node:crypto";import*as a from"node:fs";import*as o from"node:os";import*as s from"node:path";async function c(e,t
|
|
1
|
+
import{ComposeGitPortMissingOpError as e}from"../../ports/git.js";import{gitDir as t,listUntracked as n,stageAll as r}from"../port-dispatch.js";import{randomUUID as i}from"node:crypto";import*as a from"node:fs";import*as o from"node:os";import*as s from"node:path";async function c(e,t){let c=await l(e),u=s.join(o.tmpdir(),`compose-tmp-idx-${i()}`);try{a.copyFileSync(c,u);let i={GIT_INDEX_FILE:u},o=await n(e);return o.length>0&&await r(e,{intentToAdd:!0,paths:o,indexEnv:i}),await t(i)}finally{try{a.unlinkSync(u)}catch{}}}async function l(n){let r=await t(n);if(!r)throw new e(`gitDir`,`The temp-index helper needs to resolve the real .git/index path. Provide 'gitDir' (or 'exec').`);return s.join(r,`index`)}export{c as withUntrackedInTempIndex};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ 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
4
|
import { TaskInputError } from "./errors/task.js";
|
|
5
|
-
import {
|
|
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";
|
|
8
8
|
import { ConventionDescriptor } from "./git/utils/conventions.js";
|
|
@@ -14,6 +14,7 @@ import { QuickComposeInput, QuickComposeOutput, QuickComposeResult } from "./tas
|
|
|
14
14
|
import { ComposeSession } from "./tasks/session.js";
|
|
15
15
|
import { PartitionInput, PartitionOutput, PartitionResult, PartitionStrategy } from "./tasks/partition/types.js";
|
|
16
16
|
import { ComposeContext, ComposeSource, ComposeTarget } from "./workflows/types.js";
|
|
17
|
+
import { StashConflict } from "./git/types.js";
|
|
17
18
|
import { ComposeBranchPlan } from "./git/apply-target.js";
|
|
18
19
|
import { analyze } from "./tasks/analyze/analyze.js";
|
|
19
20
|
import { ComposeGroupInput, ComposeGroupOutput } from "./tasks/compose-group/types.js";
|
|
@@ -22,7 +23,8 @@ import { group } from "./tasks/group/group.js";
|
|
|
22
23
|
import { order } from "./tasks/order/order.js";
|
|
23
24
|
import { partition } from "./tasks/partition/partition.js";
|
|
24
25
|
import { quickCompose } from "./tasks/quick-compose/quick-compose.js";
|
|
25
|
-
import { ApplyComposePlanInput, ApplyComposePlanResult, ComposeApplyPlan, ComposeDepth, ComposeInput, ComposePlan, ComposePlanInput, ComposePlanResult, ComposeProgressEvent, ComposeResult, ComposeStrategy, applyComposePlan, compose, composePlan, defaultPhaseMessages } from "./workflows/compose.js";
|
|
26
|
+
import { ApplyComposePlanInput, ApplyComposePlanResult, ApplyUpTo, ComposeApplyPlan, ComposeDepth, ComposeInput, ComposePlan, ComposePlanInput, ComposePlanResult, ComposeProgressEvent, ComposeResult, ComposeStrategy, applyComposePlan, compose, composePlan, defaultPhaseMessages } from "./workflows/compose.js";
|
|
26
27
|
import { SplitInput, SplitPlan, SplitProgressEvent, SplitResult, split } from "./workflows/split.js";
|
|
27
28
|
import { UndoComposeInput, ValidateUndoComposeInput, listUndoIds, undoCompose, validateUndoCompose } from "./workflows/undo.js";
|
|
28
|
-
|
|
29
|
+
import { UndoForceOptions } from "@gitkraken/shared-tools";
|
|
30
|
+
export { AIError, type AIErrorCode, type AfterModelCallInfo, type AnalysisResult, type AnalyzeInput, type AnalyzeOutput, type ApplyComposePlanInput, type ApplyComposePlanResult, type ApplyPatchOptions, type ApplyUpTo, 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 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 LogOptions, type OnAfterModelCall, type OnBeforeModelCall, type OnBeforePrompt, type OpOptions, type OrderInput, type OrderOutput, type OrderingResult, type PartitionInput, type PartitionOutput, type PartitionResult, type PartitionStrategy, type PhaseDetail, type ProgressEvent, type PromptOverride, type QuickComposeInput, type QuickComposeOutput, type QuickComposeResult, 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, estimatedCharactersPerToken, group, listUndoIds, order, partition, quickCompose, split, undoCompose, validateUndoCompose };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
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{estimatedCharactersPerToken as o}from"./tasks/ai-loop.js";import{analyze as s}from"./tasks/analyze/analyze.js";import{composeGroup as c}from"./tasks/compose-group/compose-group.js";import{group as l}from"./tasks/group/group.js";import{order as u}from"./tasks/order/order.js";import{partition as d}from"./tasks/partition/partition.js";import{quickCompose as f}from"./tasks/quick-compose/quick-compose.js";import{applyComposePlan as p,compose as m,composePlan as h,defaultPhaseMessages as g}from"./workflows/compose.js";import{split as _}from"./workflows/split.js";import{listUndoIds as v,undoCompose as y,validateUndoCompose as b}from"./workflows/undo.js";export{e as AIError,a as ComposeGitPortMissingOpError,r as ComposeWorkflowError,i as ComposeWorkflowInputError,t as GitError,n as TaskInputError,s as analyze,p as applyComposePlan,m as compose,c as composeGroup,h as composePlan,g as defaultPhaseMessages,o as estimatedCharactersPerToken,l as group,v as listUndoIds,u as order,d as partition,f as quickCompose,_ as split,y as undoCompose,b as validateUndoCompose};
|
package/dist/ports/git.d.ts
CHANGED
|
@@ -52,6 +52,8 @@ interface ComposeGitOps {
|
|
|
52
52
|
commitTree?: (tree: string, parents: string[], message: string, opts?: CommitTreeOptions) => Promise<string>;
|
|
53
53
|
/** Apply a unified diff patch to the current (or temp) index. Covers `apply --cached --allow-binary-replacement`. */
|
|
54
54
|
applyPatchToIndex?: (patch: string, opts?: ApplyPatchOptions) => Promise<void>;
|
|
55
|
+
/** Apply a unified diff patch to the working tree (no staging). Covers `apply --allow-binary-replacement`. */
|
|
56
|
+
applyPatchToWorkdir?: (patch: string, opts?: ApplyPatchToWorkdirOptions) => Promise<void>;
|
|
55
57
|
/** 3-way merge producing a tree SHA. Covers `merge-tree --write-tree a b`. */
|
|
56
58
|
mergeTree?: (a: string, b: string, opts?: OpOptions) => Promise<string>;
|
|
57
59
|
/** Create a branch at `startPoint`. Covers `branch name start`. */
|
|
@@ -70,8 +72,10 @@ interface ComposeGitOps {
|
|
|
70
72
|
discardWorkdirChanges?: (paths?: string[], opts?: OpOptions) => Promise<void>;
|
|
71
73
|
/** Push current workdir state to stash. Covers `stash push --include-untracked -m msg`. */
|
|
72
74
|
stashPush?: (opts?: StashPushOptions) => Promise<void>;
|
|
73
|
-
/** Pop the top stash entry. Covers `stash pop`. */
|
|
74
|
-
stashPop?: (opts?:
|
|
75
|
+
/** Pop the top stash entry. `withIndex: true` maps to `--index`. Covers `stash pop` and `stash pop --index`. */
|
|
76
|
+
stashPop?: (opts?: StashPopOptions) => Promise<void>;
|
|
77
|
+
/** Drop a specific stash entry. Covers `stash drop stash@{N}`. */
|
|
78
|
+
stashDrop?: (stashRef: string, opts?: OpOptions) => Promise<void>;
|
|
75
79
|
/**
|
|
76
80
|
* Apply an existing commit onto the current HEAD, producing a new commit.
|
|
77
81
|
* Covers `git cherry-pick <sha>`. Throws when conflicts make the pick fail —
|
|
@@ -84,10 +88,16 @@ interface ComposeGitOps {
|
|
|
84
88
|
/** Abort an in-progress cherry-pick. Covers `git cherry-pick --abort`. */
|
|
85
89
|
cherryPickAbort?: (opts?: OpOptions) => Promise<void>;
|
|
86
90
|
}
|
|
87
|
-
/**
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Options passed to every op. Currently empty — git operations are not
|
|
93
|
+
* cancellable in this library; they run to completion to avoid leaving the
|
|
94
|
+
* repo in a half-mutated state. Domain workflows route cancellation only
|
|
95
|
+
* into the AI loop.
|
|
96
|
+
*
|
|
97
|
+
* Kept as an interface so individual ops that need extra options (e.g.
|
|
98
|
+
* {@link CheckoutOptions}, {@link UpdateRefOptions}) can extend it.
|
|
99
|
+
*/
|
|
100
|
+
interface OpOptions {}
|
|
91
101
|
interface DiffTreeOptions extends OpOptions {
|
|
92
102
|
/** Include binary hunks as base85. Default true. */
|
|
93
103
|
binary?: boolean;
|
|
@@ -154,6 +164,10 @@ interface ApplyPatchOptions extends IndexScopedOptions {
|
|
|
154
164
|
/** Allow binary hunks in the patch. Default true. */
|
|
155
165
|
allowBinaryReplacement?: boolean;
|
|
156
166
|
}
|
|
167
|
+
interface ApplyPatchToWorkdirOptions extends OpOptions {
|
|
168
|
+
/** Allow binary hunks in the patch. Default true. */
|
|
169
|
+
allowBinaryReplacement?: boolean;
|
|
170
|
+
}
|
|
157
171
|
interface DeleteBranchOptions extends OpOptions {
|
|
158
172
|
force?: boolean;
|
|
159
173
|
}
|
|
@@ -179,6 +193,12 @@ interface StashPushOptions extends OpOptions {
|
|
|
179
193
|
message?: string;
|
|
180
194
|
includeUntracked?: boolean;
|
|
181
195
|
}
|
|
196
|
+
interface StashPopOptions extends OpOptions {
|
|
197
|
+
/** Restore staged-vs-unstaged split where possible. Maps to `--index`. */
|
|
198
|
+
withIndex?: boolean;
|
|
199
|
+
/** Specific stash entry to pop (e.g. `stash@{2}`). Defaults to the topmost. */
|
|
200
|
+
ref?: string;
|
|
201
|
+
}
|
|
182
202
|
/**
|
|
183
203
|
* The full port shape the library accepts. Exec is optional — if absent,
|
|
184
204
|
* every flow must be fully served by high-level ops.
|
|
@@ -196,4 +216,4 @@ declare class ComposeGitPortMissingOpError extends Error {
|
|
|
196
216
|
constructor(op: string, hint?: string);
|
|
197
217
|
}
|
|
198
218
|
//#endregion
|
|
199
|
-
export { ApplyPatchOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitOps, ComposeGitPort, ComposeGitPortMissingOpError, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, GitCommitIdentity, IndexScopedOptions, LogOptions, OpOptions, SigningConfig, StageAllOptions, StashPushOptions, UpdateRefOptions };
|
|
219
|
+
export { ApplyPatchOptions, ApplyPatchToWorkdirOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitOps, ComposeGitPort, ComposeGitPortMissingOpError, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, GitCommitIdentity, IndexScopedOptions, LogOptions, OpOptions, SigningConfig, StageAllOptions, StashPopOptions, StashPushOptions, UpdateRefOptions };
|
package/dist/tasks/ai-loop.d.ts
CHANGED
|
@@ -34,15 +34,14 @@ interface AfterModelCallInfo {
|
|
|
34
34
|
type OnBeforeModelCall = (info: BeforeModelCallInfo) => void;
|
|
35
35
|
type OnAfterModelCall = (info: AfterModelCallInfo) => void;
|
|
36
36
|
/**
|
|
37
|
-
* Gating hook fired immediately before each model.generate call.
|
|
37
|
+
* Gating hook fired immediately before each `model.generate` call. Returning
|
|
38
|
+
* `false` aborts the loop with a native `AbortError` (DOMException with
|
|
39
|
+
* `name === 'AbortError'`). Intended for cost warnings, large-prompt
|
|
40
|
+
* confirmations, policy checks, and similar caller-side UX.
|
|
38
41
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* The library provides `tokenEstimate` as a rough `charCount / 2.8`
|
|
44
|
-
* approximation — reasonable for code-heavy prompts but not authoritative.
|
|
45
|
-
* Consumers can ignore it and estimate their own way.
|
|
42
|
+
* `tokenEstimate` is a rough `charCount / 2.8` approximation — reasonable for
|
|
43
|
+
* code-heavy prompts but not authoritative. Callers can ignore it and
|
|
44
|
+
* estimate their own way.
|
|
46
45
|
*/
|
|
47
46
|
interface BeforePromptInfo {
|
|
48
47
|
phase: string;
|
|
@@ -65,16 +64,11 @@ interface AiLoopParams<T> {
|
|
|
65
64
|
onProgress?: (event: ProgressEvent) => void;
|
|
66
65
|
validate: (responseText: string) => ValidationResult<T>;
|
|
67
66
|
taskName: string;
|
|
68
|
-
|
|
69
|
-
/** Phase label stamped on Before/After model-call hook events. */
|
|
67
|
+
cancellation?: AbortSignal;
|
|
68
|
+
/** Phase label stamped on Before/After model-call hook events. Defaults to `taskName`. */
|
|
70
69
|
phase?: string;
|
|
71
70
|
onBeforeModelCall?: OnBeforeModelCall;
|
|
72
71
|
onAfterModelCall?: OnAfterModelCall;
|
|
73
|
-
/**
|
|
74
|
-
* Gating hook. Called before each model.generate. If it returns `false`, the
|
|
75
|
-
* loop aborts with an AbortError (caught by the workflow and surfaced as a
|
|
76
|
-
* `WorkflowError('CANCELLED')`).
|
|
77
|
-
*/
|
|
78
72
|
onBeforePrompt?: OnBeforePrompt;
|
|
79
73
|
promptVersion?: string;
|
|
80
74
|
}
|
package/dist/tasks/ai-loop.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`)}`)}function
|
|
1
|
+
import{AIError as e}from"../errors/ai.js";import{TaskInputError as t}from"../errors/task.js";import{appendAssistantMessage as n,appendUserMessage as r,createAiSession as i}from"../ai/session.js";import{addUsage as a}from"../ai/types.js";import{randomUUID as o}from"node:crypto";const s=2.8;async function c(a){let{model:s,userPrompt:c,maxRetries:d=3,maxTokens:f=16384,onProgress:p,validate:m,taskName:h,cancellation:g,phase:_=h,onBeforeModelCall:v,onAfterModelCall:y,promptVersion:b}=a;if(!a.session&&!a.systemPrompt)throw new t(`${h}: either 'session' or 'systemPrompt' must be provided`);let x=Date.now(),S;a.session?S=r(a.session,c):(S=i(a.systemPrompt),S=r(S,c));let C,w=[],T=d+1,E=[];for(let e=0;e<=d;e++){g?.throwIfAborted();let t=e+1;if(p?.({type:`attempt_start`,attempt:t,maxAttempts:T}),a.onBeforePrompt){let e=(S.systemPrompt?.length??0)+S.messages.reduce((e,t)=>e+t.content.length,0);if(!await a.onBeforePrompt({phase:_,attempt:t,maxAttempts:T,charCount:e,messageCount:S.messages.length,tokenEstimate:Math.ceil(e/2.8)}))throw new DOMException(`Prompt gate declined by caller (onBeforePrompt returned false)`,`AbortError`)}let i=o();E.push(i);let c=Date.now();v?.({phase:_,attempt:t,maxAttempts:T,callId:i});let h;try{h=await s.generate({system:S.systemPrompt,messages:[...S.messages],maxTokens:f,temperature:0,signal:g})}catch(e){let n=e instanceof Error?e.message:String(e);throw y?.({phase:_,attempt:t,callId:i,success:!1,durationMs:Date.now()-c,error:{code:e?.code??`UNKNOWN`,message:n},promptVersion:b}),l(e)||g?.aborted?new DOMException(n,`AbortError`):e}y?.({phase:_,attempt:t,callId:i,success:!0,durationMs:Date.now()-c,usage:h.usage,promptVersion:b}),C=u(C,h.usage);let D=m(h.text);if(D.valid&&D.result!==void 0)return S=n(S,h.text),{result:D.result,session:S,usage:C,attempts:t,durationMs:Date.now()-x,callIds:E};w=D.errors??[`Unknown validation error`],p?.({type:`validation_failed`,attempt:t,maxAttempts:T,errors:w}),e<d&&D.retryFeedback&&(S=n(S,h.text),S=r(S,D.retryFeedback))}throw new e(`VALIDATION_EXHAUSTED`,`Failed to get valid ${h} after ${T} attempts. Last errors:\n${w.join(`
|
|
2
|
+
`)}`)}function l(e){return e instanceof Error&&(e.name===`AbortError`||e.name===`DOMException`)}function u(e,t){if(!e)return t;if(!t)return e;let n={...e};return a(n,t),n}function d(e){let t=e.match(/<output>([\s\S]*?)(?:<\/output>|$)/)?.[1]?.trim()??null;return t?t.match(/^```(?:\w*)\n?([\s\S]*?)```$/)?.[1]?.trim()??t:null}export{s as estimatedCharactersPerToken,d as extractOutputTag,c as runAiLoop};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{buildAnalyzeSystemPrompt as r,buildAnalyzeUserPrompt as i}from"./prompt.js";import{validateAnalyzeResponse as a}from"./validator.js";async function o(o){let s=o.hunks;if(!s||s.length===0)throw new e(`No hunks provided: supply 'hunks'`);if(o.session?.completedSteps.analyze)return{result:o.session.completedSteps.analyze,session:o.session,attempts:0,durationMs:0,callIds:[]};let c=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}),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,onProgress:o.onProgress,
|
|
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}),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};
|
|
@@ -17,8 +17,10 @@ interface AnalyzeInput {
|
|
|
17
17
|
model: AiModelPort;
|
|
18
18
|
session?: ComposeSession;
|
|
19
19
|
maxRetries?: number;
|
|
20
|
+
/** Output-token cap forwarded to `runAiLoop`. Default 16384; bump for analyze on very large diffs where the structured output (per-hunk annotations) overflows the cap mid-JSON. */
|
|
21
|
+
maxTokens?: number;
|
|
20
22
|
onProgress?: (event: ProgressEvent) => void;
|
|
21
|
-
|
|
23
|
+
cancellation?: AbortSignal;
|
|
22
24
|
onBeforeModelCall?: OnBeforeModelCall;
|
|
23
25
|
onAfterModelCall?: OnAfterModelCall;
|
|
24
26
|
onBeforePrompt?: OnBeforePrompt;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{extractOutputTag as e}from"../ai-loop.js";import{analysisResultSchema as t}from"./schema.js";function n(n,a){
|
|
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:
|
|
2
2
|
<output>
|
|
3
3
|
{
|
|
4
4
|
"title": "Short PR-style title (under 72 chars)",
|
|
@@ -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}),o.promptOverride),d=await t({model:o.model,systemPrompt:l,userPrompt:u,maxRetries:o.maxRetries,onProgress:o.onProgress,
|
|
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}),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};
|
|
@@ -10,15 +10,25 @@ In addition to the \`branches\` field, include an \`analysis\` field at the top
|
|
|
10
10
|
"branches": [ ... ]
|
|
11
11
|
}
|
|
12
12
|
\`\`\`
|
|
13
|
-
`,i=`5. Write a commit message for each commit:
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
13
|
+
`,i=`5. Write a full commit message for each commit containing both a summary and a description:
|
|
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
|
+
- **Blank line**: mandatory separator between summary and description.
|
|
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 a(e,t=`branches`,n,r,i){return i?t===`commits`?u(e,n):d(e,n):t===`commits`?o(e,n,r):s(e,n,r)}function o(e,t,a){let o=e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
|
|
17
18
|
6. Write a detailed explanation for each commit as if explaining to a reviewer.
|
|
18
19
|
|
|
19
20
|
${e.promptFragment}`:`${i}
|
|
20
21
|
6. Write a detailed explanation for each commit as if explaining to a reviewer`,s=l(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits.
|
|
21
22
|
|
|
23
|
+
## Critical constraints (output is rejected if violated)
|
|
24
|
+
|
|
25
|
+
1. **Each hunk index appears exactly once across the entire response.** Not zero. Not two. Exactly one occurrence — meaning it appears in exactly one commit's \`hunks\` array, and only once within that array. Listing the same index twice inside one commit's \`hunks\`, OR listing it in two different commits, both count as duplicates and both fail validation. This is the most common failure mode when two hunks look similar (same file, same kind of change).
|
|
26
|
+
2. **All hunk indices 0..N-1 must appear** across the union of every commit's \`hunks\` array.
|
|
27
|
+
3. Each commit must contain at least one hunk. No empty commits.
|
|
28
|
+
4. Each commit's \`id\` must be unique (e.g. \`commit-1\`, \`commit-2\`).
|
|
29
|
+
|
|
30
|
+
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
|
+
|
|
22
32
|
${a?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
|
|
23
33
|
|
|
24
34
|
Before grouping, analyze the changes to understand:
|
|
@@ -31,10 +41,7 @@ ${a?"Output this analysis as a JSON field named `analysis` alongside your `branc
|
|
|
31
41
|
## Step 2: Group hunks into commits
|
|
32
42
|
|
|
33
43
|
1. Group hunks into logical units that make sense together and can be applied atomically
|
|
34
|
-
2.
|
|
35
|
-
3. Ensure each commit is self-contained and atomic
|
|
36
|
-
4. Each commit must have a unique ID (e.g., "commit-1", "commit-2")
|
|
37
|
-
5. Every commit must contain at least one hunk. Do not produce empty commits — if you want to split a theme across multiple commits, each resulting commit needs its own hunks
|
|
44
|
+
2. Ensure each commit is self-contained and atomic
|
|
38
45
|
${o}
|
|
39
46
|
${s}
|
|
40
47
|
|
|
@@ -48,16 +55,21 @@ ${n}
|
|
|
48
55
|
${a?r:``}
|
|
49
56
|
Wrap all commits in a single branch object with id "default", name "", title "", and description "".
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
- Every hunk must be assigned to exactly one commit
|
|
53
|
-
- Every commit must contain at least one hunk (no empty commits)
|
|
54
|
-
- Use exactly one branch with id "default", name "", title "", and description ""
|
|
55
|
-
- Return only the <output> tag and no other text.`}function s(e,t,a){let o=e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
|
|
58
|
+
Return only the <output> tag and no other text.`}function s(e,t,a){let o=e?.promptFragment?`5. Write a commit message for each commit following the repository's conventions (see below).
|
|
56
59
|
6. Write a detailed explanation for each commit as if explaining to a reviewer.
|
|
57
60
|
|
|
58
61
|
${e.promptFragment}`:`${i}
|
|
59
62
|
6. Write a detailed explanation for each commit as if explaining to a reviewer`,s=c(t?.branchSplitting,e),u=l(t?.commitGranularity);return`You are an advanced AI programming assistant tasked with analyzing and organizing code changes into commits and branches.
|
|
60
63
|
|
|
64
|
+
## Critical constraints (output is rejected if violated)
|
|
65
|
+
|
|
66
|
+
1. **Each hunk index appears exactly once across the entire response.** Not zero. Not two. Exactly one occurrence — meaning it appears in exactly one branch, in exactly one commit within that branch, and only once within that commit's \`hunks\` array. Listing the same index twice inside one commit's \`hunks\`, OR listing it in two different commits, OR listing it in two different branches, all count as duplicates and all fail validation. This is the most common failure mode when two hunks look similar (same file, same kind of change).
|
|
67
|
+
2. **All hunk indices 0..N-1 must appear** across the union of every commit's \`hunks\` array across every branch.
|
|
68
|
+
3. Each commit must contain at least one hunk. No empty commits.
|
|
69
|
+
4. Each commit \`id\` must be unique across the whole response.
|
|
70
|
+
|
|
71
|
+
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
|
+
|
|
61
73
|
${a?`## Step 1: Analysis`:`## Step 1: Internal Analysis`}
|
|
62
74
|
|
|
63
75
|
Before grouping, analyze the changes to understand:
|
|
@@ -78,10 +90,7 @@ Your job has two levels:
|
|
|
78
90
|
### Commit guidelines
|
|
79
91
|
|
|
80
92
|
1. Group hunks into logical units that make sense together and can be applied atomically
|
|
81
|
-
2.
|
|
82
|
-
3. Ensure each commit is self-contained and atomic
|
|
83
|
-
4. Each commit must have a unique ID (e.g., "commit-1", "commit-2")
|
|
84
|
-
5. Every commit must contain at least one hunk. Do not produce empty commits — if you want to split a theme across multiple commits, each resulting commit needs its own hunks
|
|
93
|
+
2. Ensure each commit is self-contained and atomic
|
|
85
94
|
${o}
|
|
86
95
|
${u}
|
|
87
96
|
|
|
@@ -97,10 +106,7 @@ Output your organization as JSON inside a <output> tag matching this schema:
|
|
|
97
106
|
${n}
|
|
98
107
|
\`\`\`
|
|
99
108
|
${a?r:``}
|
|
100
|
-
|
|
101
|
-
- Every hunk must be assigned to exactly one commit in exactly one branch
|
|
102
|
-
- Every commit must contain at least one hunk (no empty commits)
|
|
103
|
-
- Return only the <output> tag and no other text.`}function c(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.
|
|
109
|
+
Return only the <output> tag and no other text.`}function c(e,t){let n=`Each branch must have a descriptive name, a short PR-style title (under 72 characters, imperative mood), and at least one commit.`,r=`- Each commit belongs to exactly one branch.
|
|
104
110
|
${t?.promptFragment?`- ${n} Follow the repository's branch naming conventions (see above).`:`- ${n}`}
|
|
105
111
|
- 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 l(e){return e===`consolidated`?`
|
|
106
112
|
Prefer fewer, larger commits. Combine related changes into a single commit when they serve the same purpose.`:e===`granular`?`
|