@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.
- package/dist/cli.js +6376 -141
- package/dist/index.js +6206 -333
- package/package.json +2 -2
- package/dist/board-helpers.d.ts +0 -31
- package/dist/board-helpers.js +0 -150
- package/dist/budget.d.ts +0 -39
- package/dist/budget.js +0 -73
- package/dist/cli.d.ts +0 -14
- package/dist/completion.d.ts +0 -36
- package/dist/completion.js +0 -322
- package/dist/config-validation.d.ts +0 -23
- package/dist/config-validation.js +0 -77
- package/dist/config.d.ts +0 -23
- package/dist/config.js +0 -103
- package/dist/episode-writer.d.ts +0 -116
- package/dist/episode-writer.js +0 -349
- package/dist/git-diff-stat.d.ts +0 -24
- package/dist/git-diff-stat.js +0 -56
- package/dist/git-pr.d.ts +0 -38
- package/dist/git-pr.js +0 -399
- package/dist/http-server.d.ts +0 -66
- package/dist/http-server.js +0 -96
- package/dist/index.d.ts +0 -5
- package/dist/log.d.ts +0 -34
- package/dist/log.js +0 -100
- package/dist/merge-monitor.d.ts +0 -23
- package/dist/merge-monitor.js +0 -169
- package/dist/pm.d.ts +0 -14
- package/dist/pm.js +0 -63
- package/dist/pool.d.ts +0 -71
- package/dist/pool.js +0 -259
- package/dist/process-group.d.ts +0 -26
- package/dist/process-group.js +0 -72
- package/dist/progress-tracker.d.ts +0 -82
- package/dist/progress-tracker.js +0 -457
- package/dist/prompt.d.ts +0 -23
- package/dist/prompt.js +0 -160
- package/dist/queue.d.ts +0 -39
- package/dist/queue.js +0 -100
- package/dist/reconcile.d.ts +0 -35
- package/dist/reconcile.js +0 -174
- package/dist/recovery.d.ts +0 -30
- package/dist/recovery.js +0 -141
- package/dist/review-completion.d.ts +0 -35
- package/dist/review-completion.js +0 -475
- package/dist/review-knowledge.d.ts +0 -14
- package/dist/review-knowledge.js +0 -89
- package/dist/review-prompt.d.ts +0 -12
- package/dist/review-prompt.js +0 -103
- package/dist/review-worker.d.ts +0 -56
- package/dist/review-worker.js +0 -638
- package/dist/review-worktree.d.ts +0 -12
- package/dist/review-worktree.js +0 -95
- package/dist/run-log.d.ts +0 -6
- package/dist/run-log.js +0 -19
- package/dist/startup-banner.d.ts +0 -29
- package/dist/startup-banner.js +0 -143
- package/dist/state-store.d.ts +0 -89
- package/dist/state-store.js +0 -230
- package/dist/stream-parser-selftest.d.ts +0 -9
- package/dist/stream-parser-selftest.js +0 -97
- package/dist/stream-parser.d.ts +0 -43
- package/dist/stream-parser.js +0 -174
- package/dist/transitions.d.ts +0 -57
- package/dist/transitions.js +0 -131
- package/dist/types.d.ts +0 -167
- package/dist/types.js +0 -76
- package/dist/verification.d.ts +0 -39
- package/dist/verification.js +0 -317
- package/dist/watcher.d.ts +0 -53
- package/dist/watcher.js +0 -153
- package/dist/worker.d.ts +0 -54
- package/dist/worker.js +0 -507
- package/dist/worktree-gc.d.ts +0 -67
- package/dist/worktree-gc.js +0 -245
- package/dist/worktree.d.ts +0 -18
- package/dist/worktree.js +0 -177
package/dist/episode-writer.js
DELETED
|
@@ -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
|
-
}
|
package/dist/git-diff-stat.d.ts
DELETED
|
@@ -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;
|
package/dist/git-diff-stat.js
DELETED
|
@@ -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;
|