@gethmy/agent 1.7.1 → 1.7.2

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 (77) hide show
  1. package/dist/cli.js +6376 -141
  2. package/dist/index.js +6206 -333
  3. package/package.json +2 -2
  4. package/dist/board-helpers.d.ts +0 -31
  5. package/dist/board-helpers.js +0 -150
  6. package/dist/budget.d.ts +0 -39
  7. package/dist/budget.js +0 -73
  8. package/dist/cli.d.ts +0 -14
  9. package/dist/completion.d.ts +0 -36
  10. package/dist/completion.js +0 -322
  11. package/dist/config-validation.d.ts +0 -23
  12. package/dist/config-validation.js +0 -77
  13. package/dist/config.d.ts +0 -23
  14. package/dist/config.js +0 -103
  15. package/dist/episode-writer.d.ts +0 -116
  16. package/dist/episode-writer.js +0 -349
  17. package/dist/git-diff-stat.d.ts +0 -24
  18. package/dist/git-diff-stat.js +0 -56
  19. package/dist/git-pr.d.ts +0 -38
  20. package/dist/git-pr.js +0 -399
  21. package/dist/http-server.d.ts +0 -66
  22. package/dist/http-server.js +0 -96
  23. package/dist/index.d.ts +0 -5
  24. package/dist/log.d.ts +0 -34
  25. package/dist/log.js +0 -100
  26. package/dist/merge-monitor.d.ts +0 -23
  27. package/dist/merge-monitor.js +0 -169
  28. package/dist/pm.d.ts +0 -14
  29. package/dist/pm.js +0 -63
  30. package/dist/pool.d.ts +0 -71
  31. package/dist/pool.js +0 -259
  32. package/dist/process-group.d.ts +0 -26
  33. package/dist/process-group.js +0 -72
  34. package/dist/progress-tracker.d.ts +0 -82
  35. package/dist/progress-tracker.js +0 -457
  36. package/dist/prompt.d.ts +0 -23
  37. package/dist/prompt.js +0 -160
  38. package/dist/queue.d.ts +0 -39
  39. package/dist/queue.js +0 -100
  40. package/dist/reconcile.d.ts +0 -35
  41. package/dist/reconcile.js +0 -174
  42. package/dist/recovery.d.ts +0 -30
  43. package/dist/recovery.js +0 -141
  44. package/dist/review-completion.d.ts +0 -35
  45. package/dist/review-completion.js +0 -475
  46. package/dist/review-knowledge.d.ts +0 -14
  47. package/dist/review-knowledge.js +0 -89
  48. package/dist/review-prompt.d.ts +0 -12
  49. package/dist/review-prompt.js +0 -103
  50. package/dist/review-worker.d.ts +0 -56
  51. package/dist/review-worker.js +0 -638
  52. package/dist/review-worktree.d.ts +0 -12
  53. package/dist/review-worktree.js +0 -95
  54. package/dist/run-log.d.ts +0 -6
  55. package/dist/run-log.js +0 -19
  56. package/dist/startup-banner.d.ts +0 -29
  57. package/dist/startup-banner.js +0 -143
  58. package/dist/state-store.d.ts +0 -89
  59. package/dist/state-store.js +0 -230
  60. package/dist/stream-parser-selftest.d.ts +0 -9
  61. package/dist/stream-parser-selftest.js +0 -97
  62. package/dist/stream-parser.d.ts +0 -43
  63. package/dist/stream-parser.js +0 -174
  64. package/dist/transitions.d.ts +0 -57
  65. package/dist/transitions.js +0 -131
  66. package/dist/types.d.ts +0 -167
  67. package/dist/types.js +0 -76
  68. package/dist/verification.d.ts +0 -39
  69. package/dist/verification.js +0 -317
  70. package/dist/watcher.d.ts +0 -53
  71. package/dist/watcher.js +0 -153
  72. package/dist/worker.d.ts +0 -54
  73. package/dist/worker.js +0 -507
  74. package/dist/worktree-gc.d.ts +0 -67
  75. package/dist/worktree-gc.js +0 -245
  76. package/dist/worktree.d.ts +0 -18
  77. package/dist/worktree.js +0 -177
@@ -1,349 +0,0 @@
1
- import { log } from "./log.js";
2
- const TAG = "episode-writer";
3
- const MAX_APPROACH_SUMMARY_CHARS = 400;
4
- // Richer approach summary cap (#272). The single-turn trim used a 400-char cap;
5
- // the assembled multi-block summary is allowed to run longer but stays bounded
6
- // so it doesn't bloat the recall prompt.
7
- const MAX_RICH_APPROACH_CHARS = 1500;
8
- // Cap on the changed-file list persisted per episode (#272).
9
- const MAX_CHANGED_FILES = 30;
10
- /**
11
- * Rule-derived quality score (0..1) for an implement run. Failures default to 0.
12
- * Plan §"Quality score": +0.4 if build passed, +0.2 if lint passed, +0.2 if no
13
- * error thrown, +0.2 if run completed cleanly.
14
- */
15
- export function computeQualityScore(result, opts) {
16
- if (!result.passed)
17
- return 0;
18
- let score = 0;
19
- if (result.buildErrors.length === 0)
20
- score += 0.4;
21
- if (result.lintWarnings.length === 0)
22
- score += 0.2;
23
- if (!opts.errorThrown)
24
- score += 0.2;
25
- if (opts.runCompletedCleanly)
26
- score += 0.2;
27
- return Math.min(1, score);
28
- }
29
- /**
30
- * Clamp confidence into the documented [0.4, 1.0] band so failures retain a
31
- * minimum floor (plan §"Episode record shape").
32
- */
33
- function clampConfidence(qualityScore) {
34
- return Math.max(0.4, Math.min(1.0, qualityScore));
35
- }
36
- /**
37
- * Trim a free-form summary down to the documented 400-char cap. v1 uses a
38
- * last-turn trim rather than an LLM rewrite (plan §"Write hook"). Empty or
39
- * whitespace-only input collapses to a marker so the episode still surfaces
40
- * as a recallable hit (rather than an empty bullet) in future prompts.
41
- */
42
- export function trimApproachSummary(text) {
43
- const trimmed = text.trim();
44
- if (trimmed.length === 0)
45
- return "(no approach summary captured)";
46
- if (trimmed.length <= MAX_APPROACH_SUMMARY_CHARS)
47
- return trimmed;
48
- return `${trimmed.slice(0, MAX_APPROACH_SUMMARY_CHARS - 1).trimEnd()}…`;
49
- }
50
- // Review rationale cap (#272 task 5). The reviewer's verdict reasoning is the
51
- // signal we want recallable, so it gets a far higher cap than the 400-char
52
- // implement trim (the upstream review summary is already sliced to ~2000).
53
- const MAX_REVIEW_RATIONALE_CHARS = 2000;
54
- /**
55
- * Cap the review rationale ("why approved / rejected") richly (#272 task 5)
56
- * rather than re-trimming to the 400-char implement bound. Empty input
57
- * collapses to a marker so the episode still surfaces as a recallable hit.
58
- */
59
- export function trimReviewRationale(text) {
60
- const trimmed = text.trim();
61
- if (trimmed.length === 0)
62
- return "(no review rationale captured)";
63
- if (trimmed.length <= MAX_REVIEW_RATIONALE_CHARS)
64
- return trimmed;
65
- return `${trimmed.slice(0, MAX_REVIEW_RATIONALE_CHARS - 1).trimEnd()}…`;
66
- }
67
- // Lines that read like a root-cause / gotcha / lesson — used to surface a cheap
68
- // deterministic `key_insight` without an LLM call (#272 task 3).
69
- const INSIGHT_RE = /\b(root cause|turned out|the (?:issue|problem|bug) (?:was|is)|the fix (?:was|is)|gotcha|caused by|because|the key (?:was|insight)|note that|caveat|the trick (?:was|is))\b/i;
70
- /**
71
- * Assemble a richer approach summary from the collected assistant text blocks
72
- * (#272). Joins the trailing blocks (most relevant context lives near the end
73
- * of a run) up to a longer bounded cap than the single-turn trim. Falls back to
74
- * the last-turn `fallback` text when no blocks were collected. No LLM call.
75
- */
76
- export function buildRichApproachSummary(blocks, fallback) {
77
- const cleaned = (blocks ?? [])
78
- .map((b) => b.trim())
79
- .filter((b) => b.length > 0);
80
- if (cleaned.length === 0)
81
- return trimApproachSummary(fallback);
82
- // Walk backwards accumulating blocks until we hit the cap, then restore order.
83
- const picked = [];
84
- let total = 0;
85
- for (let i = cleaned.length - 1; i >= 0; i--) {
86
- const block = cleaned[i];
87
- const cost = block.length + (picked.length > 0 ? 2 : 0);
88
- if (total + cost > MAX_RICH_APPROACH_CHARS && picked.length > 0)
89
- break;
90
- picked.unshift(block);
91
- total += cost;
92
- if (total >= MAX_RICH_APPROACH_CHARS)
93
- break;
94
- }
95
- const joined = picked.join("\n\n").trim();
96
- if (joined.length === 0)
97
- return trimApproachSummary(fallback);
98
- if (joined.length <= MAX_RICH_APPROACH_CHARS)
99
- return joined;
100
- return `${joined.slice(0, MAX_RICH_APPROACH_CHARS - 1).trimEnd()}…`;
101
- }
102
- /**
103
- * Extract a cheap deterministic "key insight" line from the run's assistant
104
- * text (#272 task 3). Scans for a sentence/line matching a root-cause / gotcha
105
- * pattern. Returns undefined when nothing matches — never fabricated, no LLM.
106
- */
107
- export function extractKeyInsight(blocks, fallback) {
108
- const source = blocks && blocks.length > 0 ? blocks.join("\n") : fallback;
109
- const lines = source
110
- .split(/\n|(?<=[.!?])\s+/)
111
- .map((l) => l.trim())
112
- .filter((l) => l.length >= 20 && l.length <= 280);
113
- for (const line of lines) {
114
- if (INSIGHT_RE.test(line))
115
- return line;
116
- }
117
- return undefined;
118
- }
119
- /**
120
- * Build the entity payload for one episode. Pure — returned object can be
121
- * snapshotted in tests without hitting the network.
122
- */
123
- export function buildEpisodePayload(input, projectId) {
124
- if (input.kind === "implement") {
125
- const qualityScore = computeQualityScore(input.result, {
126
- errorThrown: input.errorMessage !== undefined,
127
- runCompletedCleanly: input.result.passed,
128
- });
129
- const type = input.outcome === "success" ? "solution" : "error";
130
- const importance = input.outcome === "success" ? 7 : 5;
131
- // Richer approach summary (#272): assemble from all collected assistant
132
- // blocks when available; fall back to the single last-turn trim otherwise.
133
- const approachSummary = buildRichApproachSummary(input.approachBlocks, input.approachSummary);
134
- const keyInsight = extractKeyInsight(input.approachBlocks, input.approachSummary);
135
- const changedFiles = (input.changedFiles ?? []).slice(0, MAX_CHANGED_FILES);
136
- const outcomeRationale = input.outcome === "success"
137
- ? `Build ${input.result.buildErrors.length === 0 ? "passed" : "failed"}, lint ${input.result.lintWarnings.length === 0 ? "clean" : "issues"}.`
138
- : `Verification failed: ${input.errorMessage ?? "see findings"}.`;
139
- const metadata = {
140
- episode_kind: "implement",
141
- card_short_id: input.card.short_id,
142
- card_title: input.card.title,
143
- approach_summary: approachSummary,
144
- outcome: input.outcome,
145
- quality_score: qualityScore,
146
- duration_ms: input.cost?.durationMs ?? 0,
147
- token_cost: {
148
- input: input.cost?.totalInputTokens ?? 0,
149
- output: input.cost?.totalOutputTokens ?? 0,
150
- usd: input.cost?.totalCostUsd ?? 0,
151
- },
152
- files_touched: input.filesEdited,
153
- num_turns: input.cost?.numTurns ?? 0,
154
- // Provenance (#273, task 3). Agent episodes are always agent-run; stamp
155
- // the card + session ids we already carry so downstream hygiene + UI can
156
- // tell auto-written episodes from human-curated patterns.
157
- origin: {
158
- source: "agent-run",
159
- source_card_id: input.card.id,
160
- author: "harmony-agent",
161
- ...(input.agentSessionId
162
- ? { source_session_id: input.agentSessionId }
163
- : {}),
164
- },
165
- };
166
- if (input.errorMessage)
167
- metadata.error = input.errorMessage;
168
- if (changedFiles.length > 0)
169
- metadata.changed_files = changedFiles;
170
- if (input.churn)
171
- metadata.churn = input.churn;
172
- if (keyInsight)
173
- metadata.key_insight = keyInsight;
174
- if (input.agentSessionId)
175
- metadata.agent_session_id = input.agentSessionId;
176
- const changedFilesLine = changedFiles.length > 0
177
- ? `\n\nChanged files (${changedFiles.length}): ${changedFiles.join(", ")}`
178
- : "";
179
- const keyInsightLine = keyInsight ? `\n\nKey insight: ${keyInsight}` : "";
180
- return {
181
- workspace_id: input.workspaceId,
182
- project_id: projectId,
183
- type,
184
- memory_tier: "episode",
185
- scope: "project",
186
- title: `Agent run implement — #${input.card.short_id}: ${input.card.title}`,
187
- content: `${approachSummary}\n\nOutcome: ${outcomeRationale}${keyInsightLine}${changedFilesLine}`,
188
- metadata,
189
- importance,
190
- confidence: clampConfidence(qualityScore),
191
- tags: ["implement", input.outcome, `card:${input.card.short_id}`],
192
- agent_identifier: "harmony-agent",
193
- };
194
- }
195
- // Review episode
196
- const qualityScore = input.verdict === "approved" ? 1 : 0.4;
197
- const summary = trimReviewRationale(input.summary || "");
198
- const metadata = {
199
- episode_kind: "review",
200
- card_short_id: input.card.short_id,
201
- card_title: input.card.title,
202
- approach_summary: summary,
203
- outcome: input.verdict === "approved" ? "success" : "failure",
204
- quality_score: qualityScore,
205
- duration_ms: input.cost?.durationMs ?? 0,
206
- token_cost: {
207
- input: input.cost?.totalInputTokens ?? 0,
208
- output: input.cost?.totalOutputTokens ?? 0,
209
- usd: input.cost?.totalCostUsd ?? 0,
210
- },
211
- files_touched: 0,
212
- num_turns: input.cost?.numTurns ?? 0,
213
- // Provenance (#273, task 3). Review episodes are agent-run too. Prefer the
214
- // review session id as the originating session when present.
215
- origin: {
216
- source: "agent-run",
217
- source_card_id: input.card.id,
218
- author: "harmony-agent",
219
- ...((input.reviewSessionId ?? input.agentSessionId)
220
- ? {
221
- source_session_id: (input.reviewSessionId ??
222
- input.agentSessionId),
223
- }
224
- : {}),
225
- },
226
- };
227
- if (input.agentSessionId)
228
- metadata.agent_session_id = input.agentSessionId;
229
- if (input.reviewSessionId)
230
- metadata.review_session_id = input.reviewSessionId;
231
- if (input.originalEpisodeId)
232
- metadata.original_episode_id = input.originalEpisodeId;
233
- return {
234
- workspace_id: input.workspaceId,
235
- project_id: projectId,
236
- type: "decision",
237
- memory_tier: "episode",
238
- scope: "project",
239
- title: `Agent run review — #${input.card.short_id}: ${input.card.title}`,
240
- content: `Review verdict: ${input.verdict}.\n\n${summary}`,
241
- metadata,
242
- importance: 8,
243
- confidence: clampConfidence(qualityScore),
244
- tags: ["review", input.verdict, `card:${input.card.short_id}`],
245
- agent_identifier: "harmony-agent",
246
- };
247
- }
248
- // DEFERRED (#272 task 6, on-plan): an optional best-effort Claude-CLI
249
- // distillation of the approach summary / key insight is explicitly out of scope
250
- // for this pass — no Anthropic SDK, no extra LLM spawn. The deterministic
251
- // assembly above is the v1 surface; revisit distillation in a later card.
252
- /**
253
- * Write one episode entity. Best-effort: any failure is logged and swallowed
254
- * so the calling pipeline can complete (plan D8: episode writes never block
255
- * run completion).
256
- *
257
- * Returns the entity id on success, or null on swallowed failure.
258
- */
259
- export async function writeEpisode(client, input) {
260
- const payload = buildEpisodePayload(input, input.card.project_id);
261
- try {
262
- const { entity } = await client.createMemoryEntity({
263
- ...payload,
264
- metadata: payload.metadata,
265
- });
266
- const id = entity && typeof entity === "object" && "id" in entity
267
- ? (entity.id ?? null)
268
- : null;
269
- log.info(TAG, `episode written for #${input.card.short_id}`, {
270
- cardId: input.card.id,
271
- event: "episode_write",
272
- kind: input.kind,
273
- });
274
- return id;
275
- }
276
- catch (err) {
277
- log.warn(TAG, `episode write failed for #${input.card.short_id}`, {
278
- cardId: input.card.id,
279
- event: "episode_write_failed",
280
- kind: input.kind,
281
- error: err instanceof Error ? err.message : String(err),
282
- });
283
- return null;
284
- }
285
- }
286
- /**
287
- * Find the most recent implement episode for a given card so the review
288
- * pipeline can back-fill its verdict. Returns null when none exists or the
289
- * lookup throws — back-fill is best-effort.
290
- */
291
- export async function findLatestImplementEpisode(client, workspaceId, projectId, cardShortId) {
292
- try {
293
- const { entities } = await client.harmonyRecall({
294
- workspaceId,
295
- projectId,
296
- type: ["solution", "error"],
297
- memory_tier: "episode",
298
- scope: "project",
299
- tags: [`card:${cardShortId}`],
300
- topK: 1,
301
- });
302
- const first = entities[0];
303
- if (first &&
304
- typeof first === "object" &&
305
- "id" in first &&
306
- typeof first.id === "string") {
307
- return first.id;
308
- }
309
- return null;
310
- }
311
- catch (err) {
312
- log.warn(TAG, "implement-episode lookup failed", {
313
- event: "episode_lookup_failed",
314
- cardShortId,
315
- error: err instanceof Error ? err.message : String(err),
316
- });
317
- return null;
318
- }
319
- }
320
- /**
321
- * Apply the review verdict to an earlier implement episode (plan §"Read hook"
322
- * back-fill block). Approved nudges the original episode's confidence up;
323
- * rejected tombstones it via superseded_by.
324
- */
325
- export async function backfillReviewVerdict(client, originalEpisodeId, verdict, reviewEpisodeId) {
326
- try {
327
- if (verdict === "approved") {
328
- const { entity } = await client.getMemoryEntity(originalEpisodeId);
329
- const current = entity?.confidence ?? 0.4;
330
- const bumped = Math.min(1, current + 0.05);
331
- await client.updateMemoryEntity(originalEpisodeId, {
332
- confidence: bumped,
333
- });
334
- }
335
- else {
336
- await client.updateMemoryEntity(originalEpisodeId, {
337
- superseded_by: reviewEpisodeId,
338
- });
339
- }
340
- }
341
- catch (err) {
342
- log.warn(TAG, "review back-fill failed", {
343
- event: "episode_backfill_failed",
344
- originalEpisodeId,
345
- verdict,
346
- error: err instanceof Error ? err.message : String(err),
347
- });
348
- }
349
- }
@@ -1,24 +0,0 @@
1
- /** Default cap on the number of changed-file paths captured per episode. */
2
- export declare const MAX_CHANGED_FILES = 30;
3
- export interface DiffStat {
4
- /** Changed file paths (authoritative — derived from the diff itself). */
5
- files: string[];
6
- /** Total lines added across the diff (best-effort, may be 0). */
7
- insertions: number;
8
- /** Total lines removed across the diff (best-effort, may be 0). */
9
- deletions: number;
10
- }
11
- /**
12
- * Parse `git diff --numstat` output into a changed-file list + churn totals.
13
- * Pure — exported for testing. numstat lines look like:
14
- * `12\t3\tpath/to/file.ts`
15
- * Binary files report `-\t-\t<path>`; those contribute 0 churn but still count
16
- * as a changed file. The file list is capped at `maxFiles`.
17
- */
18
- export declare function parseNumstat(raw: string, maxFiles?: number): DiffStat;
19
- /**
20
- * Capture a changed-file list + churn for the work on a branch versus its base,
21
- * via `git diff --numstat`. Best-effort and guarded: returns null on any
22
- * failure so callers can fall back to tracked paths (#272). Never throws.
23
- */
24
- export declare function captureDiffStat(worktreePath: string, baseBranch: string, maxFiles?: number): DiffStat | null;
@@ -1,56 +0,0 @@
1
- import { execFileSync } from "node:child_process";
2
- import { log } from "./log.js";
3
- const TAG = "git-diff-stat";
4
- /** Default cap on the number of changed-file paths captured per episode. */
5
- export const MAX_CHANGED_FILES = 30;
6
- /**
7
- * Parse `git diff --numstat` output into a changed-file list + churn totals.
8
- * Pure — exported for testing. numstat lines look like:
9
- * `12\t3\tpath/to/file.ts`
10
- * Binary files report `-\t-\t<path>`; those contribute 0 churn but still count
11
- * as a changed file. The file list is capped at `maxFiles`.
12
- */
13
- export function parseNumstat(raw, maxFiles = MAX_CHANGED_FILES) {
14
- const files = [];
15
- let insertions = 0;
16
- let deletions = 0;
17
- for (const line of raw.split("\n")) {
18
- const trimmed = line.trim();
19
- if (trimmed.length === 0)
20
- continue;
21
- const parts = trimmed.split("\t");
22
- if (parts.length < 3)
23
- continue;
24
- const [add, del, ...pathParts] = parts;
25
- const path = pathParts.join("\t");
26
- if (!path)
27
- continue;
28
- const addN = Number.parseInt(add, 10);
29
- const delN = Number.parseInt(del, 10);
30
- if (Number.isFinite(addN))
31
- insertions += addN;
32
- if (Number.isFinite(delN))
33
- deletions += delN;
34
- if (files.length < maxFiles)
35
- files.push(path);
36
- }
37
- return { files, insertions, deletions };
38
- }
39
- /**
40
- * Capture a changed-file list + churn for the work on a branch versus its base,
41
- * via `git diff --numstat`. Best-effort and guarded: returns null on any
42
- * failure so callers can fall back to tracked paths (#272). Never throws.
43
- */
44
- export function captureDiffStat(worktreePath, baseBranch, maxFiles = MAX_CHANGED_FILES) {
45
- try {
46
- const raw = execFileSync("git", ["diff", "--numstat", `${baseBranch}...HEAD`], { cwd: worktreePath, encoding: "utf-8", timeout: 30_000 });
47
- return parseNumstat(raw, maxFiles);
48
- }
49
- catch (err) {
50
- log.warn(TAG, "git diff --numstat failed", {
51
- event: "diff_stat_failed",
52
- error: err instanceof Error ? err.message : String(err),
53
- });
54
- return null;
55
- }
56
- }
package/dist/git-pr.d.ts DELETED
@@ -1,38 +0,0 @@
1
- import type { Card } from "@harmony/shared";
2
- import type { AgentConfig } from "./types.js";
3
- export type GitProvider = "github" | "azure" | "gitlab" | "bitbucket" | "unknown";
4
- export declare function detectGitProvider(cwd?: string): GitProvider;
5
- /**
6
- * Validate that the CLI for the detected git provider is installed and authenticated.
7
- * Returns the provider name or throws with instructions.
8
- */
9
- export declare function validateGitProviderCli(provider: GitProvider, cwd?: string): void;
10
- export type PrState = "merged" | "open" | "closed" | "unknown";
11
- /**
12
- * Check whether a PR has been merged using the git provider CLI (async).
13
- */
14
- export declare function checkPrMergeStatus(prUrl: string, cwd: string, provider: GitProvider): Promise<PrState>;
15
- /**
16
- * Extract a PR URL from card description. Matches the format written by completion.ts:
17
- * `PR: https://...`
18
- */
19
- export declare function extractPrUrl(description: string | null): string | null;
20
- export declare function remoteBranchExists(branchName: string, cwd: string): boolean;
21
- export declare function pushBranch(branchName: string, cwd: string): void;
22
- /**
23
- * Push the current branch's tip to `newRef` on origin and delete `oldRef`.
24
- * Used when an approved attempt graduates from `agent-attempts/*` to
25
- * `agent/*` — keeps the commits durable across the rename and avoids any
26
- * window where the work is unreachable on origin.
27
- */
28
- export declare function renameRemoteBranch(oldRef: string, newRef: string, cwd: string): void;
29
- /**
30
- * Best-effort public branch URL for the recovery button on a failed session.
31
- * Returns null when we can't infer a tree URL — the daemon falls back to a
32
- * plain `git fetch && git checkout <ref>` instruction in that case.
33
- */
34
- export declare function getBranchWebUrl(branchName: string, cwd: string): string | null;
35
- export declare function buildPrBody(card: Card, commitLog: string): string;
36
- export declare function createPullRequest(card: Card, branchName: string, worktreePath: string, config: AgentConfig, provider: GitProvider): string | null;
37
- export declare function findExistingPr(branchName: string, worktreePath: string, provider: GitProvider): string | null;
38
- export declare function updateExistingPr(branchName: string, body: string, worktreePath: string, provider: GitProvider): void;