@gitkraken/compose-tools 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +15 -3
  2. package/README.md +15 -8
  3. package/dist/ai/types.d.ts +25 -9
  4. package/dist/context.d.ts +66 -0
  5. package/dist/context.js +1 -0
  6. package/dist/git/apply-target.js +1 -1
  7. package/dist/git/index.js +1 -1
  8. package/dist/index.d.ts +3 -1
  9. package/dist/index.js +1 -1
  10. package/dist/tasks/ai-loop.d.ts +29 -3
  11. package/dist/tasks/ai-loop.js +1 -1
  12. package/dist/tasks/analyze/analyze.js +1 -1
  13. package/dist/tasks/analyze/prompt.d.ts +6 -2
  14. package/dist/tasks/analyze/prompt.js +5 -5
  15. package/dist/tasks/analyze/types.d.ts +11 -0
  16. package/dist/tasks/compose-group/compose-group.js +1 -1
  17. package/dist/tasks/compose-group/prompt.d.ts +6 -1
  18. package/dist/tasks/compose-group/prompt.js +31 -31
  19. package/dist/tasks/compose-group/types.d.ts +16 -0
  20. package/dist/tasks/group/group.js +1 -1
  21. package/dist/tasks/group/prompt.d.ts +7 -1
  22. package/dist/tasks/group/prompt.js +21 -21
  23. package/dist/tasks/group/repair.d.ts +39 -0
  24. package/dist/tasks/group/repair.js +16 -0
  25. package/dist/tasks/group/types.d.ts +17 -1
  26. package/dist/tasks/group/validator.d.ts +6 -0
  27. package/dist/tasks/group/validator.js +6 -6
  28. package/dist/tasks/quick-compose/prompt.d.ts +3 -1
  29. package/dist/tasks/quick-compose/prompt.js +11 -11
  30. package/dist/tasks/quick-compose/quick-compose.js +1 -1
  31. package/dist/tasks/quick-compose/types.d.ts +7 -0
  32. package/dist/tasks/utils/analysis-prompt.d.ts +14 -0
  33. package/dist/tasks/utils/analysis-prompt.js +5 -0
  34. package/dist/tasks/utils/authoring-context.d.ts +47 -0
  35. package/dist/tasks/utils/authoring-context.js +9 -0
  36. package/dist/tasks/utils/budget.d.ts +9 -1
  37. package/dist/tasks/utils/budget.js +1 -1
  38. package/dist/tasks/utils/hunk-summaries.d.ts +19 -0
  39. package/dist/tasks/utils/hunk-summaries.js +5 -0
  40. package/dist/workflows/author.d.ts +101 -0
  41. package/dist/workflows/author.js +16 -0
  42. package/dist/workflows/compose.d.ts +93 -3
  43. package/dist/workflows/compose.js +3 -3
  44. package/dist/workflows/split.js +2 -2
  45. package/dist/workflows/types.d.ts +16 -2
  46. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,10 +1,22 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to this project will be documented in this file.
3
+ ## 0.10.0
4
4
 
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/).
5
+ ### Minor Changes
6
6
 
7
- ## [Unreleased]
7
+ - [#90](https://github.com/gitkraken/core-tools/pull/90) [`19c7d2f`](https://github.com/gitkraken/core-tools/commit/19c7d2f5aadd84159f5eaf6e7172cae6ff70134b) Thanks [@alejandroSuch](https://github.com/alejandroSuch)! - A `group` / `composeGroup` response that leaves hunks unassigned is now repaired with one small targeted call instead of a full retry. After the deterministic dedupe above, the remaining missing indices are sent on their own — those hunks' content plus the response's commits as id and message, no diff, no system prompt, no history — and the model answers with a `{hunkIndex, commitId}` assignment map. Asking for an assignment map rather than a fresh grouping keeps the repair from restructuring decisions the first response already made. Over four eval runs missing hunks accounted for 4 of 29 outright `VALIDATION_EXHAUSTED` failures, each previously costing a re-send of the whole diff. The repair is reported as a `missing-hunks` `ValidationRepair` (distinct from the free `duplicate-hunks` kind) and its call is accounted for like any other: its own entry in `callIds`, its own `onBeforeModelCall` / `onAfterModelCall` pair (flagged `isRepair`, sharing the `attempt` of the response it repairs), its tokens in `usage`, and its prompt through the `onBeforePrompt` gate. At most one repair per loop: a repair response that is itself invalid — malformed, incomplete, naming an unknown commit, or moving a hunk it was not asked about — falls through to the existing retry behaviour, as does a repair call that fails in transport. Conversation-mode `group` (hunks in the model's context, not in the task input) has no hunk bodies to ask about and retries as before, and a repair prompt that would exceed the input-token budget drops the hunk bodies and asks on file metadata alone. The repair honours `condenseHunks` / `condensed` and `redactHunkContent` like the main prompt; in `branches` mode each commit in the list carries its branch name and title. It is skipped, straight to the retry, when more than a quarter of the hunks are missing (a repair of two or fewer hunks is always tried), since past that the repair re-sends most of the diff. Declining the repair prompt at `onBeforePrompt` falls back to the retry instead of aborting. `feedbackTargetCallId` points at the response a repair completed, not at the repair call.
8
+
9
+ ## [0.9.0] - 2026-10-05
10
+
11
+ ### Added
12
+
13
+ - Adds `AuthoringContext` — a structured, optional description of what a change's author knows that the diff cannot show: intent, issue (url/key/title/body), pull request, spec, notes, per-file edit rationales, ordering dependencies, and conventions. `ComposeContext` now extends it, so every compose entry point accepts the full shape; the flat `issueUrl`/`pullRequestUrl` fields are deprecated in favor of `issue.url`/`pullRequest.url` and remain honored (the structured field wins when both are set). Everything beyond the URLs is rendered into prompts as escaped, per-field-capped, tagged data behind an explicit data-not-instructions notice, so tracker text cannot act as a prompt injection and one verbose issue body cannot crowd the hunks out of the token budget; the URL fields themselves are emitted as plain context lines, forced to a single line and length-capped. Supplying nothing leaves every prompt byte-identical to before. Type exports: `AuthoringContext`, `AuthoringDependency`, `AuthoringEdit`, `AuthoringIssue`, `AuthoringNote`, `AuthoringPullRequest`.
14
+ - Adds `collectComposeHunks` and `buildAuthoredComposePlan`, the two halves of an authored-plan flow in which the caller writes the commit plan and no model is called at any point. `collectComposeHunks` runs the collection phase alone and returns the hunk index map, the safety snapshot, and the resolved source/target — the parts a caller cannot derive itself. `buildAuthoredComposePlan` validates a caller-written commit list against the collected indices (each index claimed exactly once; unknown, duplicate, and unassigned indices are rejected with the offending files named in the error) and shapes it into a `ComposePlan` for `applyComposePlan`. Built for consumers whose planner is the coding agent that made the changes: the agent already holds the full context of its own edits, so the grouping decision can come from it directly instead of from a model prompt carrying the diff. The produced plan is a single branch group — multi-branch splitting stays with the model-driven flow. Type exports: `CollectComposeHunksInput`, `CollectComposeHunksResult`, `AuthoredCommit`, `BuildAuthoredComposePlanInput`.
15
+ - Adds `analysis` and `condenseHunks` to `compose` and `composePlan` — inputs that let a caller who already knows the change inform the composing model instead of paying to have it inferred. `analysis` supplies the breakdown the `analyze` phase exists to derive from the diff; at `depth: 'deep'` that phase is skipped entirely (still reported, with zero attempts) and the grouping model is no longer asked to produce its own. `condenseHunks` mode sends hunk *metadata only* — index, file name, hunk header, line counts — omitting every patch body from the prompts, for the situation where the answering model already has the change in front of it (an agent composing edits it made itself). Supplying neither leaves every prompt byte-identical to before. `analysis` cannot be combined with a resumed `session` — continuation prompts never carry it — and the combination is rejected with `ComposeWorkflowInputError`.
16
+ - Adds `hunkSummaries` — one-line, caller-supplied summaries of what individual hunks do, keyed by hunk index — rendered into the analyze and grouping prompts as an escaped `<hunk-summaries>` block (at `depth: 'deep'` they enter at the analyze phase and the conversation carries them forward). Built for `condenseHunks` mode: with patch bodies absent, headers say where a change is but not what it does, and grouping suffers most where related hunks span files (an export in one file, its import in another). Summaries restore that signal at a fraction of the diff's size, carry no concern structure (so, unlike a supplied `analysis`, they cannot pre-bias the grouping), are capped at 200 characters each, and drop entries for indices that were not collected. Sparse maps are expected — summarize the hunks whose headers don't speak for themselves.
17
+ - Adds `condensed` to `collectComposeHunks` — the returned hunks come back with empty patch bodies (binary payloads dropped), index map only, while collection and the safety snapshot still compute from the real content. For consumers relaying the result to the agent that authored the changes: masking becomes the library's guarantee instead of something every consumer must remember, so the author never pays to read its own diff.
18
+ - Adds `buildAuthoredPlanInstructions` — the agent-facing rules for authoring a plan (the decision JSON shape, the every-index-exactly-once rules, ordering, the hunk-boundary caveat), shipped from the library so consumers embed a function call instead of maintaining prose that can drift from the validator.
19
+ - Adds `SESSION-FLOWS.md` — consumer documentation of the four session interaction flows (detached model informed/uninformed, calling agent as composer for its own or unfamiliar changes), which functions each flow uses, and what the consumer owns (wiring, routing, the collect-to-apply binding).
8
20
 
9
21
  ## [0.8.1] - 2026-08-05
10
22
 
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  AI-powered git change organization. Takes a messy working directory or a branch full of interleaved commits and restructures them into clean, atomic commits on well-named branches — ready for code review.
4
4
 
5
+ Using compose from an MCP server or CLI that a coding agent drives? [SESSION-FLOWS.md](./SESSION-FLOWS.md) documents the four supported interaction flows and how to wire each one.
6
+
5
7
  ## What it does
6
8
 
7
9
  `compose()` collects your changes, sends the diffs through an AI pipeline that understands the logical structure of code changes, and produces clean commits grouped by concern. Depending on configuration, it can:
@@ -254,15 +256,18 @@ Equivalent to calling `composePlan` — useful when you want the sugar `compose`
254
256
  Per-call observability hooks. Informational only — the library doesn't interpret the return.
255
257
 
256
258
  ```typescript
257
- onBeforeModelCall: ({ phase, attempt, maxAttempts, callId }) => {
259
+ onBeforeModelCall: ({ phase, attempt, maxAttempts, callId, isRepair }) => {
258
260
  // phase: 'analyze' | 'group' | 'order' | 'partition' | 'compose-group' | 'quick-compose'
259
261
  // callId: stable UUID — correlates with onAfterModelCall and with result.feedbackTargetCallId
262
+ // isRepair: true for a targeted missing-hunks repair call, which shares the `attempt` of the
263
+ // response it repairs
260
264
  },
261
265
 
262
- onAfterModelCall: ({ phase, attempt, callId, success, durationMs, usage, error, promptVersion, repairs }) => {
266
+ onAfterModelCall: ({ phase, attempt, callId, success, durationMs, usage, error, promptVersion, repairs, isRepair }) => {
263
267
  // Fires on both success and error, after the response validator runs (durationMs still
264
- // measures only the model call). `repairs` lists deterministic fixes applied to an accepted
265
- // response — see ValidationRepair. An exception thrown here is swallowed.
268
+ // measures only the model call). `repairs` lists the fixes applied to an accepted response,
269
+ // see ValidationRepair: `duplicate-hunks` is deterministic and free, `missing-hunks` costs one
270
+ // extra model call. `isRepair` as above. An exception thrown here is swallowed.
266
271
  },
267
272
  ```
268
273
 
@@ -270,10 +275,10 @@ Consumers build their own per-call telemetry from these. No built-in telemetry i
270
275
 
271
276
  ### `onBeforePrompt`
272
277
 
273
- Gating hook — run immediately before each `model.generate`. Returning `false` aborts the workflow with `ComposeWorkflowError('CANCELLED')`. Use for large-prompt confirmations, cost warnings, org policy checks.
278
+ Gating hook — run immediately before each `model.generate`. Returning `false` aborts the workflow with `ComposeWorkflowError('CANCELLED')`, except on a missing-hunks repair call (`isRepair: true`): declining that one skips the repair and falls back to a regular retry, which goes through this gate too. Use for large-prompt confirmations, cost warnings, org policy checks.
274
279
 
275
280
  ```typescript
276
- onBeforePrompt: async ({ phase, attempt, tokenEstimate, charCount, messageCount }) => {
281
+ onBeforePrompt: async ({ phase, attempt, tokenEstimate, charCount, messageCount, isRepair }) => {
277
282
  if (tokenEstimate > 20_000) {
278
283
  return await showUserConfirmation(`Large prompt (${tokenEstimate} tokens). Continue?`);
279
284
  }
@@ -396,6 +401,8 @@ const second = await composePlan({ git, model, depth: 'deep', session: first.ses
396
401
 
397
402
  A resumed task contributes no usage and no callIds — telemetry for that phase reflects the original run.
398
403
 
404
+ A phase's `callIds` has one entry per attempt plus one per missing-hunks repair, so it can be longer than `attempts`.
405
+
399
406
  ### `promptOverrides`
400
407
 
401
408
  Per-task prompt overrides for consumers with their own prompt template services (server-driven templates, provider-specific templates, org-tuned wording).
@@ -453,10 +460,10 @@ Every top-level entry point returns an object with these common fields:
453
460
  | `snapshot` | all paths | Safety hash fingerprint of the collection — carry it to apply time |
454
461
  | `source` | all paths | `{ type, hunks, commitMessages?, branchName?, baseBranch? }` — the hunks the AI operated on |
455
462
  | `session` | all paths | Pass to a subsequent call to resume |
456
- | `phases` | all paths | `PhaseDetail[]` with `{ name, durationMs, attempts, usage?, modelId?, promptVersion?, callIds?, repairs? }` per phase |
463
+ | `phases` | all paths | `PhaseDetail[]` with `{ name, durationMs, attempts, usage?, modelId?, promptVersion?, callIds?, repairs? }` per phase; `callIds` can outnumber `attempts` by the repair calls |
457
464
  | `usage` | all paths | Aggregated `{ inputTokens, outputTokens, ... }` across all AI calls |
458
465
  | `diffStats` | sugar / plan | `{ fileCount, hunkCount, addedLines, removedLines }` |
459
- | `feedbackTargetCallId` | when AI ran | callId of the last validated model response — stamp this on thumbs up/down feedback events |
466
+ | `feedbackTargetCallId` | when AI ran | callId of the last validated model response (after a missing-hunks repair, the response it completed, not the repair call) — stamp this on thumbs up/down feedback events |
460
467
  | `hunksFilteredCount` | when `hunkFilter` ran | Number of hunks the filter dropped |
461
468
  | `commitShas` | apply paths | `{ commitId: sha }` mapping for every commit written |
462
469
  | `undoId` | apply paths | Pass to `undoCompose()` to reverse |
@@ -31,14 +31,27 @@ interface DuplicateHunksRepair {
31
31
  detail: string;
32
32
  }
33
33
  /**
34
- * A deterministic fix a response validator applied to an AI response that would otherwise have
35
- * been rejected, sparing a retry. A repaired response is treated as valid — repairs are reported
36
- * for observability, not as a failure signal.
34
+ * Hunk indices assigned to no commit were placed by asking the model where they belong, sending
35
+ * only those hunks and the commit list. Costs one extra model call, which shows up in the usual
36
+ * call, token and cost accounting.
37
+ */
38
+ interface MissingHunksRepair {
39
+ kind: 'missing-hunks';
40
+ /** The hunk indices the repair placed, ascending. */
41
+ hunkIndices: number[];
42
+ /** Unstable log string. */
43
+ detail: string;
44
+ }
45
+ /**
46
+ * A fix a response validator applied to an AI response that would otherwise have been rejected,
47
+ * sparing a full retry. A repaired response is treated as valid — repairs are reported for
48
+ * observability, not as a failure signal. `duplicate-hunks` is deterministic and free;
49
+ * `missing-hunks` costs one small extra model call, visible in the usual call and cost accounting.
37
50
  *
38
- * This covers duplicate-hunk repair only. It is not an exhaustive record of every correction a
39
- * validator makes: refine's locked-content substitution and `restoreLockedOrdering` stay silent.
51
+ * This is not an exhaustive record of every correction a validator makes: refine's locked-content
52
+ * substitution and `restoreLockedOrdering` stay silent.
40
53
  */
41
- type ValidationRepair = DuplicateHunksRepair;
54
+ type ValidationRepair = DuplicateHunksRepair | MissingHunksRepair;
42
55
  interface TokenUsage {
43
56
  inputTokens: number;
44
57
  outputTokens: number;
@@ -58,10 +71,13 @@ interface PhaseDetail {
58
71
  modelId?: string;
59
72
  /** Identifier of the prompt template used for this phase, when a consumer override supplied one. */
60
73
  promptVersion?: string;
61
- /** Stable call IDs for each model.generate invocation in this phase (one per attempt). */
74
+ /**
75
+ * Stable call IDs for each model.generate invocation in this phase: one per attempt, plus one
76
+ * for each targeted repair, so the count can exceed `attempts`.
77
+ */
62
78
  callIds?: string[];
63
79
  /**
64
- * Deterministic repairs applied to this phase's accepted response instead of spending a retry.
80
+ * Repairs applied to this phase's accepted response instead of spending a retry.
65
81
  * Undefined when the response needed no repair.
66
82
  */
67
83
  repairs?: ValidationRepair[];
@@ -73,4 +89,4 @@ interface ProgressEvent {
73
89
  errors?: string[];
74
90
  }
75
91
  //#endregion
76
- export { DuplicateHunksRepair, PhaseDetail, ProgressEvent, RepairScope, RepairedHunkClaim, TokenUsage, ValidationRepair, addUsage, emptyUsage };
92
+ export { DuplicateHunksRepair, MissingHunksRepair, PhaseDetail, ProgressEvent, RepairScope, RepairedHunkClaim, TokenUsage, ValidationRepair, addUsage, emptyUsage };
@@ -0,0 +1,66 @@
1
+ //#region src/context.d.ts
2
+ /**
3
+ * What the author of a change knows that the diff cannot show: the goal, the ticket, the reasoning
4
+ * behind individual edits, and the order things had to happen in.
5
+ *
6
+ * Every field is optional and every field is a hint. A consumer must still behave correctly when it
7
+ * is absent or empty, and must still validate whatever a model produces from it.
8
+ *
9
+ * The producer is deliberately unspecified. A coding agent writes it from its own session, a host
10
+ * application generates it from a task record, or a person writes it by hand — the consuming
11
+ * library cannot tell the difference and does not need to.
12
+ *
13
+ * **Treat every string here as untrusted.** Issue and pull-request bodies are third-party text on
14
+ * its way into a prompt, so a consumer rendering them must escape them and label them as data.
15
+ */
16
+ interface AuthoringContext {
17
+ /** Why this work exists, in the author's own words. */
18
+ intent?: string;
19
+ issue?: AuthoringIssue;
20
+ pullRequest?: AuthoringPullRequest;
21
+ /** The design or plan the author worked from, when there was a written one. */
22
+ spec?: string;
23
+ /** Decisions and findings recorded during the work. */
24
+ notes?: AuthoringNote[];
25
+ /** Per-file rationale: why this path was touched at all. */
26
+ edits?: AuthoringEdit[];
27
+ /** Ordering constraints the author already knows. */
28
+ dependencies?: AuthoringDependency[];
29
+ /** Repository rules the author was already following. */
30
+ conventions?: string;
31
+ }
32
+ interface AuthoringIssue {
33
+ url?: string;
34
+ /** Tracker-native identifier, e.g. `AGP-430` or `#1234`. */
35
+ key?: string;
36
+ title?: string;
37
+ body?: string;
38
+ }
39
+ interface AuthoringPullRequest {
40
+ url?: string;
41
+ title?: string;
42
+ body?: string;
43
+ }
44
+ interface AuthoringNote {
45
+ title?: string;
46
+ content: string;
47
+ }
48
+ interface AuthoringEdit {
49
+ path: string;
50
+ rationale: string;
51
+ }
52
+ /**
53
+ * `before` must land ahead of `after`. Each names either a repository path or a free-form concern
54
+ * label; a consumer resolves them against whatever it is grouping and ignores what it cannot match.
55
+ *
56
+ * Deliberately looser than the analyzer's `ConcernDependency`, which relates two concern ids inside
57
+ * one `AnalysisResult`. This shape has no analysis to reference — the author states orderings in
58
+ * its own words before any concern structure exists, and the ends here are prose, not identifiers.
59
+ */
60
+ interface AuthoringDependency {
61
+ before: string;
62
+ after: string;
63
+ why?: string;
64
+ }
65
+ //#endregion
66
+ export { AuthoringContext, AuthoringDependency, AuthoringEdit, AuthoringIssue, AuthoringNote, AuthoringPullRequest };
@@ -0,0 +1 @@
1
+ export{};
@@ -1 +1 @@
1
- import{GitError as e}from"../errors/git.js";import{applyPatchToWorkdir as t,checkout as n,cleanWorkdir as r,currentBranch as i,deleteBranch as a,discardWorkdirChanges as o,reset as s,revParse as c,revParseVerify as l,updateRef as u}from"./port-dispatch.js";import{applyBranchStructure as d}from"./apply-branches.js";import{resolveWorkdirLayers as f}from"./diff-collector.js";import{reparentDescendants as p}from"./reparent.js";import{applyCommitChain as m,applyRangeRewrite as h,buildCommitChain as g,moveRefsAtSha as _}from"./commit-chain.js";import{verifyMultiBranchDiff as v,verifyResultingDiff as y}from"./safety.js";async function b(t){let{git:n,source:r,target:i,branchPlans:a,allOrderedCommits:o,hunks:s,snapshot:l,collection:u,signing:p,authorAttribution:_,stashLabel:v,leftoverPatch:b}=t,C=r.type===`commit-range`&&r.includeWorkdir!=null?f(r.includeWorkdir):void 0;if(i.type===`rewrite-range`){if(!u.branchName||!u.branchSha)throw new e(`OPERATION_FAILED`,`rewrite-range target requires a source that records branchName and branchSha (branch or commit-range).`);let t;try{t=await c(n,`${i.rangeFrom}^`)}catch{throw new e(`OPERATION_FAILED`,`rewrite-range target's rangeFrom (${i.rangeFrom.slice(0,8)}) has no parent (root commit) — rewriting the root commit is not currently supported.`)}let{headSha:r,shaMap:a}=await g({git:n,baseSha:t,commits:o,hunks:s,signing:p,authorAttribution:_}),d=await y(n,t,r,l);if(!d.valid)throw new e(`SAFETY_CHECK_FAILED`,`Diff verification failed: ${d.error}`);let f=await h({git:n,branch:i.branch,rangeFrom:i.rangeFrom,rangeTo:i.rangeTo,newHeadSha:r,originalBranchSha:u.branchSha,stashLabel:v,leftoverPatch:b,includeWorkdir:C});return{commitShas:Object.fromEntries(a),mutations:D(f.refMovements),stashConflict:f.stashConflict,stashNotRestored:f.stashNotRestored}}if(u.branchName&&!u.mergeBaseSha)throw new e(`OPERATION_FAILED`,`Branch source is missing mergeBaseSha — cannot determine base commit`);let w=u.mergeBaseSha??await c(n,`HEAD`),E=k(r,i);if(i.type===`auto`&&a.length>1)return x(n,w,a,s,l,u,E,p,_);if(i.type===`auto`&&a.length===1&&a[0].partitioning)return S(n,w,a[0],s,l,u,E,p,_,v,C);let{headSha:j,shaMap:M}=await g({git:n,baseSha:w,commits:o,hunks:s,signing:p,authorAttribution:_}),N=Object.fromEntries(M),P=await y(n,w,j,l);if(!P.valid)throw new e(`SAFETY_CHECK_FAILED`,`Diff verification failed: ${P.error}`);if(i.type===`branch`)return A({git:n,source:r,target:i,newHeadSha:j,collection:u,stashLabel:v,leftoverPatch:b,commitShas:N});if(i.type===`auto`){let e=a[0]?.branchGroup.name||u.branchName;if(e){let t=!E&&u.branchName&&u.branchSha?{originalBranchName:u.branchName,originalBranchSha:u.branchSha}:{},r={branchName:e,title:a[0]?.branchGroup.title??``,description:a[0]?.branchGroup.description??``,commitIds:o.map(e=>e.id)};await d({git:n,branches:[{partitions:[r],tipSha:j}],...t});let i=O([r],N,t.originalBranchName,t.originalBranchSha);return{partitions:[r],commitShas:N,mutations:i}}let t=await m({git:n,newHeadSha:j,stashLabel:v,includeWorkdir:C});return{commitShas:N,mutations:T(),stashConflict:t.stashConflict,stashNotRestored:t.stashNotRestored}}let F=await m({git:n,newHeadSha:j,stashLabel:v,leftoverPatch:b,includeWorkdir:C});return{commitShas:N,mutations:T(),stashConflict:F.stashConflict,stashNotRestored:F.stashNotRestored}}async function x(t,n,i,a,s,c,l,u,f){let p=[],m=[],h=[],_=[],y={};for(let e of i){let r=C(e),{headSha:i,shaMap:o}=await g({git:t,baseSha:n,commits:r,hunks:a,signing:u,authorAttribution:f});h.push(i);for(let[e,t]of o)y[e]=t;if(e.partitioning&&e.partitioning.partitions.length>1){let t=w(e.partitioning.partitions,o);p.push(...t),m.push(t),_.push({partitions:t,tipSha:i})}else{let t={branchName:e.branchGroup.name,title:e.branchGroup.title,description:e.branchGroup.description,commitIds:r.map(e=>o.get(e.id)??e.id)};p.push(t),m.push([t]),_.push({partitions:[t],tipSha:i})}}let b=await v(t,n,h,s);if(!b.valid)throw new e(`SAFETY_CHECK_FAILED`,`Diff verification failed: ${b.error}`);let x=!l&&c.branchName&&c.branchSha?{originalBranchName:c.branchName,originalBranchSha:c.branchSha}:{};return await d({git:t,branches:_,...x}),!c.branchName&&!l&&(await o(t),c.untrackedIncluded&&await r(t)),{partitions:p,partitionGroups:m,commitShas:y,mutations:O(p,y,x.originalBranchName,x.originalBranchSha)}}async function S(t,n,i,a,s,c,l,u,f,p,h){let{headSha:_,shaMap:v}=await g({git:t,baseSha:n,commits:C(i),hunks:a,signing:u,authorAttribution:f}),b=await y(t,n,_,s);if(!b.valid)throw new e(`SAFETY_CHECK_FAILED`,`Diff verification failed: ${b.error}`);let x=w(i.partitioning.partitions,v),S=Object.fromEntries(v);if(x.length>1){let e=!l&&c.branchName&&c.branchSha?{originalBranchName:c.branchName,originalBranchSha:c.branchSha}:{};return await d({git:t,branches:[{partitions:x,tipSha:_}],...e}),!c.branchName&&!l&&(await o(t),c.untrackedIncluded&&await r(t,{force:!0,directories:!0})),{partitions:x,commitShas:S,mutations:O(x,S,e.originalBranchName,e.originalBranchSha)}}let E=await m({git:t,newHeadSha:_,stashLabel:p,includeWorkdir:h});return{partitions:x,commitShas:S,mutations:T(),stashConflict:E.stashConflict,stashNotRestored:E.stashNotRestored}}function C(t){return t.orderedCommitIds.map(n=>{let r=t.branchGroup.commits.find(e=>e.id===n);if(!r)throw new e(`OPERATION_FAILED`,`Commit ${n} not found in branch '${t.branchGroup.id}'`);return r})}function w(t,n){return t.map(t=>({...t,commitIds:t.commitIds.map(t=>{let r=n.get(t);if(!r)throw new e(`OPERATION_FAILED`,`No SHA found for commit ID '${t}'`);return r})}))}function T(){return{createdRefs:[],deletedRefs:[],movedRefs:[],composedBranchCount:0}}function E(t){if(t.startsWith(`refs/heads/`))return{kind:`branch`,name:t.slice(11)};if(t.startsWith(`refs/tags/`))return{kind:`tag`,name:t.slice(10)};throw new e(`OPERATION_FAILED`,`Unexpected refname '${t}' in apply mutations — only refs/heads/* and refs/tags/* are tracked.`)}function D(e){let t=[];for(let n of e){let{kind:e,name:r}=E(n.refname);t.push({kind:e,name:r,previousSha:n.previousSha,newSha:n.newSha})}return{createdRefs:[],deletedRefs:[],movedRefs:t,composedBranchCount:1}}function O(e,t,n,r){let i=e.map(e=>{let n=e.commitIds[e.commitIds.length-1];return{kind:`branch`,name:e.branchName,sha:t[n]??n}}),a=[];return n&&r&&a.push({kind:`branch`,name:n,sha:r}),{createdRefs:i,deletedRefs:a,movedRefs:[],composedBranchCount:i.length}}function k(e,t){return t.type!==`branch`&&t.type!==`auto`||e.type===`commit-range`?!0:t.preserveOriginal===!0}async function A(r){let{git:o,source:d,target:f,newHeadSha:m,collection:h,leftoverPatch:g,commitShas:v}=r,y=k(d,f),b=d.type===`branch`&&!y&&h.branchName!=null&&h.branchSha!=null;if(b&&f.name===h.branchName)throw new e(`OPERATION_FAILED`,`target 'branch' with name '${f.name}' conflicts with the source branch (preserveOriginal=false). Use target 'rewrite-range' to rewrite the branch in place under its existing name.`);let x=await l(o,`refs/heads/${f.name}`),S=x?{createdRefs:[],deletedRefs:[],movedRefs:[{kind:`branch`,name:f.name,previousSha:x,newSha:m}],composedBranchCount:1}:{createdRefs:[{kind:`branch`,name:f.name,sha:m}],deletedRefs:[],movedRefs:[],composedBranchCount:1},C=await i(o),w=await c(o,`HEAD`);if(await u(o,`refs/heads/${f.name}`,m),b){let e=h.branchSha,r=h.branchName,i=await p({git:o,oldToSha:e,newTopSha:m,sourceBranchName:void 0});for(let e of i.reparentedRefs){let{kind:t,name:n}=E(e.refname);S.movedRefs.push({kind:t,name:n,previousSha:e.oldSha,newSha:e.newSha})}let c=await _(o,e,m,`refs/heads/${r}`);for(let e of c){let{kind:t,name:n}=E(e.refname);S.movedRefs.push({kind:t,name:n,previousSha:e.previousSha,newSha:e.newSha})}C===r&&(await n(o,f.name),await s(o,m,`hard`),g&&await t(o,g)),await a(o,r,{force:!0}),S.deletedRefs.push({kind:`branch`,name:r,sha:e})}else d.type===`workdir`&&!y&&(await s(o,w,`hard`),g&&await t(o,g));return{commitShas:v,mutations:S}}export{b as applyToTarget};
1
+ import{GitError as e}from"../errors/git.js";import{applyPatchToWorkdir as t,checkout as n,cleanWorkdir as r,currentBranch as i,deleteBranch as a,discardWorkdirChanges as o,reset as s,revParse as c,revParseVerify as l,updateRef as u}from"./port-dispatch.js";import{resolveWorkdirLayers as d}from"./diff-collector.js";import{applyBranchStructure as f}from"./apply-branches.js";import{reparentDescendants as p}from"./reparent.js";import{applyCommitChain as m,applyRangeRewrite as h,buildCommitChain as g,moveRefsAtSha as _}from"./commit-chain.js";import{verifyMultiBranchDiff as v,verifyResultingDiff as y}from"./safety.js";async function b(t){let{git:n,source:r,target:i,branchPlans:a,allOrderedCommits:o,hunks:s,snapshot:l,collection:u,signing:p,authorAttribution:_,stashLabel:v,leftoverPatch:b}=t,C=r.type===`commit-range`&&r.includeWorkdir!=null?d(r.includeWorkdir):void 0;if(i.type===`rewrite-range`){if(!u.branchName||!u.branchSha)throw new e(`OPERATION_FAILED`,`rewrite-range target requires a source that records branchName and branchSha (branch or commit-range).`);let t;try{t=await c(n,`${i.rangeFrom}^`)}catch{throw new e(`OPERATION_FAILED`,`rewrite-range target's rangeFrom (${i.rangeFrom.slice(0,8)}) has no parent (root commit) — rewriting the root commit is not currently supported.`)}let{headSha:r,shaMap:a}=await g({git:n,baseSha:t,commits:o,hunks:s,signing:p,authorAttribution:_}),d=await y(n,t,r,l);if(!d.valid)throw new e(`SAFETY_CHECK_FAILED`,`Diff verification failed: ${d.error}`);let f=await h({git:n,branch:i.branch,rangeFrom:i.rangeFrom,rangeTo:i.rangeTo,newHeadSha:r,originalBranchSha:u.branchSha,stashLabel:v,leftoverPatch:b,includeWorkdir:C});return{commitShas:Object.fromEntries(a),mutations:D(f.refMovements),stashConflict:f.stashConflict,stashNotRestored:f.stashNotRestored}}if(u.branchName&&!u.mergeBaseSha)throw new e(`OPERATION_FAILED`,`Branch source is missing mergeBaseSha — cannot determine base commit`);let w=u.mergeBaseSha??await c(n,`HEAD`),E=k(r,i);if(i.type===`auto`&&a.length>1)return x(n,w,a,s,l,u,E,p,_);if(i.type===`auto`&&a.length===1&&a[0].partitioning)return S(n,w,a[0],s,l,u,E,p,_,v,C);let{headSha:j,shaMap:M}=await g({git:n,baseSha:w,commits:o,hunks:s,signing:p,authorAttribution:_}),N=Object.fromEntries(M),P=await y(n,w,j,l);if(!P.valid)throw new e(`SAFETY_CHECK_FAILED`,`Diff verification failed: ${P.error}`);if(i.type===`branch`)return A({git:n,source:r,target:i,newHeadSha:j,collection:u,stashLabel:v,leftoverPatch:b,commitShas:N});if(i.type===`auto`){let e=a[0]?.branchGroup.name||u.branchName;if(e){let t=!E&&u.branchName&&u.branchSha?{originalBranchName:u.branchName,originalBranchSha:u.branchSha}:{},r={branchName:e,title:a[0]?.branchGroup.title??``,description:a[0]?.branchGroup.description??``,commitIds:o.map(e=>e.id)};await f({git:n,branches:[{partitions:[r],tipSha:j}],...t});let i=O([r],N,t.originalBranchName,t.originalBranchSha);return{partitions:[r],commitShas:N,mutations:i}}let t=await m({git:n,newHeadSha:j,stashLabel:v,includeWorkdir:C});return{commitShas:N,mutations:T(),stashConflict:t.stashConflict,stashNotRestored:t.stashNotRestored}}let F=await m({git:n,newHeadSha:j,stashLabel:v,leftoverPatch:b,includeWorkdir:C});return{commitShas:N,mutations:T(),stashConflict:F.stashConflict,stashNotRestored:F.stashNotRestored}}async function x(t,n,i,a,s,c,l,u,d){let p=[],m=[],h=[],_=[],y={};for(let e of i){let r=C(e),{headSha:i,shaMap:o}=await g({git:t,baseSha:n,commits:r,hunks:a,signing:u,authorAttribution:d});h.push(i);for(let[e,t]of o)y[e]=t;if(e.partitioning&&e.partitioning.partitions.length>1){let t=w(e.partitioning.partitions,o);p.push(...t),m.push(t),_.push({partitions:t,tipSha:i})}else{let t={branchName:e.branchGroup.name,title:e.branchGroup.title,description:e.branchGroup.description,commitIds:r.map(e=>o.get(e.id)??e.id)};p.push(t),m.push([t]),_.push({partitions:[t],tipSha:i})}}let b=await v(t,n,h,s);if(!b.valid)throw new e(`SAFETY_CHECK_FAILED`,`Diff verification failed: ${b.error}`);let x=!l&&c.branchName&&c.branchSha?{originalBranchName:c.branchName,originalBranchSha:c.branchSha}:{};return await f({git:t,branches:_,...x}),!c.branchName&&!l&&(await o(t),c.untrackedIncluded&&await r(t)),{partitions:p,partitionGroups:m,commitShas:y,mutations:O(p,y,x.originalBranchName,x.originalBranchSha)}}async function S(t,n,i,a,s,c,l,u,d,p,h){let{headSha:_,shaMap:v}=await g({git:t,baseSha:n,commits:C(i),hunks:a,signing:u,authorAttribution:d}),b=await y(t,n,_,s);if(!b.valid)throw new e(`SAFETY_CHECK_FAILED`,`Diff verification failed: ${b.error}`);let x=w(i.partitioning.partitions,v),S=Object.fromEntries(v);if(x.length>1){let e=!l&&c.branchName&&c.branchSha?{originalBranchName:c.branchName,originalBranchSha:c.branchSha}:{};return await f({git:t,branches:[{partitions:x,tipSha:_}],...e}),!c.branchName&&!l&&(await o(t),c.untrackedIncluded&&await r(t,{force:!0,directories:!0})),{partitions:x,commitShas:S,mutations:O(x,S,e.originalBranchName,e.originalBranchSha)}}let E=await m({git:t,newHeadSha:_,stashLabel:p,includeWorkdir:h});return{partitions:x,commitShas:S,mutations:T(),stashConflict:E.stashConflict,stashNotRestored:E.stashNotRestored}}function C(t){return t.orderedCommitIds.map(n=>{let r=t.branchGroup.commits.find(e=>e.id===n);if(!r)throw new e(`OPERATION_FAILED`,`Commit ${n} not found in branch '${t.branchGroup.id}'`);return r})}function w(t,n){return t.map(t=>({...t,commitIds:t.commitIds.map(t=>{let r=n.get(t);if(!r)throw new e(`OPERATION_FAILED`,`No SHA found for commit ID '${t}'`);return r})}))}function T(){return{createdRefs:[],deletedRefs:[],movedRefs:[],composedBranchCount:0}}function E(t){if(t.startsWith(`refs/heads/`))return{kind:`branch`,name:t.slice(11)};if(t.startsWith(`refs/tags/`))return{kind:`tag`,name:t.slice(10)};throw new e(`OPERATION_FAILED`,`Unexpected refname '${t}' in apply mutations — only refs/heads/* and refs/tags/* are tracked.`)}function D(e){let t=[];for(let n of e){let{kind:e,name:r}=E(n.refname);t.push({kind:e,name:r,previousSha:n.previousSha,newSha:n.newSha})}return{createdRefs:[],deletedRefs:[],movedRefs:t,composedBranchCount:1}}function O(e,t,n,r){let i=e.map(e=>{let n=e.commitIds[e.commitIds.length-1];return{kind:`branch`,name:e.branchName,sha:t[n]??n}}),a=[];return n&&r&&a.push({kind:`branch`,name:n,sha:r}),{createdRefs:i,deletedRefs:a,movedRefs:[],composedBranchCount:i.length}}function k(e,t){return t.type!==`branch`&&t.type!==`auto`||e.type===`commit-range`?!0:t.preserveOriginal===!0}async function A(r){let{git:o,source:d,target:f,newHeadSha:m,collection:h,leftoverPatch:g,commitShas:v}=r,y=k(d,f),b=d.type===`branch`&&!y&&h.branchName!=null&&h.branchSha!=null;if(b&&f.name===h.branchName)throw new e(`OPERATION_FAILED`,`target 'branch' with name '${f.name}' conflicts with the source branch (preserveOriginal=false). Use target 'rewrite-range' to rewrite the branch in place under its existing name.`);let x=await l(o,`refs/heads/${f.name}`),S=x?{createdRefs:[],deletedRefs:[],movedRefs:[{kind:`branch`,name:f.name,previousSha:x,newSha:m}],composedBranchCount:1}:{createdRefs:[{kind:`branch`,name:f.name,sha:m}],deletedRefs:[],movedRefs:[],composedBranchCount:1},C=await i(o),w=await c(o,`HEAD`);if(await u(o,`refs/heads/${f.name}`,m),b){let e=h.branchSha,r=h.branchName,i=await p({git:o,oldToSha:e,newTopSha:m,sourceBranchName:void 0});for(let e of i.reparentedRefs){let{kind:t,name:n}=E(e.refname);S.movedRefs.push({kind:t,name:n,previousSha:e.oldSha,newSha:e.newSha})}let c=await _(o,e,m,`refs/heads/${r}`);for(let e of c){let{kind:t,name:n}=E(e.refname);S.movedRefs.push({kind:t,name:n,previousSha:e.previousSha,newSha:e.newSha})}C===r&&(await n(o,f.name),await s(o,m,`hard`),g&&await t(o,g)),await a(o,r,{force:!0}),S.deletedRefs.push({kind:`branch`,name:r,sha:e})}else d.type===`workdir`&&!y&&(await s(o,w,`hard`),g&&await t(o,g));return{commitShas:v,mutations:S}}export{b as applyToTarget};
package/dist/git/index.js CHANGED
@@ -1 +1 @@
1
- import{applyBranchStructure as e,createStackedBranches as t}from"./apply-branches.js";import{collectBranchDiffs as n,collectCommitsForSplit as r,collectFromSource as i,collectWorkingDiffs as a}from"./diff-collector.js";import{applyCommitChain as o,buildCommitChain as s}from"./commit-chain.js";import{verifyMultiBranchDiff as c,verifyResultingDiff as l,verifySnapshot as u}from"./safety.js";import{applyToTarget as d}from"./apply-target.js";export{e as applyBranchStructure,o as applyCommitChain,d as applyToTarget,s as buildCommitChain,n as collectBranchDiffs,r as collectCommitsForSplit,i as collectFromSource,a as collectWorkingDiffs,t as createStackedBranches,c as verifyMultiBranchDiff,l as verifyResultingDiff,u as verifySnapshot};
1
+ import{collectBranchDiffs as e,collectCommitsForSplit as t,collectFromSource as n,collectWorkingDiffs as r}from"./diff-collector.js";import{applyBranchStructure as i,createStackedBranches as a}from"./apply-branches.js";import{applyCommitChain as o,buildCommitChain as s}from"./commit-chain.js";import{verifyMultiBranchDiff as c,verifyResultingDiff as l,verifySnapshot as u}from"./safety.js";import{applyToTarget as d}from"./apply-target.js";export{i as applyBranchStructure,o as applyCommitChain,d as applyToTarget,s as buildCommitChain,e as collectBranchDiffs,t as collectCommitsForSplit,n as collectFromSource,r as collectWorkingDiffs,a as createStackedBranches,c as verifyMultiBranchDiff,l as verifyResultingDiff,u as verifySnapshot};
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { DuplicateHunksRepair, PhaseDetail, ProgressEvent, RepairScope, RepairedHunkClaim, TokenUsage, ValidationRepair } from "./ai/types.js";
2
+ import { AuthoringContext, AuthoringDependency, AuthoringEdit, AuthoringIssue, AuthoringNote, AuthoringPullRequest } from "./context.js";
2
3
  import { AIError, AIErrorCode } from "./errors/ai.js";
3
4
  import { GitError, GitErrorCode } from "./errors/git.js";
4
5
  import { DiffTooLargeError, TaskInputError } from "./errors/task.js";
@@ -29,8 +30,9 @@ import { quickCompose } from "./tasks/quick-compose/quick-compose.js";
29
30
  import { DEFAULT_INPUT_TOKEN_BUDGET } from "./tasks/utils/budget.js";
30
31
  import { isNoiseHunk, withDefaultNoiseRedaction } from "./tasks/utils/noise.js";
31
32
  import { ApplyComposePlanInput, ApplyComposePlanResult, ComposeApplyPlan, ComposeDepth, ComposeDiffStats, ComposeInput, ComposePlan, ComposePlanInput, ComposePlanResult, ComposeProgressEvent, ComposeResult, ComposeStrategy, applyComposePlan, compose, composePlan, defaultPhaseMessages } from "./workflows/compose.js";
33
+ import { AuthoredCommit, BuildAuthoredComposePlanInput, CollectComposeHunksInput, CollectComposeHunksResult, buildAuthoredComposePlan, buildAuthoredPlanInstructions, collectComposeHunks } from "./workflows/author.js";
32
34
  import { RefinePlanInput, RefinePlanResult, RefineProgressEvent, refinePlan } from "./workflows/refine.js";
33
35
  import { SplitInput, SplitPlan, SplitProgressEvent, SplitResult, split } from "./workflows/split.js";
34
36
  import { UndoComposeInput, ValidateUndoComposeInput, listUndoIds, undoCompose, validateUndoCompose } from "./workflows/undo.js";
35
37
  import { UndoForceOptions } from "@gitkraken/shared-tools";
36
- export { AIError, type AIErrorCode, type AfterModelCallInfo, type AnalysisResult, type AnalyzeInput, type AnalyzeOutput, type ApplyComposePlanInput, type ApplyComposePlanResult, type ApplyPatchOptions, type BeforeModelCallInfo, type BeforePromptInfo, type BranchGroup, type BranchOrdering, type BranchPartition, type CheckoutOptions, type CleanOptions, type CommitDetail, type CommitSuggestion, type CommitTreeOptions, type ComposeApplyPlan, type ComposeBranchPlan, type ComposeContext, type ComposeDepth, type ComposeDiffStats, type ComposeGitOps, type ComposeGitPort, ComposeGitPortMissingOpError, type ComposeGroupInput, type ComposeGroupOutput, type ComposeHunk, type ComposeInput, type ComposePlan, type ComposePlanInput, type ComposePlanResult, type ComposeProgressEvent, type ComposeResult, type ComposeSession, type ComposeSource, type ComposeStrategy, type ComposeTarget, type ComposeTaskName, ComposeWorkflowError, type ComposeWorkflowErrorCode, ComposeWorkflowInputError, type Concern, type ConcernDependency, type ConventionDescriptor, DEFAULT_INPUT_TOKEN_BUDGET, type DeleteBranchOptions, DiffTooLargeError, type DiffTreeOptions, type DuplicateHunksRepair, type ForEachRefOptions, type GitCommitIdentity, GitError, type GitErrorCode, type GroupInput, type GroupOutput, type GroupingResult, type GroupingStrategy, type HunkAnnotation, type HunkOrderingConstraint, type IndexScopedOptions, type InteriorRefViolation, type LogOptions, type OnAfterModelCall, type OnBeforeModelCall, type OnBeforePrompt, type OpOptions, type OrderInput, type OrderOutput, type OrderingResult, type OverrideSafetyFailures, type PartitionInput, type PartitionOutput, type PartitionResult, type PartitionStrategy, type PhaseDetail, type ProgressEvent, type PromptOverride, type QuickComposeInput, type QuickComposeOutput, type QuickComposeResult, REDACTED_HUNK_CONTENT, type RedactHunkContent, type RefinePlanInput, type RefinePlanResult, type RefineProgressEvent, type RepairScope, type RepairedHunkClaim, type SigningConfig, type SplitInput, type SplitPlan, type SplitProgressEvent, type SplitResult, type StageAllOptions, type StashConflict, type StashNotRestored, type StashPushOptions, TaskInputError, type TokenUsage, type UndoComposeInput, type UndoForceOptions, type UpdateRefOptions, type ValidateUndoComposeInput, type ValidationRepair, analyze, applyComposePlan, compose, composeGroup, composePlan, defaultPhaseMessages, detectInteriorRefs, estimatedCharactersPerToken, group, isNoiseHunk, listUndoIds, order, parseUnifiedDiff, partition, quickCompose, refinePlan, split, undoCompose, validateUndoCompose, withDefaultNoiseRedaction };
38
+ export { AIError, type AIErrorCode, type AfterModelCallInfo, type AnalysisResult, type AnalyzeInput, type AnalyzeOutput, type ApplyComposePlanInput, type ApplyComposePlanResult, type ApplyPatchOptions, type AuthoredCommit, type AuthoringContext, type AuthoringDependency, type AuthoringEdit, type AuthoringIssue, type AuthoringNote, type AuthoringPullRequest, type BeforeModelCallInfo, type BeforePromptInfo, type BranchGroup, type BranchOrdering, type BranchPartition, type BuildAuthoredComposePlanInput, type CheckoutOptions, type CleanOptions, type CollectComposeHunksInput, type CollectComposeHunksResult, type CommitDetail, type CommitSuggestion, type CommitTreeOptions, type ComposeApplyPlan, type ComposeBranchPlan, type ComposeContext, type ComposeDepth, type ComposeDiffStats, type ComposeGitOps, type ComposeGitPort, ComposeGitPortMissingOpError, type ComposeGroupInput, type ComposeGroupOutput, type ComposeHunk, type ComposeInput, type ComposePlan, type ComposePlanInput, type ComposePlanResult, type ComposeProgressEvent, type ComposeResult, type ComposeSession, type ComposeSource, type ComposeStrategy, type ComposeTarget, type ComposeTaskName, ComposeWorkflowError, type ComposeWorkflowErrorCode, ComposeWorkflowInputError, type Concern, type ConcernDependency, type ConventionDescriptor, DEFAULT_INPUT_TOKEN_BUDGET, type DeleteBranchOptions, DiffTooLargeError, type DiffTreeOptions, type DuplicateHunksRepair, type ForEachRefOptions, type GitCommitIdentity, GitError, type GitErrorCode, type GroupInput, type GroupOutput, type GroupingResult, type GroupingStrategy, type HunkAnnotation, type HunkOrderingConstraint, type IndexScopedOptions, type InteriorRefViolation, type LogOptions, type OnAfterModelCall, type OnBeforeModelCall, type OnBeforePrompt, type OpOptions, type OrderInput, type OrderOutput, type OrderingResult, type OverrideSafetyFailures, type PartitionInput, type PartitionOutput, type PartitionResult, type PartitionStrategy, type PhaseDetail, type ProgressEvent, type PromptOverride, type QuickComposeInput, type QuickComposeOutput, type QuickComposeResult, REDACTED_HUNK_CONTENT, type RedactHunkContent, type RefinePlanInput, type RefinePlanResult, type RefineProgressEvent, type RepairScope, type RepairedHunkClaim, type SigningConfig, type SplitInput, type SplitPlan, type SplitProgressEvent, type SplitResult, type StageAllOptions, type StashConflict, type StashNotRestored, type StashPushOptions, TaskInputError, type TokenUsage, type UndoComposeInput, type UndoForceOptions, type UpdateRefOptions, type ValidateUndoComposeInput, type ValidationRepair, analyze, applyComposePlan, buildAuthoredComposePlan, buildAuthoredPlanInstructions, collectComposeHunks, compose, composeGroup, composePlan, defaultPhaseMessages, detectInteriorRefs, estimatedCharactersPerToken, group, isNoiseHunk, listUndoIds, order, parseUnifiedDiff, partition, quickCompose, refinePlan, split, undoCompose, validateUndoCompose, withDefaultNoiseRedaction };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{AIError as e}from"./errors/ai.js";import{GitError as t}from"./errors/git.js";import{DiffTooLargeError as n,TaskInputError as r}from"./errors/task.js";import{ComposeWorkflowError as i,ComposeWorkflowInputError as a}from"./errors/workflow.js";import{ComposeGitPortMissingOpError as o}from"./ports/git.js";import{detectInteriorRefs as s}from"./git/safety-range.js";import{parseUnifiedDiff as c}from"./git/utils/hunk-parser.js";import{estimatedCharactersPerToken as l}from"./tasks/ai-loop.js";import{REDACTED_HUNK_CONTENT as u}from"./tasks/utils/redact.js";import{DEFAULT_INPUT_TOKEN_BUDGET as d}from"./tasks/utils/budget.js";import{analyze as f}from"./tasks/analyze/analyze.js";import{composeGroup as p}from"./tasks/compose-group/compose-group.js";import{group as m}from"./tasks/group/group.js";import{order as h}from"./tasks/order/order.js";import{partition as g}from"./tasks/partition/partition.js";import{quickCompose as _}from"./tasks/quick-compose/quick-compose.js";import{isNoiseHunk as v,withDefaultNoiseRedaction as y}from"./tasks/utils/noise.js";import{applyComposePlan as b,compose as x,composePlan as S,defaultPhaseMessages as C}from"./workflows/compose.js";import{refinePlan as w}from"./workflows/refine.js";import{split as T}from"./workflows/split.js";import{listUndoIds as E,undoCompose as D,validateUndoCompose as O}from"./workflows/undo.js";export{e as AIError,o as ComposeGitPortMissingOpError,i as ComposeWorkflowError,a as ComposeWorkflowInputError,d as DEFAULT_INPUT_TOKEN_BUDGET,n as DiffTooLargeError,t as GitError,u as REDACTED_HUNK_CONTENT,r as TaskInputError,f as analyze,b as applyComposePlan,x as compose,p as composeGroup,S as composePlan,C as defaultPhaseMessages,s as detectInteriorRefs,l as estimatedCharactersPerToken,m as group,v as isNoiseHunk,E as listUndoIds,h as order,c as parseUnifiedDiff,g as partition,_ as quickCompose,w as refinePlan,T as split,D as undoCompose,O as validateUndoCompose,y as withDefaultNoiseRedaction};
1
+ import{AIError as e}from"./errors/ai.js";import{GitError as t}from"./errors/git.js";import{DiffTooLargeError as n,TaskInputError as r}from"./errors/task.js";import{ComposeWorkflowError as i,ComposeWorkflowInputError as a}from"./errors/workflow.js";import{ComposeGitPortMissingOpError as o}from"./ports/git.js";import{detectInteriorRefs as s}from"./git/safety-range.js";import{parseUnifiedDiff as c}from"./git/utils/hunk-parser.js";import{estimatedCharactersPerToken as l}from"./tasks/ai-loop.js";import{REDACTED_HUNK_CONTENT as u}from"./tasks/utils/redact.js";import{DEFAULT_INPUT_TOKEN_BUDGET as d}from"./tasks/utils/budget.js";import{analyze as f}from"./tasks/analyze/analyze.js";import{composeGroup as p}from"./tasks/compose-group/compose-group.js";import{group as m}from"./tasks/group/group.js";import{order as h}from"./tasks/order/order.js";import{partition as g}from"./tasks/partition/partition.js";import{quickCompose as _}from"./tasks/quick-compose/quick-compose.js";import{isNoiseHunk as v,withDefaultNoiseRedaction as y}from"./tasks/utils/noise.js";import{applyComposePlan as b,compose as x,composePlan as S,defaultPhaseMessages as C}from"./workflows/compose.js";import{buildAuthoredComposePlan as w,buildAuthoredPlanInstructions as T,collectComposeHunks as E}from"./workflows/author.js";import{refinePlan as D}from"./workflows/refine.js";import{split as O}from"./workflows/split.js";import{listUndoIds as k,undoCompose as A,validateUndoCompose as j}from"./workflows/undo.js";export{e as AIError,o as ComposeGitPortMissingOpError,i as ComposeWorkflowError,a as ComposeWorkflowInputError,d as DEFAULT_INPUT_TOKEN_BUDGET,n as DiffTooLargeError,t as GitError,u as REDACTED_HUNK_CONTENT,r as TaskInputError,f as analyze,b as applyComposePlan,w as buildAuthoredComposePlan,T as buildAuthoredPlanInstructions,E as collectComposeHunks,x as compose,p as composeGroup,S as composePlan,C as defaultPhaseMessages,s as detectInteriorRefs,l as estimatedCharactersPerToken,m as group,v as isNoiseHunk,k as listUndoIds,h as order,c as parseUnifiedDiff,g as partition,_ as quickCompose,D as refinePlan,O as split,A as undoCompose,j as validateUndoCompose,y as withDefaultNoiseRedaction};
@@ -16,6 +16,12 @@ interface BeforeModelCallInfo {
16
16
  maxAttempts: number;
17
17
  /** Stable UUID identifying this model.generate call. Pairs with the matching After event. */
18
18
  callId: string;
19
+ /**
20
+ * True when this is a targeted repair of the previous response rather than a fresh attempt:
21
+ * a much smaller prompt carrying only what the fix needs. Shares `attempt` with the call
22
+ * whose response it repairs.
23
+ */
24
+ isRepair?: boolean;
19
25
  }
20
26
  interface AfterModelCallInfo {
21
27
  phase: string;
@@ -31,17 +37,20 @@ interface AfterModelCallInfo {
31
37
  /** Identifier of the prompt template used for this call, when a consumer override supplied one. */
32
38
  promptVersion?: string;
33
39
  /**
34
- * Deterministic repairs applied to this response so it could be accepted instead of retried.
40
+ * Repairs applied to this response so it could be accepted instead of retried.
35
41
  * Undefined when the response needed no repair (or when the call itself failed).
36
42
  */
37
43
  repairs?: ValidationRepair[];
44
+ /** See {@link BeforeModelCallInfo.isRepair}. */
45
+ isRepair?: boolean;
38
46
  }
39
47
  type OnBeforeModelCall = (info: BeforeModelCallInfo) => void;
40
48
  type OnAfterModelCall = (info: AfterModelCallInfo) => void;
41
49
  /**
42
50
  * Gating hook fired immediately before each `model.generate` call. Returning
43
51
  * `false` aborts the loop with a native `AbortError` (DOMException with
44
- * `name === 'AbortError'`). Intended for cost warnings, large-prompt
52
+ * `name === 'AbortError'`), except on a repair call (`isRepair`), where it skips
53
+ * the repair and falls back to the regular retry. Intended for cost warnings, large-prompt
45
54
  * confirmations, policy checks, and similar caller-side UX.
46
55
  *
47
56
  * `tokenEstimate` is a rough `charCount / 2.8` approximation — reasonable for
@@ -55,6 +64,8 @@ interface BeforePromptInfo {
55
64
  charCount: number;
56
65
  messageCount: number;
57
66
  tokenEstimate: number;
67
+ /** See {@link BeforeModelCallInfo.isRepair}. */
68
+ isRepair?: boolean;
58
69
  }
59
70
  type OnBeforePrompt = (info: BeforePromptInfo) => Promise<boolean>;
60
71
  /** Character-to-token approximation for English/code prompts. */
@@ -84,6 +95,21 @@ interface ValidationResult<T> {
84
95
  retryFeedback?: string;
85
96
  /** Set (non-empty) only when the validator repaired the response to make it valid. */
86
97
  repairs?: ValidationRepair[];
98
+ /**
99
+ * Offered by a validator on a rejected response whose only defect one small model call can
100
+ * settle. The loop attempts it once, before falling back to a full retry.
101
+ */
102
+ repair?: TargetedRepair<T>;
103
+ }
104
+ /**
105
+ * A rejected response the validator can still salvage by asking the model one narrow question.
106
+ * The prompt must be self-contained — the loop sends it on its own, without the system prompt
107
+ * or the message history, which is the entire point of the escalation.
108
+ */
109
+ interface TargetedRepair<T> {
110
+ prompt: string;
111
+ /** Validates the repair response and folds it into the response being repaired. Must be pure. */
112
+ apply: (responseText: string) => ValidationResult<T>;
87
113
  }
88
114
  interface AiLoopResult<T> {
89
115
  result: T;
@@ -99,4 +125,4 @@ interface AiLoopResult<T> {
99
125
  declare function runAiLoop<T>(params: AiLoopParams<T>): Promise<AiLoopResult<T>>;
100
126
  declare function extractOutputTag(text: string): string | null;
101
127
  //#endregion
102
- export { AfterModelCallInfo, BeforeModelCallInfo, BeforePromptInfo, OnAfterModelCall, OnBeforeModelCall, OnBeforePrompt, ValidationResult, estimatedCharactersPerToken, extractOutputTag, runAiLoop };
128
+ export { AfterModelCallInfo, BeforeModelCallInfo, BeforePromptInfo, OnAfterModelCall, OnBeforeModelCall, OnBeforePrompt, TargetedRepair, ValidationResult, estimatedCharactersPerToken, extractOutputTag, runAiLoop };
@@ -1,2 +1,2 @@
1
- import{AIError as e}from"../errors/ai.js";import{TaskInputError as t}from"../errors/task.js";import{appendAssistantMessage as n,appendUserMessage as r,createAiSession as i}from"../ai/session.js";import{addUsage as a}from"../ai/types.js";import{randomUUID as o}from"node:crypto";const s=2.8;async function c(a){let{model:s,userPrompt:c,maxRetries:d=3,maxTokens:f=16384,onProgress:p,validate:m,taskName:h,cancellation:g,phase:_=h,onBeforeModelCall:v,onAfterModelCall:y,promptVersion:b}=a;if(!a.session&&!a.systemPrompt)throw new t(`${h}: either 'session' or 'systemPrompt' must be provided`);let x=Date.now(),S;a.session?S=r(a.session,c):(S=i(a.systemPrompt),S=r(S,c));let C,w=[],T=d+1,E=[];for(let e=0;e<=d;e++){g?.throwIfAborted();let t=e+1;if(p?.({type:`attempt_start`,attempt:t,maxAttempts:T}),a.onBeforePrompt){let e=(S.systemPrompt?.length??0)+S.messages.reduce((e,t)=>e+t.content.length,0);if(!await a.onBeforePrompt({phase:_,attempt:t,maxAttempts:T,charCount:e,messageCount:S.messages.length,tokenEstimate:Math.ceil(e/2.8)}))throw new DOMException(`Prompt gate declined by caller (onBeforePrompt returned false)`,`AbortError`)}let i=o();E.push(i);let c=Date.now();v?.({phase:_,attempt:t,maxAttempts:T,callId:i});let h;try{h=await s.generate({system:S.systemPrompt,messages:[...S.messages],maxTokens:f,temperature:0,signal:g})}catch(e){let n=e instanceof Error?e.message:String(e);try{y?.({phase:_,attempt:t,callId:i,success:!1,durationMs:Date.now()-c,error:{code:e?.code??`UNKNOWN`,message:n},promptVersion:b})}catch{}throw l(e)||g?.aborted?new DOMException(n,`AbortError`):e}let D=Date.now()-c,O=e=>{try{y?.({phase:_,attempt:t,callId:i,success:!0,durationMs:D,usage:h.usage,promptVersion:b,repairs:e})}catch{}};C=u(C,h.usage);let k;try{k=m(h.text)}catch(e){throw O(),e}if(O(k.valid?k.repairs:void 0),k.valid&&k.result!==void 0)return S=n(S,h.text),{result:k.result,session:S,usage:C,attempts:t,durationMs:Date.now()-x,callIds:E,repairs:k.repairs};w=k.errors??[`Unknown validation error`],p?.({type:`validation_failed`,attempt:t,maxAttempts:T,errors:w}),e<d&&k.retryFeedback&&(S=n(S,h.text),S=r(S,k.retryFeedback))}throw new e(`VALIDATION_EXHAUSTED`,`Failed to get valid ${h} after ${T} attempts. Last errors:\n${w.join(`
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:c,userPrompt:d,maxRetries:f=3,maxTokens:p=16384,onProgress:m,validate:h,taskName:g,cancellation:_,phase:v=g,onBeforeModelCall:y,onAfterModelCall:b,promptVersion:x}=a;if(!a.session&&!a.systemPrompt)throw new t(`${g}: either 'session' or 'systemPrompt' must be provided`);let S=Date.now(),C;a.session?C=r(a.session,d):(C=i(a.systemPrompt),C=r(C,d));let w,T=[],E=f+1,D=[],O=!1,k=async e=>a.onBeforePrompt?a.onBeforePrompt({phase:v,attempt:e.attempt,maxAttempts:E,charCount:e.charCount,messageCount:e.messageCount,tokenEstimate:Math.ceil(e.charCount/s),isRepair:e.isRepair}):!0,A=async(e,t)=>{if(!await k({attempt:t,charCount:e.prompt.length,messageCount:1,isRepair:!0}))return;let n=o();D.push(n);let r=Date.now();y?.({phase:v,attempt:t,maxAttempts:E,callId:n,isRepair:!0});let i;try{i=await c.generate({messages:[{role:`user`,content:e.prompt}],maxTokens:p,temperature:0,signal:_})}catch(e){let i=e instanceof Error?e.message:String(e);try{b?.({phase:v,attempt:t,callId:n,success:!1,durationMs:Date.now()-r,error:{code:e?.code??`UNKNOWN`,message:i},promptVersion:x,isRepair:!0})}catch{}if(l(e)||_?.aborted)throw new DOMException(i,`AbortError`);return}let a=Date.now()-r;w=u(w,i.usage);let s=e=>{try{b?.({phase:v,attempt:t,callId:n,success:!0,durationMs:a,usage:i.usage,promptVersion:x,repairs:e,isRepair:!0})}catch{}},d;try{d=e.apply(i.text)}catch(e){throw s(),e}return s(d.valid?d.repairs:void 0),d.valid&&d.result!==void 0?{result:d.result,repairs:d.repairs,responseText:i.text}:{errors:d.errors??[`Unknown repair validation error`]}};for(let e=0;e<=f;e++){_?.throwIfAborted();let t=e+1;if(m?.({type:`attempt_start`,attempt:t,maxAttempts:E}),a.onBeforePrompt&&!await k({attempt:t,charCount:(C.systemPrompt?.length??0)+C.messages.reduce((e,t)=>e+t.content.length,0),messageCount:C.messages.length}))throw new DOMException(`Prompt gate declined by caller (onBeforePrompt returned false)`,`AbortError`);let i=o();D.push(i);let s=Date.now();y?.({phase:v,attempt:t,maxAttempts:E,callId:i});let d;try{d=await c.generate({system:C.systemPrompt,messages:[...C.messages],maxTokens:p,temperature:0,signal:_})}catch(e){let n=e instanceof Error?e.message:String(e);try{b?.({phase:v,attempt:t,callId:i,success:!1,durationMs:Date.now()-s,error:{code:e?.code??`UNKNOWN`,message:n},promptVersion:x})}catch{}throw l(e)||_?.aborted?new DOMException(n,`AbortError`):e}let g=Date.now()-s,j=e=>{try{b?.({phase:v,attempt:t,callId:i,success:!0,durationMs:g,usage:d.usage,promptVersion:x,repairs:e})}catch{}};w=u(w,d.usage);let M;try{M=h(d.text)}catch(e){throw j(),e}if(j(M.valid?M.repairs:void 0),M.valid&&M.result!==void 0)return C=n(C,d.text),{result:M.result,session:C,usage:w,attempts:t,durationMs:Date.now()-S,callIds:D,repairs:M.repairs};if(T=M.errors??[`Unknown validation error`],M.repair&&!O){O=!0;let e=await A(M.repair,t);if(e&&`result`in e)return C=n(C,d.text),C=r(C,M.repair.prompt),C=n(C,e.responseText),{result:e.result,session:C,usage:w,attempts:t,durationMs:Date.now()-S,callIds:D,repairs:e.repairs};e&&(T=[...T,`Targeted repair rejected: ${e.errors.join(`; `)}`])}m?.({type:`validation_failed`,attempt:t,maxAttempts:E,errors:T}),e<f&&M.retryFeedback&&(C=n(C,d.text),C=r(C,M.retryFeedback))}throw new e(`VALIDATION_EXHAUSTED`,`Failed to get valid ${g} after ${E} attempts. Last errors:\n${T.join(`
2
2
  `)}`)}function l(e){return e instanceof Error&&(e.name===`AbortError`||e.name===`DOMException`)}function u(e,t){if(!e)return t;if(!t)return e;let n={...e};return a(n,t),n}function d(e){let t=e.match(/<output>([\s\S]*?)(?:<\/output>|$)/)?.[1]?.trim()??null;return t?t.match(/^```(?:\w*)\n?([\s\S]*?)```$/)?.[1]?.trim()??t:null}export{s as estimatedCharactersPerToken,d as extractOutputTag,c as runAiLoop};
@@ -1 +1 @@
1
- import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{buildAnalyzeSystemPrompt as r,buildAnalyzeUserPrompt as i}from"./prompt.js";import{validateAnalyzeResponse as a}from"./validator.js";async function o(o){let s=o.hunks;if(!s||s.length===0)throw new e(`No hunks provided: supply 'hunks'`);if(o.session?.completedSteps.analyze)return{result:o.session.completedSteps.analyze,session:o.session,attempts:0,durationMs:0,callIds:[]};let c=o.session?void 0:o.promptOverride?.system??r(),l,u=n(i({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions,redactHunkContent:o.redactHunkContent,inputTokenBudget:o.inputTokenBudget,systemPromptChars:c?.length,onBudgetReduction:e=>{l=e}}),o.promptOverride),d=new Set(s.map(e=>e.index)),f=await t({model:o.model,systemPrompt:c,userPrompt:u,session:o.session,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>a(e,d),taskName:`analyze`}),p=o.session?.completedSteps??{},m={...f.session,completedSteps:{...p,analyze:f.result}};return{result:f.result,session:m,usage:f.usage,attempts:f.attempts,durationMs:f.durationMs,callIds:f.callIds,hunksCondensedCount:l}}export{o as analyze};
1
+ import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{buildAnalyzeSystemPrompt as r,buildAnalyzeUserPrompt as i}from"./prompt.js";import{validateAnalyzeResponse as a}from"./validator.js";async function o(o){let s=o.hunks;if(!s||s.length===0)throw new e(`No hunks provided: supply 'hunks'`);if(o.session?.completedSteps.analyze)return{result:o.session.completedSteps.analyze,session:o.session,attempts:0,durationMs:0,callIds:[]};let c=o.session?void 0:o.promptOverride?.system??r(),l,u=n(i({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,authoring:o.authoring,hunkSummaries:o.hunkSummaries,condensed:o.condensed,instructions:o.instructions,redactHunkContent:o.redactHunkContent,inputTokenBudget:o.inputTokenBudget,systemPromptChars:c?.length,onBudgetReduction:e=>{l=e}}),o.promptOverride),d=new Set(s.map(e=>e.index)),f=await t({model:o.model,systemPrompt:c,userPrompt:u,session:o.session,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>a(e,d),taskName:`analyze`}),p=o.session?.completedSteps??{},m={...f.session,completedSteps:{...p,analyze:f.result}};return{result:f.result,session:m,usage:f.usage,attempts:f.attempts,durationMs:f.durationMs,callIds:f.callIds,hunksCondensedCount:l}}export{o as analyze};
@@ -1,3 +1,4 @@
1
+ import { AuthoringContext } from "../../context.js";
1
2
  import { ComposeHunk } from "../types.js";
2
3
  import { RedactHunkContent } from "../utils/redact.js";
3
4
 
@@ -10,8 +11,11 @@ declare function buildAnalyzeUserPrompt(params: {
10
11
  branchName?: string;
11
12
  baseBranch?: string;
12
13
  issueUrl?: string;
13
- pullRequestUrl?: string;
14
- instructions?: string;
14
+ pullRequestUrl?: string; /** What the author knows that the hunks cannot show. Rendered as escaped, tagged data. */
15
+ authoring?: AuthoringContext; /** One-line summaries of what individual hunks do, keyed by index. See `ComposeInput.hunkSummaries`. */
16
+ hunkSummaries?: Record<number, string>;
17
+ instructions?: string; /** Send hunk metadata only, omitting every patch body. See `ComposeInput.condenseHunks`. */
18
+ condensed?: boolean;
15
19
  redactHunkContent?: RedactHunkContent; /** Input-token ceiling that triggers progressive hunk reduction. Defaults to the shared budget. */
16
20
  inputTokenBudget?: number; /** Char count of the system prompt, folded into the budget estimate. Defaults to 0. */
17
21
  systemPromptChars?: number; /** Reports how many hunks were condensed for budget (full to condensed), for consumer telemetry. */
@@ -1,4 +1,4 @@
1
- import{assembleHunkPrompt as e}from"../utils/budget.js";import{analysisResultJsonSchema as t}from"./schema.js";const n=JSON.stringify(t,null,2);function r(){return`You are an advanced AI code analysis assistant. Your task is to deeply analyze a set of code changes (provided as diff hunks) and produce a structured understanding of what the changes accomplish, what concerns they address, and how hunks relate to each other.
1
+ import{authoringContextBlock as e,resolveAuthoringUrls as t}from"../utils/authoring-context.js";import{assembleHunkPrompt as n}from"../utils/budget.js";import{renderHunkSummaries as r}from"../utils/hunk-summaries.js";import{analysisResultJsonSchema as i}from"./schema.js";const a=JSON.stringify(i,null,2);function o(){return`You are an advanced AI code analysis assistant. Your task is to deeply analyze a set of code changes (provided as diff hunks) and produce a structured understanding of what the changes accomplish, what concerns they address, and how hunks relate to each other.
2
2
 
3
3
  Your analysis must produce:
4
4
  1. **Title** — a short, PR-style title for the changes (under 72 characters, imperative mood, e.g. "Add user authentication and session management"). This should capture the essence of what the changes accomplish.
@@ -26,10 +26,10 @@ Rules:
26
26
  Output your analysis as JSON inside a <output> tag matching this schema:
27
27
 
28
28
  \`\`\`json
29
- ${n}
29
+ ${a}
30
30
  \`\`\`
31
31
 
32
- Return only the <output> tag and no other text.`}function i(t){let{hunks:n,commitMessages:r,commitOrder:i,branchName:a,baseBranch:o,issueUrl:s,pullRequestUrl:c,instructions:l,redactHunkContent:u,inputTokenBudget:d,systemPromptChars:f,onBudgetReduction:p}=t,{prompt:m,hunksCondensedForBudget:h}=e({hunks:n,assemble:e=>{let t=`Analyze the following code changes:\n\n<hunks>\n${e}\n</hunks>`,n=[];if(a&&n.push(`Branch: ${a}`),o&&n.push(`Base branch: ${o}`),s&&n.push(`Issue: ${s}`),c&&n.push(`Pull request: ${c}`),n.length>0&&(t+=`\n\n<context>\n${n.join(`
33
- `)}\n</context>`),r?.length){let e=r.map((e,t)=>({index:t,sha:i?.[t],message:e}));t+=`\n\n<commit_messages>\n${JSON.stringify(e)}\n</commit_messages>`}return l&&(t+=`\n\nCarefully follow these additional instructions, but do not deviate from the output structure:\n${l}`),t+=`
32
+ Return only the <output> tag and no other text.`}function s(i){let{hunks:a,commitMessages:o,commitOrder:s,branchName:c,baseBranch:l,issueUrl:u,pullRequestUrl:d,authoring:f,hunkSummaries:p,instructions:m,condensed:h,redactHunkContent:g,inputTokenBudget:_,systemPromptChars:v,onBudgetReduction:y}=i,b=t(f,{issueUrl:u,pullRequestUrl:d}),x=e(f),S=r(p,a),{prompt:C,hunksCondensedForBudget:w}=n({hunks:a,assemble:e=>{let t=`Analyze the following code changes:\n\n<hunks>\n${e}\n</hunks>`,n=[];if(c&&n.push(`Branch: ${c}`),l&&n.push(`Base branch: ${l}`),b.issueUrl&&n.push(`Issue: ${b.issueUrl}`),b.pullRequestUrl&&n.push(`Pull request: ${b.pullRequestUrl}`),n.length>0&&(t+=`\n\n<context>\n${n.join(`
33
+ `)}\n</context>`),S&&(t+=`\n\n${S}`),x&&(t+=`\n\n${x}`),o?.length){let e=o.map((e,t)=>({index:t,sha:s?.[t],message:e}));t+=`\n\n<commit_messages>\n${JSON.stringify(e)}\n</commit_messages>`}return m&&(t+=`\n\nCarefully follow these additional instructions, but do not deviate from the output structure:\n${m}`),t+=`
34
34
 
35
- Now analyze the changes and return your structured analysis. Return only the <output> tag and no other text.`,t},redactHunkContent:u,inputTokenBudget:d,systemPromptChars:f});return p?.(h),m}export{r as buildAnalyzeSystemPrompt,i as buildAnalyzeUserPrompt};
35
+ Now analyze the changes and return your structured analysis. Return only the <output> tag and no other text.`,t},condensed:h,redactHunkContent:g,inputTokenBudget:_,systemPromptChars:v});return y?.(w),C}export{o as buildAnalyzeSystemPrompt,s as buildAnalyzeUserPrompt};
@@ -1,4 +1,5 @@
1
1
  import { ProgressEvent, TokenUsage } from "../../ai/types.js";
2
+ import { AuthoringContext } from "../../context.js";
2
3
  import { ComposeHunk, PromptOverride } from "../types.js";
3
4
  import { OnAfterModelCall, OnBeforeModelCall, OnBeforePrompt } from "../ai-loop.js";
4
5
  import { RedactHunkContent } from "../utils/redact.js";
@@ -14,7 +15,17 @@ interface AnalyzeInput {
14
15
  baseBranch?: string;
15
16
  issueUrl?: string;
16
17
  pullRequestUrl?: string;
18
+ /**
19
+ * What the author knows that the hunks cannot show — intent, issue text, per-file rationale,
20
+ * ordering constraints. Rendered into the prompt as escaped, tagged data, never as instructions;
21
+ * for guidance the model should act on, use {@link instructions}.
22
+ */
23
+ authoring?: AuthoringContext;
24
+ /** One-line summaries of what individual hunks do, keyed by index. See `ComposeInput.hunkSummaries`. */
25
+ hunkSummaries?: Record<number, string>;
17
26
  instructions?: string;
27
+ /** Send hunk metadata only, omitting every patch body. See `ComposeInput.condenseHunks`. */
28
+ condensed?: boolean;
18
29
  /** See {@link RedactHunkContent}. */
19
30
  redactHunkContent?: RedactHunkContent;
20
31
  /**
@@ -1 +1 @@
1
- import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{validateGroupResponse as r}from"../group/validator.js";import{buildComposeGroupSystemPrompt as i,buildComposeGroupUserPrompt as a}from"./prompt.js";async function o(o){let s=o.hunks,c=o.mode??`branches`;if(!s||s.length===0)throw new e(`No hunks provided for compose-group`);let l=new Set(s.map(e=>e.index));if(o.session?.completedSteps.composeGroup)return{result:o.session.completedSteps.composeGroup,session:o.session,attempts:0,durationMs:0,callIds:[]};let u=o.promptOverride?.system??i(o.conventions,c,o.strategy,o.includeAnalysis,o.lightweight),d,f=n(a({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,instructions:o.instructions,condensed:o.condensed,redactHunkContent:o.redactHunkContent,inputTokenBudget:o.inputTokenBudget,systemPromptChars:u.length,onBudgetReduction:e=>{d=e}}),o.promptOverride),p=await t({model:o.model,systemPrompt:u,userPrompt:f,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>r(e,l,c,o.existingBranchNames),taskName:`compose-group`}),m=o.session?.completedSteps??{},h={...p.session,completedSteps:{...m,composeGroup:p.result}};return{result:p.result,session:h,usage:p.usage,attempts:p.attempts,durationMs:p.durationMs,callIds:p.callIds,hunksCondensedCount:d,repairs:p.repairs}}export{o as composeGroup};
1
+ import{TaskInputError as e}from"../../errors/task.js";import{runAiLoop as t}from"../ai-loop.js";import{applyUserPromptOverride as n}from"../types.js";import{validateGroupResponse as r}from"../group/validator.js";import{buildComposeGroupSystemPrompt as i,buildComposeGroupUserPrompt as a}from"./prompt.js";async function o(o){let s=o.hunks,c=o.mode??`branches`;if(!s||s.length===0)throw new e(`No hunks provided for compose-group`);let l=new Set(s.map(e=>e.index));if(o.session?.completedSteps.composeGroup)return{result:o.session.completedSteps.composeGroup,session:o.session,attempts:0,durationMs:0,callIds:[]};let u=o.promptOverride?.system??i(o.conventions,c,o.strategy,o.includeAnalysis,o.lightweight),d,f=n(a({hunks:s,commitMessages:o.commitMessages,commitOrder:o.commitOrder,branchName:o.branchName,baseBranch:o.baseBranch,issueUrl:o.issueUrl,pullRequestUrl:o.pullRequestUrl,authoring:o.authoring,analysis:o.analysis,hunkSummaries:o.hunkSummaries,instructions:o.instructions,condensed:o.condensed,redactHunkContent:o.redactHunkContent,inputTokenBudget:o.inputTokenBudget,systemPromptChars:u.length,onBudgetReduction:e=>{d=e}}),o.promptOverride),p=await t({model:o.model,systemPrompt:u,userPrompt:f,maxRetries:o.maxRetries,maxTokens:o.maxTokens,onProgress:o.onProgress,cancellation:o.cancellation,onBeforeModelCall:o.onBeforeModelCall,onAfterModelCall:o.onAfterModelCall,onBeforePrompt:o.onBeforePrompt,validate:e=>r(e,l,c,o.existingBranchNames,{repairContext:{hunks:s,condensed:o.condensed,redactHunkContent:o.redactHunkContent,inputTokenBudget:o.inputTokenBudget}}),taskName:`compose-group`}),m=o.session?.completedSteps??{},h={...p.session,completedSteps:{...m,composeGroup:p.result}};return{result:p.result,session:h,usage:p.usage,attempts:p.attempts,durationMs:p.durationMs,callIds:p.callIds,hunksCondensedCount:d,repairs:p.repairs}}export{o as composeGroup};
@@ -1,6 +1,8 @@
1
+ import { AuthoringContext } from "../../context.js";
1
2
  import { ComposeHunk } from "../types.js";
2
3
  import { ConventionDescriptor } from "../../git/utils/conventions.js";
3
4
  import { RedactHunkContent } from "../utils/redact.js";
5
+ import { AnalysisResult } from "../analyze/types.js";
4
6
  import { GroupingStrategy } from "../group/types.js";
5
7
 
6
8
  //#region src/tasks/compose-group/prompt.d.ts
@@ -19,7 +21,10 @@ declare function buildComposeGroupUserPrompt(params: {
19
21
  branchName?: string;
20
22
  baseBranch?: string;
21
23
  issueUrl?: string;
22
- pullRequestUrl?: string;
24
+ pullRequestUrl?: string; /** What the author knows that the hunks cannot show. Rendered as escaped, tagged data. */
25
+ authoring?: AuthoringContext; /** Caller-supplied analysis, used in place of asking the model to derive one. */
26
+ analysis?: AnalysisResult; /** One-line summaries of what individual hunks do, keyed by index. See `ComposeInput.hunkSummaries`. */
27
+ hunkSummaries?: Record<number, string>;
23
28
  instructions?: string;
24
29
  condensed?: boolean;
25
30
  redactHunkContent?: RedactHunkContent; /** Input-token ceiling that triggers progressive hunk reduction. Defaults to the shared budget. */