@gitkraken/compose-tools 0.1.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 +27 -0
- package/LICENSE +19 -0
- package/README.md +543 -0
- package/dist/ai/session.d.ts +12 -0
- package/dist/ai/session.js +1 -0
- package/dist/ai/types.d.ts +31 -0
- package/dist/ai/types.js +1 -0
- package/dist/cancellation.d.ts +20 -0
- package/dist/cancellation.js +1 -0
- package/dist/errors/ai.d.ts +10 -0
- package/dist/errors/ai.js +1 -0
- package/dist/errors/git.d.ts +12 -0
- package/dist/errors/git.js +1 -0
- package/dist/errors/task.d.ts +6 -0
- package/dist/errors/task.js +1 -0
- package/dist/errors/workflow.d.ts +13 -0
- package/dist/errors/workflow.js +1 -0
- package/dist/git/apply-branches.d.ts +28 -0
- package/dist/git/apply-branches.js +1 -0
- package/dist/git/apply-target.d.ts +39 -0
- package/dist/git/apply-target.js +1 -0
- package/dist/git/commit-chain.d.ts +87 -0
- package/dist/git/commit-chain.js +6 -0
- package/dist/git/diff-collector.d.ts +80 -0
- package/dist/git/diff-collector.js +8 -0
- package/dist/git/index.d.ts +7 -0
- package/dist/git/index.js +1 -0
- package/dist/git/port-dispatch.d.ts +34 -0
- package/dist/git/port-dispatch.js +3 -0
- package/dist/git/require-exec.d.ts +17 -0
- package/dist/git/require-exec.js +1 -0
- package/dist/git/safety.d.ts +22 -0
- package/dist/git/safety.js +1 -0
- package/dist/git/types.d.ts +43 -0
- package/dist/git/types.js +1 -0
- package/dist/git/utils/conventions.d.ts +33 -0
- package/dist/git/utils/conventions.js +10 -0
- package/dist/git/utils/crypto.d.ts +4 -0
- package/dist/git/utils/crypto.js +1 -0
- package/dist/git/utils/hunk-parser.d.ts +6 -0
- package/dist/git/utils/hunk-parser.js +7 -0
- package/dist/git/utils/patch-builder.d.ts +6 -0
- package/dist/git/utils/patch-builder.js +2 -0
- package/dist/git/utils/temp-index.d.ts +6 -0
- package/dist/git/utils/temp-index.js +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +1 -0
- package/dist/ports/git.d.ts +199 -0
- package/dist/ports/git.js +1 -0
- package/dist/tasks/ai-loop.d.ts +99 -0
- package/dist/tasks/ai-loop.js +2 -0
- package/dist/tasks/analyze/analyze.d.ts +6 -0
- package/dist/tasks/analyze/analyze.js +1 -0
- package/dist/tasks/analyze/prompt.d.ts +16 -0
- package/dist/tasks/analyze/prompt.js +34 -0
- package/dist/tasks/analyze/schema.d.ts +38 -0
- package/dist/tasks/analyze/schema.js +1 -0
- package/dist/tasks/analyze/types.d.ts +69 -0
- package/dist/tasks/analyze/types.js +1 -0
- package/dist/tasks/analyze/validator.d.ts +7 -0
- package/dist/tasks/analyze/validator.js +13 -0
- package/dist/tasks/compose-group/compose-group.d.ts +15 -0
- package/dist/tasks/compose-group/compose-group.js +1 -0
- package/dist/tasks/compose-group/prompt.d.ts +26 -0
- package/dist/tasks/compose-group/prompt.js +162 -0
- package/dist/tasks/compose-group/types.d.ts +62 -0
- package/dist/tasks/compose-group/types.js +1 -0
- package/dist/tasks/group/group.d.ts +6 -0
- package/dist/tasks/group/group.js +1 -0
- package/dist/tasks/group/prompt.d.ts +15 -0
- package/dist/tasks/group/prompt.js +158 -0
- package/dist/tasks/group/schema.d.ts +19 -0
- package/dist/tasks/group/schema.js +1 -0
- package/dist/tasks/group/types.d.ts +61 -0
- package/dist/tasks/group/types.js +1 -0
- package/dist/tasks/group/validator.d.ts +7 -0
- package/dist/tasks/group/validator.js +53 -0
- package/dist/tasks/index.d.ts +15 -0
- package/dist/tasks/index.js +1 -0
- package/dist/tasks/order/order.d.ts +6 -0
- package/dist/tasks/order/order.js +1 -0
- package/dist/tasks/order/prompt.d.ts +13 -0
- package/dist/tasks/order/prompt.js +45 -0
- package/dist/tasks/order/schema.d.ts +12 -0
- package/dist/tasks/order/schema.js +1 -0
- package/dist/tasks/order/types.d.ts +36 -0
- package/dist/tasks/order/types.js +1 -0
- package/dist/tasks/order/validator.d.ts +11 -0
- package/dist/tasks/order/validator.js +12 -0
- package/dist/tasks/partition/partition.d.ts +6 -0
- package/dist/tasks/partition/partition.js +1 -0
- package/dist/tasks/partition/prompt.d.ts +16 -0
- package/dist/tasks/partition/prompt.js +61 -0
- package/dist/tasks/partition/schema.d.ts +14 -0
- package/dist/tasks/partition/schema.js +1 -0
- package/dist/tasks/partition/types.d.ts +53 -0
- package/dist/tasks/partition/types.js +1 -0
- package/dist/tasks/partition/validator.d.ts +7 -0
- package/dist/tasks/partition/validator.js +11 -0
- package/dist/tasks/quick-compose/prompt.d.ts +18 -0
- package/dist/tasks/quick-compose/prompt.js +47 -0
- package/dist/tasks/quick-compose/quick-compose.d.ts +6 -0
- package/dist/tasks/quick-compose/quick-compose.js +1 -0
- package/dist/tasks/quick-compose/schema.d.ts +27 -0
- package/dist/tasks/quick-compose/schema.js +1 -0
- package/dist/tasks/quick-compose/types.d.ts +63 -0
- package/dist/tasks/quick-compose/types.js +1 -0
- package/dist/tasks/quick-compose/validator.d.ts +7 -0
- package/dist/tasks/quick-compose/validator.js +25 -0
- package/dist/tasks/session.d.ts +22 -0
- package/dist/tasks/session.js +1 -0
- package/dist/tasks/types.d.ts +101 -0
- package/dist/tasks/types.js +3 -0
- package/dist/workflows/compose.d.ts +371 -0
- package/dist/workflows/compose.js +2 -0
- package/dist/workflows/split.d.ts +80 -0
- package/dist/workflows/split.js +3 -0
- package/dist/workflows/types.d.ts +63 -0
- package/dist/workflows/types.js +1 -0
- package/dist/workflows/undo.d.ts +56 -0
- package/dist/workflows/undo.js +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e){if(e.length===0)return``;let t=new Map;for(let n of e){let e=n.diffHeader,r=t.get(e);r||(r=[],t.set(e,r)),r.push(n)}let n=``;for(let[e,r]of t){let t=r.find(e=>e.isBinary);if(t?.binaryPatchData){let r=t.binaryPatchData.endsWith(`
|
|
2
|
+
`)?t.binaryPatchData:`${t.binaryPatchData}\n`;n+=`${e}\n${r}`;continue}n+=`${e}\n`;let i=r.filter(e=>e.hunkHeader&&e.content&&!e.isRename);for(let e of i)n+=`${e.hunkHeader}\n${e.content}\n`}return n}export{e as buildPatchForCommit};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ComposeGitPort } from "../../ports/git.js";
|
|
2
|
+
|
|
3
|
+
//#region src/git/utils/temp-index.d.ts
|
|
4
|
+
declare function withUntrackedInTempIndex<T>(git: ComposeGitPort, fn: (indexEnv: Record<string, string>) => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { withUntrackedInTempIndex };
|
|
@@ -0,0 +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,c){let u=await l(e,c),d=s.join(o.tmpdir(),`compose-tmp-idx-${i()}`);try{a.copyFileSync(u,d);let i={GIT_INDEX_FILE:d},o=await n(e,{signal:c});return o.length>0&&await r(e,{intentToAdd:!0,paths:o,indexEnv:i,signal:c}),await t(i)}finally{try{a.unlinkSync(d)}catch{}}}async function l(n,r){let i=await t(n,{signal:r});if(!i)throw new e(`gitDir`,`The temp-index helper needs to resolve the real .git/index path. Provide 'gitDir' (or 'exec').`);return s.join(i,`index`)}export{c as withUntrackedInTempIndex};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { PhaseDetail, ProgressEvent, TokenUsage } from "./ai/types.js";
|
|
2
|
+
import { AIError, AIErrorCode } from "./errors/ai.js";
|
|
3
|
+
import { GitError, GitErrorCode } from "./errors/git.js";
|
|
4
|
+
import { TaskInputError } from "./errors/task.js";
|
|
5
|
+
import { WorkflowError, WorkflowErrorCode, WorkflowInputError } from "./errors/workflow.js";
|
|
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
|
+
import { BranchGroup, BranchOrdering, BranchPartition, CommitDetail, CommitSuggestion, ComposeHunk, ComposeTaskName, PromptOverride } from "./tasks/types.js";
|
|
8
|
+
import { ConventionDescriptor } from "./git/utils/conventions.js";
|
|
9
|
+
import { AfterModelCallInfo, BeforeModelCallInfo, BeforePromptInfo, OnAfterModelCall, OnBeforeModelCall, OnBeforePrompt, estimatedCharactersPerToken } from "./tasks/ai-loop.js";
|
|
10
|
+
import { AnalysisResult, AnalyzeInput, AnalyzeOutput, Concern, ConcernDependency, HunkAnnotation, HunkOrderingConstraint } from "./tasks/analyze/types.js";
|
|
11
|
+
import { GroupInput, GroupOutput, GroupingResult, GroupingStrategy } from "./tasks/group/types.js";
|
|
12
|
+
import { OrderInput, OrderOutput, OrderingResult } from "./tasks/order/types.js";
|
|
13
|
+
import { QuickComposeInput, QuickComposeOutput, QuickComposeResult } from "./tasks/quick-compose/types.js";
|
|
14
|
+
import { ComposeSession } from "./tasks/session.js";
|
|
15
|
+
import { PartitionInput, PartitionOutput, PartitionResult, PartitionStrategy } from "./tasks/partition/types.js";
|
|
16
|
+
import { ComposeContext, ComposeSource, ComposeTarget } from "./workflows/types.js";
|
|
17
|
+
import { ComposeBranchPlan } from "./git/apply-target.js";
|
|
18
|
+
import { analyze } from "./tasks/analyze/analyze.js";
|
|
19
|
+
import { ComposeGroupInput, ComposeGroupOutput } from "./tasks/compose-group/types.js";
|
|
20
|
+
import { composeGroup } from "./tasks/compose-group/compose-group.js";
|
|
21
|
+
import { group } from "./tasks/group/group.js";
|
|
22
|
+
import { order } from "./tasks/order/order.js";
|
|
23
|
+
import { partition } from "./tasks/partition/partition.js";
|
|
24
|
+
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 { SplitInput, SplitPlan, SplitProgressEvent, SplitResult, split } from "./workflows/split.js";
|
|
27
|
+
import { UndoComposeInput, ValidateUndoComposeInput, listUndoIds, undoCompose, validateUndoCompose } from "./workflows/undo.js";
|
|
28
|
+
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 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, 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 StashPushOptions, TaskInputError, type TokenUsage, type UndoComposeInput, type UpdateRefOptions, type ValidateUndoComposeInput, WorkflowError, type WorkflowErrorCode, WorkflowInputError, analyze, applyComposePlan, compose, composeGroup, composePlan, defaultPhaseMessages, estimatedCharactersPerToken, group, listUndoIds, order, partition, quickCompose, split, undoCompose, validateUndoCompose };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{WorkflowError as e,WorkflowInputError as t}from"./errors/workflow.js";import{AIError as n}from"./errors/ai.js";import{GitError as r}from"./errors/git.js";import{TaskInputError as i}from"./errors/task.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{n as AIError,a as ComposeGitPortMissingOpError,r as GitError,i as TaskInputError,e as WorkflowError,t as WorkflowInputError,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};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { GitPort } from "@gitkraken/shared-tools";
|
|
2
|
+
|
|
3
|
+
//#region src/ports/git.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Compose-specific git port — accepts either high-level ops, raw `exec`, or both.
|
|
6
|
+
*
|
|
7
|
+
* The library's internal call sites go through dispatchers in `../git/port-dispatch.js`
|
|
8
|
+
* that prefer the high-level op when provided, fall back to `exec` otherwise, and
|
|
9
|
+
* throw `ComposeGitPortMissingOpError` when neither is available for the current flow.
|
|
10
|
+
*
|
|
11
|
+
* Consumers have three reasonable shapes:
|
|
12
|
+
* - CLI consumer: provide only `exec` — every op is routed through it.
|
|
13
|
+
* - libgit2 consumer: provide every op used by their flow, skip `exec`.
|
|
14
|
+
* - Hybrid: provide `exec` plus targeted high-level overrides (e.g. signing-aware
|
|
15
|
+
* `commitTree`) that the library prefers over the default CLI route.
|
|
16
|
+
*
|
|
17
|
+
* `exec` is optional at the type level, but required at runtime if any flow calls an
|
|
18
|
+
* op the consumer didn't provide — the dispatcher throws with a clear message.
|
|
19
|
+
*/
|
|
20
|
+
interface ComposeGitOps {
|
|
21
|
+
/** Resolve a ref to its SHA. Covers `rev-parse HEAD`, `rev-parse <branch>`, `rev-parse <sha>`. */
|
|
22
|
+
revParse?: (ref: string, opts?: OpOptions) => Promise<string>;
|
|
23
|
+
/** Resolve and verify a ref exists. Returns SHA on success, `null` on not-found. Covers `rev-parse --verify`. */
|
|
24
|
+
revParseVerify?: (ref: string, opts?: OpOptions) => Promise<string | null>;
|
|
25
|
+
/** Absolute path to the `.git` dir for this repo. Covers `rev-parse --absolute-git-dir`. */
|
|
26
|
+
gitDir?: (opts?: OpOptions) => Promise<string>;
|
|
27
|
+
/** Name of the branch HEAD points at, or `null` if HEAD is detached. Covers `rev-parse --abbrev-ref HEAD` / `symbolic-ref`. */
|
|
28
|
+
currentBranch?: (opts?: OpOptions) => Promise<string | null>;
|
|
29
|
+
/** Best common ancestor between two refs. Covers `merge-base a b`. */
|
|
30
|
+
mergeBase?: (a: string, b: string, opts?: OpOptions) => Promise<string>;
|
|
31
|
+
/** Tree-to-tree unified diff. Covers `diff --binary from to` and `diff --name-only from to`. */
|
|
32
|
+
diffTree?: (from: string, to: string, opts?: DiffTreeOptions) => Promise<string>;
|
|
33
|
+
/** Commit log with custom format. Covers various `git log --format=... <range>` / `-1 <sha>` uses. */
|
|
34
|
+
log?: (opts: LogOptions) => Promise<string>;
|
|
35
|
+
/** Ref enumeration. Covers `for-each-ref`. */
|
|
36
|
+
forEachRef?: (opts: ForEachRefOptions) => Promise<string>;
|
|
37
|
+
/** Write the current (or temp) index to a tree object. Covers `write-tree`. */
|
|
38
|
+
writeTree?: (opts?: IndexScopedOptions) => Promise<string>;
|
|
39
|
+
/** Load a tree's contents into the current (or temp) index. Covers `read-tree <sha>`. */
|
|
40
|
+
readTree?: (sha: string, opts?: IndexScopedOptions) => Promise<void>;
|
|
41
|
+
/** Stage files to the current (or temp) index. Covers `add -u`, `add -A`, `add -N -- paths`, `add <paths>`. */
|
|
42
|
+
stageAll?: (opts?: StageAllOptions) => Promise<void>;
|
|
43
|
+
/** Porcelain status. Covers `status --porcelain`. */
|
|
44
|
+
statusPorcelain?: (opts?: OpOptions) => Promise<string>;
|
|
45
|
+
/** Untracked file paths relative to repo root. Covers `ls-files --others --exclude-standard`. */
|
|
46
|
+
listUntracked?: (opts?: OpOptions) => Promise<string[]>;
|
|
47
|
+
/** Read a file's contents at a given ref. Covers `show ref:path`. */
|
|
48
|
+
showFile?: (ref: string, path: string, opts?: OpOptions) => Promise<string>;
|
|
49
|
+
/** Read a git config value. Returns `null` when unset. Covers `config <key>`. */
|
|
50
|
+
getConfig?: (key: string, opts?: OpOptions) => Promise<string | null>;
|
|
51
|
+
/** Create a commit object from a tree and parents. Covers `commit-tree tree -p parent -m message`. */
|
|
52
|
+
commitTree?: (tree: string, parents: string[], message: string, opts?: CommitTreeOptions) => Promise<string>;
|
|
53
|
+
/** Apply a unified diff patch to the current (or temp) index. Covers `apply --cached --allow-binary-replacement`. */
|
|
54
|
+
applyPatchToIndex?: (patch: string, opts?: ApplyPatchOptions) => Promise<void>;
|
|
55
|
+
/** 3-way merge producing a tree SHA. Covers `merge-tree --write-tree a b`. */
|
|
56
|
+
mergeTree?: (a: string, b: string, opts?: OpOptions) => Promise<string>;
|
|
57
|
+
/** Create a branch at `startPoint`. Covers `branch name start`. */
|
|
58
|
+
createBranch?: (name: string, startPoint: string, opts?: OpOptions) => Promise<void>;
|
|
59
|
+
/** Delete a branch. `force: true` maps to `-D`. Covers `branch -D` and `branch -d`. */
|
|
60
|
+
deleteBranch?: (name: string, opts?: DeleteBranchOptions) => Promise<void>;
|
|
61
|
+
/** Set a ref to a SHA atomically. `previousSha` allows CAS. Covers `update-ref <ref> <sha>` / `update-ref -d`. */
|
|
62
|
+
updateRef?: (ref: string, sha: string, opts?: UpdateRefOptions) => Promise<void>;
|
|
63
|
+
/** Switch to a ref. `createBranch: true` → new branch. `detach: true` → detached HEAD. Covers `checkout <ref>`, `checkout -b`, `checkout --detach`. */
|
|
64
|
+
checkout?: (ref: string, opts?: CheckoutOptions) => Promise<void>;
|
|
65
|
+
/** Reset HEAD (and optionally index/worktree) to a ref. Covers `reset [--soft/--mixed/--hard] sha`. */
|
|
66
|
+
reset?: (ref: string, mode: 'soft' | 'mixed' | 'hard', opts?: OpOptions) => Promise<void>;
|
|
67
|
+
/** Remove untracked files/dirs from workdir. Covers `clean -fd`. */
|
|
68
|
+
cleanWorkdir?: (opts?: CleanOptions) => Promise<void>;
|
|
69
|
+
/** Discard unstaged workdir changes. Covers `checkout -- .` or `checkout -- paths`. */
|
|
70
|
+
discardWorkdirChanges?: (paths?: string[], opts?: OpOptions) => Promise<void>;
|
|
71
|
+
/** Push current workdir state to stash. Covers `stash push --include-untracked -m msg`. */
|
|
72
|
+
stashPush?: (opts?: StashPushOptions) => Promise<void>;
|
|
73
|
+
/** Pop the top stash entry. Covers `stash pop`. */
|
|
74
|
+
stashPop?: (opts?: OpOptions) => Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Apply an existing commit onto the current HEAD, producing a new commit.
|
|
77
|
+
* Covers `git cherry-pick <sha>`. Throws when conflicts make the pick fail —
|
|
78
|
+
* callers should wrap and surface that as a `GitError('CHERRY_PICK_CONFLICT')`.
|
|
79
|
+
*
|
|
80
|
+
* A libgit2-backed implementation can perform a 3-way merge in memory and
|
|
81
|
+
* create the resulting commit directly, without touching the working tree.
|
|
82
|
+
*/
|
|
83
|
+
cherryPick?: (sha: string, opts?: OpOptions) => Promise<void>;
|
|
84
|
+
/** Abort an in-progress cherry-pick. Covers `git cherry-pick --abort`. */
|
|
85
|
+
cherryPickAbort?: (opts?: OpOptions) => Promise<void>;
|
|
86
|
+
}
|
|
87
|
+
/** Options passed to every op — all ops can be cancelled via signal. */
|
|
88
|
+
interface OpOptions {
|
|
89
|
+
signal?: AbortSignal;
|
|
90
|
+
}
|
|
91
|
+
interface DiffTreeOptions extends OpOptions {
|
|
92
|
+
/** Include binary hunks as base85. Default true. */
|
|
93
|
+
binary?: boolean;
|
|
94
|
+
/** Return file list instead of patch content. */
|
|
95
|
+
nameOnly?: boolean;
|
|
96
|
+
}
|
|
97
|
+
interface LogOptions extends OpOptions {
|
|
98
|
+
format: string;
|
|
99
|
+
range?: string;
|
|
100
|
+
sha?: string;
|
|
101
|
+
limit?: number;
|
|
102
|
+
noMerges?: boolean;
|
|
103
|
+
reverse?: boolean;
|
|
104
|
+
author?: string;
|
|
105
|
+
}
|
|
106
|
+
interface ForEachRefOptions extends OpOptions {
|
|
107
|
+
format: string;
|
|
108
|
+
prefix?: string;
|
|
109
|
+
sort?: string;
|
|
110
|
+
limit?: number;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Index-scoped options.
|
|
114
|
+
*
|
|
115
|
+
* `indexEnv` is the env vars the library produces when it wants an op to run
|
|
116
|
+
* against a scratch index rather than the real one — typically
|
|
117
|
+
* `{ GIT_INDEX_FILE: <tmp path> }`. CLI consumers route these env vars to the
|
|
118
|
+
* child process. libgit2 consumers can treat `indexEnv.GIT_INDEX_FILE` as an
|
|
119
|
+
* opaque key to look up their in-memory index object.
|
|
120
|
+
*/
|
|
121
|
+
interface IndexScopedOptions extends OpOptions {
|
|
122
|
+
indexEnv?: Record<string, string>;
|
|
123
|
+
}
|
|
124
|
+
interface StageAllOptions extends IndexScopedOptions {
|
|
125
|
+
/** Stage only tracked modifications (`add -u`). Mutually exclusive with `intentToAdd`. */
|
|
126
|
+
trackedOnly?: boolean;
|
|
127
|
+
/** Add with intent-to-add (`add -N`). */
|
|
128
|
+
intentToAdd?: boolean;
|
|
129
|
+
/** Stage specific paths. When omitted, stages all (`add -A`). */
|
|
130
|
+
paths?: string[];
|
|
131
|
+
}
|
|
132
|
+
interface GitCommitIdentity {
|
|
133
|
+
name: string;
|
|
134
|
+
email: string;
|
|
135
|
+
date?: Date;
|
|
136
|
+
}
|
|
137
|
+
interface SigningConfig {
|
|
138
|
+
enabled: boolean;
|
|
139
|
+
/**
|
|
140
|
+
* The signing key to pass to `commit-tree -S<key>`. Accepts any value git
|
|
141
|
+
* would — GPG key ID, SSH key path, etc. — matching the `user.signingkey`
|
|
142
|
+
* git config semantics.
|
|
143
|
+
*/
|
|
144
|
+
signingKey?: string;
|
|
145
|
+
gpgProgram?: string;
|
|
146
|
+
}
|
|
147
|
+
interface CommitTreeOptions extends OpOptions {
|
|
148
|
+
/** When set, sets `GIT_AUTHOR_*` env on the commit. */
|
|
149
|
+
author?: GitCommitIdentity;
|
|
150
|
+
/** When enabled, the op signs the commit (consumer responsibility to honor). */
|
|
151
|
+
signing?: SigningConfig;
|
|
152
|
+
}
|
|
153
|
+
interface ApplyPatchOptions extends IndexScopedOptions {
|
|
154
|
+
/** Allow binary hunks in the patch. Default true. */
|
|
155
|
+
allowBinaryReplacement?: boolean;
|
|
156
|
+
}
|
|
157
|
+
interface DeleteBranchOptions extends OpOptions {
|
|
158
|
+
force?: boolean;
|
|
159
|
+
}
|
|
160
|
+
interface UpdateRefOptions extends OpOptions {
|
|
161
|
+
/** When set, only update if the current SHA matches. */
|
|
162
|
+
previousSha?: string;
|
|
163
|
+
/** When true, deletes the ref instead. */
|
|
164
|
+
remove?: boolean;
|
|
165
|
+
}
|
|
166
|
+
interface CheckoutOptions extends OpOptions {
|
|
167
|
+
/** When true, `ref` is a new branch name to create. Requires `startPoint` for non-HEAD starts. */
|
|
168
|
+
createBranch?: boolean;
|
|
169
|
+
/** Start point for a new branch created via `createBranch`. Ignored otherwise. */
|
|
170
|
+
startPoint?: string;
|
|
171
|
+
/** Detach HEAD at the target. */
|
|
172
|
+
detach?: boolean;
|
|
173
|
+
}
|
|
174
|
+
interface CleanOptions extends OpOptions {
|
|
175
|
+
force?: boolean;
|
|
176
|
+
directories?: boolean;
|
|
177
|
+
}
|
|
178
|
+
interface StashPushOptions extends OpOptions {
|
|
179
|
+
message?: string;
|
|
180
|
+
includeUntracked?: boolean;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The full port shape the library accepts. Exec is optional — if absent,
|
|
184
|
+
* every flow must be fully served by high-level ops.
|
|
185
|
+
*/
|
|
186
|
+
type ComposeGitPort = ComposeGitOps & {
|
|
187
|
+
exec?: GitPort['exec'];
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Thrown when the library needs a git op the consumer didn't provide, and
|
|
191
|
+
* `exec` isn't available as a fallback.
|
|
192
|
+
*/
|
|
193
|
+
declare class ComposeGitPortMissingOpError extends Error {
|
|
194
|
+
readonly op: string;
|
|
195
|
+
readonly hint: string;
|
|
196
|
+
constructor(op: string, hint?: string);
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
export { ApplyPatchOptions, CheckoutOptions, CleanOptions, CommitTreeOptions, ComposeGitOps, ComposeGitPort, ComposeGitPortMissingOpError, DeleteBranchOptions, DiffTreeOptions, ForEachRefOptions, GitCommitIdentity, IndexScopedOptions, LogOptions, OpOptions, SigningConfig, StageAllOptions, StashPushOptions, UpdateRefOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=class extends Error{op;hint;constructor(e,t){super(`ComposeGitPort is missing op '${e}'. `+`Provide either '${e}' directly, or provide 'exec' as a fallback. ${t??``}`.trim()),this.name=`ComposeGitPortMissingOpError`,this.op=e,this.hint=t??``}};export{e as ComposeGitPortMissingOpError};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { AiSession } from "../ai/session.js";
|
|
2
|
+
import { ProgressEvent, TokenUsage } from "../ai/types.js";
|
|
3
|
+
import { AiModelPort } from "@gitkraken/shared-tools";
|
|
4
|
+
|
|
5
|
+
//#region src/tasks/ai-loop.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Observability hook: called immediately before each model.generate invocation.
|
|
8
|
+
*
|
|
9
|
+
* Consumers can use this for telemetry, progress UI, log emission, etc. The
|
|
10
|
+
* library doesn't interpret the result — the hook is purely informational.
|
|
11
|
+
*/
|
|
12
|
+
interface BeforeModelCallInfo {
|
|
13
|
+
/** Phase name (e.g. "analyze", "compose-group", "order", "partition"). */
|
|
14
|
+
phase: string;
|
|
15
|
+
attempt: number;
|
|
16
|
+
maxAttempts: number;
|
|
17
|
+
/** Stable UUID identifying this model.generate call. Pairs with the matching After event. */
|
|
18
|
+
callId: string;
|
|
19
|
+
}
|
|
20
|
+
interface AfterModelCallInfo {
|
|
21
|
+
phase: string;
|
|
22
|
+
attempt: number;
|
|
23
|
+
callId: string;
|
|
24
|
+
success: boolean;
|
|
25
|
+
durationMs: number;
|
|
26
|
+
usage?: TokenUsage;
|
|
27
|
+
error?: {
|
|
28
|
+
code: string;
|
|
29
|
+
message: string;
|
|
30
|
+
};
|
|
31
|
+
/** Identifier of the prompt template used for this call, when a consumer override supplied one. */
|
|
32
|
+
promptVersion?: string;
|
|
33
|
+
}
|
|
34
|
+
type OnBeforeModelCall = (info: BeforeModelCallInfo) => void;
|
|
35
|
+
type OnAfterModelCall = (info: AfterModelCallInfo) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Gating hook fired immediately before each model.generate call.
|
|
38
|
+
*
|
|
39
|
+
* Returning `false` aborts the workflow; the library throws the resulting
|
|
40
|
+
* rejection as a `WorkflowError('CANCELLED')`. Intended for cost warnings,
|
|
41
|
+
* large-prompt confirmations, policy checks, and similar consumer-side UX.
|
|
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.
|
|
46
|
+
*/
|
|
47
|
+
interface BeforePromptInfo {
|
|
48
|
+
phase: string;
|
|
49
|
+
attempt: number;
|
|
50
|
+
maxAttempts: number;
|
|
51
|
+
charCount: number;
|
|
52
|
+
messageCount: number;
|
|
53
|
+
tokenEstimate: number;
|
|
54
|
+
}
|
|
55
|
+
type OnBeforePrompt = (info: BeforePromptInfo) => Promise<boolean>;
|
|
56
|
+
/** Character-to-token approximation for English/code prompts. */
|
|
57
|
+
declare const estimatedCharactersPerToken = 2.8;
|
|
58
|
+
interface AiLoopParams<T> {
|
|
59
|
+
model: AiModelPort;
|
|
60
|
+
systemPrompt?: string;
|
|
61
|
+
userPrompt: string;
|
|
62
|
+
session?: AiSession;
|
|
63
|
+
maxRetries?: number;
|
|
64
|
+
maxTokens?: number;
|
|
65
|
+
onProgress?: (event: ProgressEvent) => void;
|
|
66
|
+
validate: (responseText: string) => ValidationResult<T>;
|
|
67
|
+
taskName: string;
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
/** Phase label stamped on Before/After model-call hook events. */
|
|
70
|
+
phase?: string;
|
|
71
|
+
onBeforeModelCall?: OnBeforeModelCall;
|
|
72
|
+
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
|
+
onBeforePrompt?: OnBeforePrompt;
|
|
79
|
+
promptVersion?: string;
|
|
80
|
+
}
|
|
81
|
+
interface ValidationResult<T> {
|
|
82
|
+
valid: boolean;
|
|
83
|
+
result?: T;
|
|
84
|
+
errors?: string[];
|
|
85
|
+
retryFeedback?: string;
|
|
86
|
+
}
|
|
87
|
+
interface AiLoopResult<T> {
|
|
88
|
+
result: T;
|
|
89
|
+
session: AiSession;
|
|
90
|
+
usage?: TokenUsage;
|
|
91
|
+
attempts: number;
|
|
92
|
+
durationMs: number;
|
|
93
|
+
/** Stable call IDs for each model.generate invocation this loop made, in order. */
|
|
94
|
+
callIds: string[];
|
|
95
|
+
}
|
|
96
|
+
declare function runAiLoop<T>(params: AiLoopParams<T>): Promise<AiLoopResult<T>>;
|
|
97
|
+
declare function extractOutputTag(text: string): string | null;
|
|
98
|
+
//#endregion
|
|
99
|
+
export { AfterModelCallInfo, BeforeModelCallInfo, BeforePromptInfo, OnAfterModelCall, OnBeforeModelCall, OnBeforePrompt, ValidationResult, estimatedCharactersPerToken, extractOutputTag, runAiLoop };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{WorkflowError as e}from"../errors/workflow.js";import{AIError as t}from"../errors/ai.js";import{TaskInputError as n}from"../errors/task.js";import{appendAssistantMessage as r,appendUserMessage as i,createAiSession as a}from"../ai/session.js";import{addUsage as o}from"../ai/types.js";import{randomUUID as s}from"node:crypto";const c=2.8;async function l(o){let{model:c,userPrompt:l,maxRetries:f=3,maxTokens:p=16384,onProgress:m,validate:h,taskName:g,signal:_,phase:v=g,onBeforeModelCall:y,onAfterModelCall:b,promptVersion:x}=o;if(!o.session&&!o.systemPrompt)throw new n(`${g}: either 'session' or 'systemPrompt' must be provided`);let S=Date.now(),C;o.session?C=i(o.session,l):(C=a(o.systemPrompt),C=i(C,l));let w,T=[],E=f+1,D=[];for(let t=0;t<=f;t++){_?.throwIfAborted();let n=t+1;if(m?.({type:`attempt_start`,attempt:n,maxAttempts:E}),o.onBeforePrompt){let t=(C.systemPrompt?.length??0)+C.messages.reduce((e,t)=>e+t.content.length,0);if(!await o.onBeforePrompt({phase:v,attempt:n,maxAttempts:E,charCount:t,messageCount:C.messages.length,tokenEstimate:Math.ceil(t/2.8)}))throw new e(`CANCELLED`,`Prompt gate declined by consumer (onBeforePrompt returned false)`)}let a=s();D.push(a);let l=Date.now();y?.({phase:v,attempt:n,maxAttempts:E,callId:a});let g;try{g=await c.generate({system:C.systemPrompt,messages:[...C.messages],maxTokens:p,temperature:0,signal:_})}catch(t){let r=t instanceof Error?t.message:String(t);throw b?.({phase:v,attempt:n,callId:a,success:!1,durationMs:Date.now()-l,error:{code:t?.code??`UNKNOWN`,message:r},promptVersion:x}),u(t)||_?.aborted?new e(`CANCELLED`,r,{cause:t}):t}b?.({phase:v,attempt:n,callId:a,success:!0,durationMs:Date.now()-l,usage:g.usage,promptVersion:x}),w=d(w,g.usage);let O=h(g.text);if(O.valid&&O.result!==void 0)return C=r(C,g.text),{result:O.result,session:C,usage:w,attempts:n,durationMs:Date.now()-S,callIds:D};T=O.errors??[`Unknown validation error`],m?.({type:`validation_failed`,attempt:n,maxAttempts:E,errors:T}),t<f&&O.retryFeedback&&(C=r(C,g.text),C=i(C,O.retryFeedback))}throw new t(`VALIDATION_EXHAUSTED`,`Failed to get valid ${g} after ${E} attempts. Last errors:\n${T.join(`
|
|
2
|
+
`)}`)}function u(e){return e instanceof Error&&(e.name===`AbortError`||e.name===`DOMException`)}function d(e,t){if(!e)return t;if(!t)return e;let n={...e};return o(n,t),n}function f(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{c as estimatedCharactersPerToken,f as extractOutputTag,l as runAiLoop};
|
|
@@ -0,0 +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,signal:o.signal,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};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ComposeHunk } from "../types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/tasks/analyze/prompt.d.ts
|
|
4
|
+
declare function buildAnalyzeSystemPrompt(): string;
|
|
5
|
+
declare function buildAnalyzeUserPrompt(params: {
|
|
6
|
+
hunks: ComposeHunk[];
|
|
7
|
+
commitMessages?: string[];
|
|
8
|
+
commitOrder?: string[];
|
|
9
|
+
branchName?: string;
|
|
10
|
+
baseBranch?: string;
|
|
11
|
+
issueUrl?: string;
|
|
12
|
+
pullRequestUrl?: string;
|
|
13
|
+
instructions?: string;
|
|
14
|
+
}): string;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { buildAnalyzeSystemPrompt, buildAnalyzeUserPrompt };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import{analysisResultSchema as e}from"./schema.js";import{z as t}from"zod";const n=JSON.stringify(t.toJSONSchema(e),null,2);function r(){return`You are an advanced AI 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
|
+
|
|
3
|
+
Your analysis must produce:
|
|
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.
|
|
5
|
+
2. **Concerns** — distinct themes, features, or purposes within the changes. Each concern should represent a logically independent unit of work.
|
|
6
|
+
3. **Dependencies** — relationships between concerns where one depends on or builds upon another.
|
|
7
|
+
4. **Hunk annotations** — for each hunk, which concern it belongs to and what role it plays.
|
|
8
|
+
5. **Ordering constraints** — pairs of hunks where one provides something (defines a file, function, type, config entry, etc.) that the other consumes (imports, calls, references, uses). The provider hunk must appear in the same commit as or an earlier commit than the consumer hunk. This does NOT mean they must be in different commits — they can absolutely be in the same commit if that makes sense. It simply means the consumer cannot appear in a commit that precedes the provider's commit.
|
|
9
|
+
|
|
10
|
+
Look for these patterns:
|
|
11
|
+
- A hunk adds a script/file, another hunk references that file path in configuration, package.json, or imports
|
|
12
|
+
- A hunk defines a function, class, or type, another hunk imports or calls it
|
|
13
|
+
- A hunk creates a module, another hunk adds it to an index/barrel export
|
|
14
|
+
- A hunk adds a dependency, another hunk uses that dependency
|
|
15
|
+
|
|
16
|
+
Rules:
|
|
17
|
+
- Every hunk must be annotated with exactly one concern
|
|
18
|
+
- Concerns should be named clearly and concisely
|
|
19
|
+
- Dependencies should capture real code-level relationships, not just thematic similarity
|
|
20
|
+
- Ordering constraints should capture concrete provider/consumer relationships between specific hunks, not vague thematic ordering
|
|
21
|
+
- Your summary should be actionable — someone reading it should understand the full scope of changes
|
|
22
|
+
|
|
23
|
+
## Required Output Format
|
|
24
|
+
|
|
25
|
+
Output your analysis as JSON inside a <output> tag matching this schema:
|
|
26
|
+
|
|
27
|
+
\`\`\`json
|
|
28
|
+
${n}
|
|
29
|
+
\`\`\`
|
|
30
|
+
|
|
31
|
+
Return only the <output> tag and no other text.`}function i(e){let{hunks:t,commitMessages:n,commitOrder:r,branchName:i,baseBranch:a,issueUrl:o,pullRequestUrl:s,instructions:c}=e,l=`Analyze the following code changes:\n\n<hunks>\n${JSON.stringify(t.map(e=>({index:e.index,fileName:e.fileName,hunkHeader:e.hunkHeader,content:e.content,...e.isBinary?{isBinary:!0}:{},...e.isRename?{isRename:!0}:{},...e.originalFileName?{originalFileName:e.originalFileName}:{}})))}\n</hunks>`,u=[];if(i&&u.push(`Branch: ${i}`),a&&u.push(`Base branch: ${a}`),o&&u.push(`Issue: ${o}`),s&&u.push(`Pull request: ${s}`),u.length>0&&(l+=`\n\n<context>\n${u.join(`
|
|
32
|
+
`)}\n</context>`),n?.length){let e=n.map((e,t)=>({index:t,sha:r?.[t],message:e}));l+=`\n\n<commit_messages>\n${JSON.stringify(e)}\n</commit_messages>`}return c&&(l+=`\n\nCarefully follow these additional instructions, but do not deviate from the output structure:\n${c}`),l+=`
|
|
33
|
+
|
|
34
|
+
Now analyze the changes and return your structured analysis. Return only the <output> tag and no other text.`,l}export{r as buildAnalyzeSystemPrompt,i as buildAnalyzeUserPrompt};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#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>;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { analysisResultSchema };
|
|
@@ -0,0 +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};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ProgressEvent, TokenUsage } from "../../ai/types.js";
|
|
2
|
+
import { ComposeHunk, PromptOverride } from "../types.js";
|
|
3
|
+
import { OnAfterModelCall, OnBeforeModelCall, OnBeforePrompt } from "../ai-loop.js";
|
|
4
|
+
import { ComposeSession } from "../session.js";
|
|
5
|
+
import { AiModelPort } from "@gitkraken/shared-tools";
|
|
6
|
+
|
|
7
|
+
//#region src/tasks/analyze/types.d.ts
|
|
8
|
+
interface AnalyzeInput {
|
|
9
|
+
hunks?: ComposeHunk[];
|
|
10
|
+
commitMessages?: string[];
|
|
11
|
+
commitOrder?: string[];
|
|
12
|
+
branchName?: string;
|
|
13
|
+
baseBranch?: string;
|
|
14
|
+
issueUrl?: string;
|
|
15
|
+
pullRequestUrl?: string;
|
|
16
|
+
instructions?: string;
|
|
17
|
+
model: AiModelPort;
|
|
18
|
+
session?: ComposeSession;
|
|
19
|
+
maxRetries?: number;
|
|
20
|
+
onProgress?: (event: ProgressEvent) => void;
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
onBeforeModelCall?: OnBeforeModelCall;
|
|
23
|
+
onAfterModelCall?: OnAfterModelCall;
|
|
24
|
+
onBeforePrompt?: OnBeforePrompt;
|
|
25
|
+
/** Optional prompt override. When set, replaces or decorates the built-in prompts. */
|
|
26
|
+
promptOverride?: PromptOverride;
|
|
27
|
+
}
|
|
28
|
+
interface Concern {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
hunkIndices: number[];
|
|
33
|
+
}
|
|
34
|
+
interface ConcernDependency {
|
|
35
|
+
from: string;
|
|
36
|
+
to: string;
|
|
37
|
+
reason: string;
|
|
38
|
+
}
|
|
39
|
+
interface HunkAnnotation {
|
|
40
|
+
hunkIndex: number;
|
|
41
|
+
concernId: string;
|
|
42
|
+
purpose: string;
|
|
43
|
+
}
|
|
44
|
+
interface HunkOrderingConstraint {
|
|
45
|
+
/** Hunk that provides/defines/creates something */
|
|
46
|
+
provider: number;
|
|
47
|
+
/** Hunk that consumes/references/uses it */
|
|
48
|
+
consumer: number;
|
|
49
|
+
kind: 'file-reference' | 'import' | 'function-call' | 'type-reference' | 'config-reference' | 'other';
|
|
50
|
+
reason: string;
|
|
51
|
+
}
|
|
52
|
+
interface AnalysisResult {
|
|
53
|
+
title: string;
|
|
54
|
+
summary: string;
|
|
55
|
+
concerns: Concern[];
|
|
56
|
+
dependencies: ConcernDependency[];
|
|
57
|
+
hunkAnnotations: HunkAnnotation[];
|
|
58
|
+
orderingConstraints: HunkOrderingConstraint[];
|
|
59
|
+
}
|
|
60
|
+
interface AnalyzeOutput {
|
|
61
|
+
result: AnalysisResult;
|
|
62
|
+
session: ComposeSession;
|
|
63
|
+
usage?: TokenUsage;
|
|
64
|
+
attempts: number;
|
|
65
|
+
durationMs: number;
|
|
66
|
+
callIds: string[];
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { AnalysisResult, AnalyzeInput, AnalyzeOutput, Concern, ConcernDependency, HunkAnnotation, HunkOrderingConstraint };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ValidationResult } from "../ai-loop.js";
|
|
2
|
+
import { AnalysisResult } from "./types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/tasks/analyze/validator.d.ts
|
|
5
|
+
declare function validateAnalyzeResponse(responseText: string, totalHunks: number): ValidationResult<AnalysisResult>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { validateAnalyzeResponse };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import{extractOutputTag as e}from"../ai-loop.js";import{analysisResultSchema as t}from"./schema.js";function n(n,a){try{let o=e(n);if(!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{return r([`Failed to parse response as valid JSON`],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
|
+
<output>
|
|
3
|
+
{
|
|
4
|
+
"title": "Short PR-style title (under 72 chars)",
|
|
5
|
+
"summary": "...",
|
|
6
|
+
"concerns": [{ "id": "c1", "name": "...", "description": "...", "hunkIndices": [0, 1] }],
|
|
7
|
+
"dependencies": [{ "from": "c1", "to": "c2", "reason": "..." }],
|
|
8
|
+
"hunkAnnotations": [{ "hunkIndex": 0, "concernId": "c1", "purpose": "..." }],
|
|
9
|
+
"orderingConstraints": [{ "provider": 0, "consumer": 1, "kind": "import", "reason": "..." }]
|
|
10
|
+
}
|
|
11
|
+
</output>
|
|
12
|
+
|
|
13
|
+
Return only the <output> tag and no other text.`}export{n as validateAnalyzeResponse};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { GroupOutput } from "../group/types.js";
|
|
2
|
+
import { ComposeGroupInput } from "./types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/tasks/compose-group/compose-group.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Balanced-mode task that combines analysis reasoning with grouping.
|
|
7
|
+
*
|
|
8
|
+
* The AI internally analyzes concerns, dependencies, and ordering constraints
|
|
9
|
+
* before producing a GroupingResult. The output uses the same schema and
|
|
10
|
+
* validator as the standard group task, but the prompt instructs deeper
|
|
11
|
+
* reasoning than a standalone group call.
|
|
12
|
+
*/
|
|
13
|
+
declare function composeGroup(input: ComposeGroupInput): Promise<GroupOutput>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { composeGroup };
|
|
@@ -0,0 +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,signal:o.signal,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};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ComposeHunk } from "../types.js";
|
|
2
|
+
import { ConventionDescriptor } from "../../git/utils/conventions.js";
|
|
3
|
+
import { GroupingStrategy } from "../group/types.js";
|
|
4
|
+
|
|
5
|
+
//#region src/tasks/compose-group/prompt.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* System prompt for balanced-mode compose-group.
|
|
8
|
+
*
|
|
9
|
+
* Combines analysis reasoning (identify concerns, dependencies, ordering)
|
|
10
|
+
* with grouping instructions. The AI reasons about codependencies internally
|
|
11
|
+
* but only outputs GroupingResult — no separate AnalysisResult.
|
|
12
|
+
*/
|
|
13
|
+
declare function buildComposeGroupSystemPrompt(conventions?: ConventionDescriptor, mode?: 'commits' | 'branches', strategy?: GroupingStrategy, includeAnalysis?: boolean, lightweight?: boolean): string;
|
|
14
|
+
declare function buildComposeGroupUserPrompt(params: {
|
|
15
|
+
hunks: ComposeHunk[];
|
|
16
|
+
commitMessages?: string[];
|
|
17
|
+
commitOrder?: string[];
|
|
18
|
+
branchName?: string;
|
|
19
|
+
baseBranch?: string;
|
|
20
|
+
issueUrl?: string;
|
|
21
|
+
pullRequestUrl?: string;
|
|
22
|
+
instructions?: string;
|
|
23
|
+
condensed?: boolean;
|
|
24
|
+
}): string;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { buildComposeGroupSystemPrompt, buildComposeGroupUserPrompt };
|