@gethmy/agent 1.7.0 → 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 (76) hide show
  1. package/README.md +8 -1
  2. package/dist/cli.js +6376 -205
  3. package/dist/index.js +6206 -341
  4. package/package.json +2 -2
  5. package/dist/board-helpers.d.ts +0 -31
  6. package/dist/board-helpers.js +0 -150
  7. package/dist/budget.d.ts +0 -47
  8. package/dist/budget.js +0 -161
  9. package/dist/cli.d.ts +0 -16
  10. package/dist/completion.d.ts +0 -32
  11. package/dist/completion.js +0 -304
  12. package/dist/config-validation.d.ts +0 -23
  13. package/dist/config-validation.js +0 -77
  14. package/dist/config.d.ts +0 -23
  15. package/dist/config.js +0 -103
  16. package/dist/episode-writer.d.ts +0 -84
  17. package/dist/episode-writer.js +0 -232
  18. package/dist/git-pr.d.ts +0 -38
  19. package/dist/git-pr.js +0 -399
  20. package/dist/http-server.d.ts +0 -79
  21. package/dist/http-server.js +0 -114
  22. package/dist/index.d.ts +0 -5
  23. package/dist/log.d.ts +0 -34
  24. package/dist/log.js +0 -100
  25. package/dist/merge-monitor.d.ts +0 -23
  26. package/dist/merge-monitor.js +0 -169
  27. package/dist/pm.d.ts +0 -14
  28. package/dist/pm.js +0 -63
  29. package/dist/pool.d.ts +0 -70
  30. package/dist/pool.js +0 -258
  31. package/dist/process-group.d.ts +0 -26
  32. package/dist/process-group.js +0 -72
  33. package/dist/progress-tracker.d.ts +0 -79
  34. package/dist/progress-tracker.js +0 -442
  35. package/dist/prompt.d.ts +0 -18
  36. package/dist/prompt.js +0 -117
  37. package/dist/queue.d.ts +0 -39
  38. package/dist/queue.js +0 -100
  39. package/dist/reconcile.d.ts +0 -35
  40. package/dist/reconcile.js +0 -174
  41. package/dist/recovery.d.ts +0 -30
  42. package/dist/recovery.js +0 -141
  43. package/dist/review-completion.d.ts +0 -40
  44. package/dist/review-completion.js +0 -474
  45. package/dist/review-knowledge.d.ts +0 -14
  46. package/dist/review-knowledge.js +0 -89
  47. package/dist/review-prompt.d.ts +0 -12
  48. package/dist/review-prompt.js +0 -103
  49. package/dist/review-worker.d.ts +0 -56
  50. package/dist/review-worker.js +0 -638
  51. package/dist/review-worktree.d.ts +0 -12
  52. package/dist/review-worktree.js +0 -95
  53. package/dist/run-log.d.ts +0 -6
  54. package/dist/run-log.js +0 -19
  55. package/dist/startup-banner.d.ts +0 -29
  56. package/dist/startup-banner.js +0 -143
  57. package/dist/state-store.d.ts +0 -88
  58. package/dist/state-store.js +0 -239
  59. package/dist/stream-parser-selftest.d.ts +0 -9
  60. package/dist/stream-parser-selftest.js +0 -97
  61. package/dist/stream-parser.d.ts +0 -43
  62. package/dist/stream-parser.js +0 -174
  63. package/dist/transitions.d.ts +0 -57
  64. package/dist/transitions.js +0 -131
  65. package/dist/types.d.ts +0 -140
  66. package/dist/types.js +0 -79
  67. package/dist/verification.d.ts +0 -39
  68. package/dist/verification.js +0 -317
  69. package/dist/watcher.d.ts +0 -53
  70. package/dist/watcher.js +0 -153
  71. package/dist/worker.d.ts +0 -53
  72. package/dist/worker.js +0 -464
  73. package/dist/worktree-gc.d.ts +0 -67
  74. package/dist/worktree-gc.js +0 -245
  75. package/dist/worktree.d.ts +0 -18
  76. package/dist/worktree.js +0 -177
@@ -1,304 +0,0 @@
1
- import { execFileSync } from "node:child_process";
2
- import { moveCardToColumn } from "./board-helpers.js";
3
- import { writeEpisode } from "./episode-writer.js";
4
- import { createPullRequest, detectGitProvider, getBranchWebUrl, pushBranch, } from "./git-pr.js";
5
- import { log } from "./log.js";
6
- import { AGENT_NAME, agentIdentifier } from "./types.js";
7
- import { attemptAutoFix, reportFindings, runVerification, } from "./verification.js";
8
- import { cleanupWorktree } from "./worktree.js";
9
- const TAG = "completion";
10
- function formatTokenCount(tokens) {
11
- if (tokens >= 1_000_000)
12
- return `${(tokens / 1_000_000).toFixed(1)}M`;
13
- if (tokens >= 1_000)
14
- return `${(tokens / 1_000).toFixed(1)}k`;
15
- return String(tokens);
16
- }
17
- export function buildTokenPayload(stats) {
18
- if (!stats?.cost)
19
- return {};
20
- return {
21
- costCents: Math.round(stats.cost.totalCostUsd * 100),
22
- inputTokens: stats.cost.totalInputTokens,
23
- outputTokens: stats.cost.totalOutputTokens,
24
- cacheCreationInputTokens: stats.cost.totalCacheCreationInputTokens,
25
- cacheReadInputTokens: stats.cost.totalCacheReadInputTokens,
26
- modelName: stats.cost.modelName,
27
- };
28
- }
29
- /**
30
- * Post-work pipeline: push branch, create PR, move card, post summary.
31
- */
32
- export async function runCompletion(client, card, branchName, worktreePath, config, workerId, sessionStats, workspaceId, agentSessionId, stateStore) {
33
- // Hoisted so the episode write hook can read final verification state.
34
- let verificationResult = {
35
- passed: true,
36
- buildErrors: [],
37
- lintWarnings: [],
38
- reviewFindings: [],
39
- };
40
- // Check if there are any commits on the branch
41
- const hasCommits = checkHasCommits(worktreePath, config.worktree.baseBranch);
42
- if (!hasCommits) {
43
- log.warn(TAG, `No commits on branch ${branchName} — skipping completion`);
44
- await client.endAgentSession(card.id, {
45
- status: "completed",
46
- progressPercent: 100,
47
- ...buildTokenPayload(sessionStats),
48
- });
49
- cleanupWorktree(worktreePath, branchName);
50
- return;
51
- }
52
- // 1. Push branch FIRST so commits are durable on origin regardless of
53
- // verification outcome. A failed verify (below) then preserves the work
54
- // under `agent-attempts/*` for `failedAttemptRetentionDays`. Without this
55
- // ordering, verify failures used to orphan commits in a deleted worktree —
56
- // recoverable only via `git reflog`.
57
- //
58
- // `lastPushedSha` tracks the most recent commit we successfully pushed so
59
- // later push points (post-fix, post-fail) can skip when HEAD hasn't moved.
60
- log.info(TAG, `Pushing branch ${branchName} (pre-verify)...`);
61
- let lastPushedSha = null;
62
- try {
63
- pushBranch(branchName, worktreePath);
64
- lastPushedSha = readHeadSha(worktreePath);
65
- }
66
- catch (err) {
67
- // Push failure shouldn't prevent verification from running, but the
68
- // safety guarantee is gone — surface it loudly so the operator notices.
69
- log.error(TAG, `pre-verify push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
70
- }
71
- // Only surface a recovery URL when the push actually landed — otherwise
72
- // the link points at a ref that doesn't exist on origin.
73
- const recoveryUrl = lastPushedSha
74
- ? getBranchWebUrl(branchName, worktreePath)
75
- : null;
76
- // 2. Verification gate
77
- if (config.verification.enabled) {
78
- await client.updateAgentProgress(card.id, {
79
- agentIdentifier: agentIdentifier(workerId),
80
- agentName: AGENT_NAME,
81
- status: "working",
82
- currentTask: "Verifying build...",
83
- progressPercent: 80,
84
- });
85
- let result = await runVerification(worktreePath, config, workerId);
86
- let autoFixAttempts = 0;
87
- if (!result.passed && config.verification.autoFix) {
88
- for (let attempt = 0; attempt < config.verification.maxFixAttempts; attempt++) {
89
- log.info(TAG, `Auto-fix attempt ${attempt + 1}/${config.verification.maxFixAttempts}`);
90
- await client.updateAgentProgress(card.id, {
91
- agentIdentifier: agentIdentifier(workerId),
92
- agentName: AGENT_NAME,
93
- status: "working",
94
- currentTask: `Fixing issues (attempt ${attempt + 1})...`,
95
- progressPercent: 85,
96
- });
97
- const allErrors = [...result.buildErrors, ...result.lintWarnings];
98
- await attemptAutoFix(worktreePath, config, allErrors);
99
- result = await runVerification(worktreePath, config, workerId);
100
- autoFixAttempts = attempt + 1;
101
- if (result.passed) {
102
- log.info(TAG, `Auto-fix succeeded on attempt ${attempt + 1}`);
103
- // Push again so any auto-fix commits are durable on origin. Skip
104
- // when HEAD hasn't moved since the last successful push — autofix
105
- // can pass without producing new commits.
106
- const sha = readHeadSha(worktreePath);
107
- if (sha && sha !== lastPushedSha) {
108
- try {
109
- pushBranch(branchName, worktreePath);
110
- lastPushedSha = sha;
111
- }
112
- catch (err) {
113
- log.warn(TAG, `post-fix push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
114
- }
115
- }
116
- break;
117
- }
118
- }
119
- }
120
- verificationResult = result;
121
- if (!result.passed) {
122
- log.warn(TAG, `Verification failed for #${card.short_id} — reporting findings`);
123
- // Push the latest tip (including any auto-fix attempts) so the
124
- // failed branch on origin reflects what verify saw. Skip when HEAD
125
- // hasn't advanced since the last successful push — common when
126
- // every auto-fix attempt produced no new commits.
127
- const failSha = readHeadSha(worktreePath);
128
- if (failSha && failSha !== lastPushedSha) {
129
- try {
130
- pushBranch(branchName, worktreePath);
131
- lastPushedSha = failSha;
132
- }
133
- catch (err) {
134
- log.warn(TAG, `post-fail push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
135
- }
136
- }
137
- const failureSummary = buildVerificationFailureSummary(result, autoFixAttempts);
138
- try {
139
- await stateStore.recordFailureSummary(card.id, {
140
- summary: failureSummary,
141
- reason: "verification",
142
- recoveryBranch: branchName,
143
- });
144
- }
145
- catch (err) {
146
- log.debug(TAG, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
147
- }
148
- // Only attach recovery instructions when the branch actually exists
149
- // on origin — otherwise the `git fetch && git checkout` command we'd
150
- // surface would fail for the user.
151
- await reportFindings(client, card.id, result, lastPushedSha ? { branchName, branchUrl: recoveryUrl } : null);
152
- await moveCardToColumn(client, card, config.verification.failColumn);
153
- await client.endAgentSession(card.id, {
154
- status: "failed",
155
- failureReason: "verification",
156
- failureSummary,
157
- recoveryBranch: branchName,
158
- ...buildTokenPayload(sessionStats),
159
- });
160
- // Local-only cleanup. The remote ref under `agent-attempts/*` stays
161
- // up; the GC sweep (worktree-gc.ts) prunes it after retention.
162
- cleanupWorktree(worktreePath, branchName);
163
- return;
164
- }
165
- log.info(TAG, `Verification passed for #${card.short_id}`);
166
- }
167
- // 3. Create PR
168
- let prUrl = null;
169
- if (config.completion.createPR) {
170
- const provider = detectGitProvider(worktreePath);
171
- prUrl = createPullRequest(card, branchName, worktreePath, config, provider);
172
- }
173
- // 4. Move card to Review column
174
- if (config.completion.moveToColumn) {
175
- await moveCardToColumn(client, card, config.completion.moveToColumn);
176
- }
177
- // 5. Post summary — always includes branch, optionally PR link
178
- if (config.completion.postSummary) {
179
- await postSummary(client, card, branchName, worktreePath, prUrl, config.worktree.baseBranch, sessionStats);
180
- }
181
- // 6. End agent session (include final token/cost snapshot)
182
- await client.endAgentSession(card.id, {
183
- status: "completed",
184
- progressPercent: 100,
185
- ...buildTokenPayload(sessionStats),
186
- });
187
- // 6a. Episode write hook (Phase 1.5): completed implement runs accumulate
188
- // into project-scoped episodic memory. Best-effort — failures never block
189
- // the completion path (plan §"Write hook" + D8).
190
- //
191
- // Outcome is constant "success" here: verification failures return early
192
- // above with status=paused, and D8 mandates paused/orphaned runs skip the
193
- // episode write entirely. A failure-outcome episode would require routing
194
- // a separate write hook into the pre-return path, which D8 intentionally
195
- // omits ("daemon crashes ≠ task outcome").
196
- if (workspaceId) {
197
- await writeEpisode(client, {
198
- kind: "implement",
199
- card,
200
- workspaceId,
201
- outcome: "success",
202
- approachSummary: sessionStats?.lastAssistantText ?? "",
203
- result: verificationResult,
204
- cost: sessionStats?.cost ?? null,
205
- filesEdited: sessionStats?.filesEdited ?? 0,
206
- agentSessionId: agentSessionId ?? null,
207
- });
208
- }
209
- // 7. Cleanup worktree
210
- cleanupWorktree(worktreePath, branchName);
211
- log.info(TAG, `Completion done for #${card.short_id}${prUrl ? ` — PR: ${prUrl}` : ""}`);
212
- }
213
- function buildVerificationFailureSummary(result, autoFixAttempts) {
214
- const counts = [];
215
- if (result.buildErrors.length > 0) {
216
- counts.push(`${result.buildErrors.length} build error(s)`);
217
- }
218
- if (result.lintWarnings.length > 0) {
219
- counts.push(`${result.lintWarnings.length} lint issue(s)`);
220
- }
221
- if (result.reviewFindings.length > 0) {
222
- counts.push(`${result.reviewFindings.length} review finding(s)`);
223
- }
224
- const head = counts.length > 0 ? counts.join(", ") : "verification failed";
225
- const tail = autoFixAttempts > 0 ? ` after ${autoFixAttempts} auto-fix attempt(s)` : "";
226
- return `${head}${tail}`;
227
- }
228
- function readHeadSha(worktreePath) {
229
- try {
230
- return execFileSync("git", ["rev-parse", "HEAD"], {
231
- cwd: worktreePath,
232
- encoding: "utf-8",
233
- }).trim();
234
- }
235
- catch {
236
- return null;
237
- }
238
- }
239
- function checkHasCommits(worktreePath, baseBranch) {
240
- try {
241
- const count = execFileSync("git", ["rev-list", "--count", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
242
- return parseInt(count, 10) > 0;
243
- }
244
- catch {
245
- return false;
246
- }
247
- }
248
- async function postSummary(client, card, branchName, worktreePath, prUrl, baseBranch, sessionStats) {
249
- // Build commit summary
250
- let commitLog = "";
251
- try {
252
- commitLog = execFileSync("git", ["log", "--oneline", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
253
- }
254
- catch {
255
- // best-effort
256
- }
257
- // Build description note
258
- const SUMMARY_MARKER = "---\n**Agent completed**";
259
- const parts = [`\n\n${SUMMARY_MARKER}`];
260
- if (prUrl) {
261
- parts.push(`PR: ${prUrl}`);
262
- }
263
- parts.push(`Branch: \`${branchName}\``);
264
- if (sessionStats) {
265
- const statParts = [];
266
- statParts.push(`${sessionStats.toolCalls} tool calls`);
267
- statParts.push(`${sessionStats.filesEdited} files edited`);
268
- statParts.push(`${sessionStats.filesRead} files read`);
269
- if (sessionStats.cost) {
270
- statParts.push(`$${sessionStats.cost.totalCostUsd.toFixed(2)} cost`);
271
- statParts.push(`${sessionStats.cost.numTurns} turns`);
272
- const totalInput = sessionStats.cost.totalInputTokens +
273
- sessionStats.cost.totalCacheCreationInputTokens +
274
- sessionStats.cost.totalCacheReadInputTokens;
275
- const totalTokens = totalInput + sessionStats.cost.totalOutputTokens;
276
- if (totalTokens > 0) {
277
- statParts.push(`${formatTokenCount(totalTokens)} tokens`);
278
- }
279
- const cacheRead = sessionStats.cost.totalCacheReadInputTokens;
280
- if (totalInput > 0 && cacheRead > 0) {
281
- const hitPct = Math.round((cacheRead / totalInput) * 100);
282
- statParts.push(`${hitPct}% cache hit`);
283
- }
284
- }
285
- parts.push(`Stats: ${statParts.join(" · ")}`);
286
- }
287
- if (commitLog) {
288
- parts.push(`\n\`\`\`\n${commitLog}\n\`\`\``);
289
- }
290
- try {
291
- // Strip any previous agent summary block before appending (rework)
292
- const existingDesc = card.description || "";
293
- const baseDesc = existingDesc.includes(SUMMARY_MARKER)
294
- ? existingDesc.slice(0, existingDesc.indexOf(SUMMARY_MARKER)).trimEnd()
295
- : existingDesc;
296
- await client.updateCard(card.id, {
297
- description: baseDesc + parts.join("\n"),
298
- });
299
- log.info(TAG, `Posted completion summary to #${card.short_id}`);
300
- }
301
- catch (err) {
302
- log.error(TAG, `Failed to post summary: ${err instanceof Error ? err.message : err}`);
303
- }
304
- }
@@ -1,23 +0,0 @@
1
- import type { HarmonyApiClient } from "@gethmy/mcp/src/api-client.js";
2
- import type { AgentConfig } from "./types.js";
3
- export declare class ConfigValidationError extends Error {
4
- readonly issues: string[];
5
- constructor(message: string, issues: string[]);
6
- }
7
- /**
8
- * Validate that every column referenced in the agent config actually
9
- * exists on the board. Labels that the daemon needs to create (agent,
10
- * Ready to Merge, Merged, Need Review, agent-recovered) are not
11
- * required to pre-exist — the daemon creates them on demand. Columns
12
- * must already exist because moving a card to a non-existent column
13
- * silently fails and leaves cards stuck.
14
- *
15
- * Fail-fast on any mismatch: the operator fixes their config OR the
16
- * board before the daemon is useful.
17
- */
18
- export declare function validateColumnReferences(client: HarmonyApiClient, projectId: string, config: AgentConfig): Promise<void>;
19
- /**
20
- * Same checks as validateColumnReferences but returns the unique list of
21
- * validated column names (used by the startup banner). Throws on mismatch.
22
- */
23
- export declare function validateAndListColumns(client: HarmonyApiClient, projectId: string, config: AgentConfig): Promise<string[]>;
@@ -1,77 +0,0 @@
1
- export class ConfigValidationError extends Error {
2
- issues;
3
- constructor(message, issues) {
4
- super(message);
5
- this.issues = issues;
6
- this.name = "ConfigValidationError";
7
- }
8
- }
9
- function columnNames(board) {
10
- return board.columns.map((c) => c.name);
11
- }
12
- function findColumn(board, name) {
13
- const target = name.toLowerCase();
14
- return board.columns.some((c) => c.name.toLowerCase() === target);
15
- }
16
- /**
17
- * Validate that every column referenced in the agent config actually
18
- * exists on the board. Labels that the daemon needs to create (agent,
19
- * Ready to Merge, Merged, Need Review, agent-recovered) are not
20
- * required to pre-exist — the daemon creates them on demand. Columns
21
- * must already exist because moving a card to a non-existent column
22
- * silently fails and leaves cards stuck.
23
- *
24
- * Fail-fast on any mismatch: the operator fixes their config OR the
25
- * board before the daemon is useful.
26
- */
27
- export async function validateColumnReferences(client, projectId, config) {
28
- const board = (await client.getBoard(projectId, {
29
- summary: true,
30
- }));
31
- const known = columnNames(board);
32
- const issues = [];
33
- const required = [
34
- ...config.pickupColumns.map((c) => ({ value: c, where: "pickupColumns" })),
35
- {
36
- value: config.completion.moveToColumn,
37
- where: "completion.moveToColumn",
38
- },
39
- {
40
- value: config.verification.failColumn,
41
- where: "verification.failColumn",
42
- },
43
- ];
44
- if (config.review.enabled) {
45
- for (const c of config.review.pickupColumns) {
46
- required.push({ value: c, where: "review.pickupColumns" });
47
- }
48
- required.push({ value: config.review.moveToColumn, where: "review.moveToColumn" }, { value: config.review.failColumn, where: "review.failColumn" });
49
- }
50
- for (const { value, where } of required) {
51
- if (!value)
52
- continue;
53
- if (!findColumn(board, value)) {
54
- issues.push(`${where}: column "${value}" not found on board`);
55
- }
56
- }
57
- if (issues.length > 0) {
58
- const help = `Available columns: ${known.join(", ")}`;
59
- throw new ConfigValidationError(`Invalid agent config — the following columns are missing:\n - ${issues.join("\n - ")}\n${help}`, issues);
60
- }
61
- }
62
- /**
63
- * Same checks as validateColumnReferences but returns the unique list of
64
- * validated column names (used by the startup banner). Throws on mismatch.
65
- */
66
- export async function validateAndListColumns(client, projectId, config) {
67
- await validateColumnReferences(client, projectId, config);
68
- const names = [
69
- ...config.pickupColumns,
70
- config.completion.moveToColumn,
71
- config.verification.failColumn,
72
- ];
73
- if (config.review.enabled) {
74
- names.push(...config.review.pickupColumns, config.review.moveToColumn, config.review.failColumn);
75
- }
76
- return Array.from(new Set(names.filter(Boolean)));
77
- }
package/dist/config.d.ts DELETED
@@ -1,23 +0,0 @@
1
- import { HarmonyApiClient } from "@gethmy/mcp/src/api-client.js";
2
- import type { AgentConfig, RealtimeCredentials } from "./types.js";
3
- export interface DaemonConfig {
4
- apiKey: string;
5
- apiUrl: string;
6
- workspaceId: string;
7
- projectId: string;
8
- userEmail: string;
9
- agent: AgentConfig;
10
- }
11
- /**
12
- * Load daemon configuration from ~/.harmony-mcp/config.json,
13
- * merging default agent settings with any user overrides.
14
- */
15
- export declare function loadDaemonConfig(): DaemonConfig;
16
- /**
17
- * Fetch Supabase realtime credentials from the Harmony API.
18
- */
19
- export declare function fetchRealtimeCredentials(client: HarmonyApiClient): Promise<RealtimeCredentials>;
20
- /**
21
- * Create an API client from daemon config.
22
- */
23
- export declare function createApiClient(config: DaemonConfig): HarmonyApiClient;
package/dist/config.js DELETED
@@ -1,103 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import { readFileSync } from "node:fs";
3
- import { homedir } from "node:os";
4
- import { join } from "node:path";
5
- import { HarmonyApiClient } from "@gethmy/mcp/src/api-client.js";
6
- import { getActiveProjectId, getActiveWorkspaceId, getApiKey, getApiUrl, getUserEmail, } from "@gethmy/mcp/src/config.js";
7
- import { DEFAULT_AGENT_CONFIG } from "./types.js";
8
- /**
9
- * Resolve the git repo root so local config lookup always
10
- * finds .harmony-mcp.json regardless of cwd.
11
- */
12
- function getRepoRoot() {
13
- return execSync("git rev-parse --show-toplevel", {
14
- encoding: "utf-8",
15
- }).trim();
16
- }
17
- /**
18
- * Load daemon configuration from ~/.harmony-mcp/config.json,
19
- * merging default agent settings with any user overrides.
20
- */
21
- export function loadDaemonConfig() {
22
- const repoRoot = getRepoRoot();
23
- const apiKey = getApiKey();
24
- const apiUrl = getApiUrl();
25
- const workspaceId = getActiveWorkspaceId(repoRoot);
26
- const projectId = getActiveProjectId(repoRoot);
27
- const userEmail = getUserEmail();
28
- if (!workspaceId) {
29
- throw new Error("No active workspace configured. Run `npx @gethmy/mcp setup` first.");
30
- }
31
- if (!projectId) {
32
- throw new Error("No active project configured. Run `npx @gethmy/mcp setup` first.");
33
- }
34
- if (!userEmail) {
35
- throw new Error("No user email configured. Run `npx @gethmy/mcp setup` first.");
36
- }
37
- // Try to load agent-specific overrides from config file
38
- let agentOverrides = {};
39
- try {
40
- const configPath = join(homedir(), ".harmony-mcp", "config.json");
41
- const raw = readFileSync(configPath, "utf-8");
42
- const parsed = JSON.parse(raw);
43
- if (parsed.agent) {
44
- agentOverrides = parsed.agent;
45
- }
46
- }
47
- catch {
48
- // No overrides, use defaults
49
- }
50
- const agent = {
51
- ...DEFAULT_AGENT_CONFIG,
52
- ...agentOverrides,
53
- completion: {
54
- ...DEFAULT_AGENT_CONFIG.completion,
55
- ...(agentOverrides.completion ?? {}),
56
- },
57
- claude: {
58
- ...DEFAULT_AGENT_CONFIG.claude,
59
- ...(agentOverrides.claude ?? {}),
60
- },
61
- worktree: {
62
- ...DEFAULT_AGENT_CONFIG.worktree,
63
- ...(agentOverrides.worktree ?? {}),
64
- },
65
- verification: {
66
- ...DEFAULT_AGENT_CONFIG.verification,
67
- ...(agentOverrides.verification ?? {}),
68
- },
69
- review: {
70
- ...DEFAULT_AGENT_CONFIG.review,
71
- ...(agentOverrides.review ?? {}),
72
- },
73
- budget: {
74
- ...DEFAULT_AGENT_CONFIG.budget,
75
- ...(agentOverrides.budget ?? {}),
76
- },
77
- http: {
78
- ...DEFAULT_AGENT_CONFIG.http,
79
- ...(agentOverrides.http ?? {}),
80
- },
81
- timing: {
82
- ...DEFAULT_AGENT_CONFIG.timing,
83
- ...(agentOverrides.timing ?? {}),
84
- },
85
- };
86
- return { apiKey, apiUrl, workspaceId, projectId, userEmail, agent };
87
- }
88
- /**
89
- * Fetch Supabase realtime credentials from the Harmony API.
90
- */
91
- export async function fetchRealtimeCredentials(client) {
92
- const result = (await client.request("GET", "/config/realtime"));
93
- if (!result.supabaseUrl || !result.supabaseAnonKey) {
94
- throw new Error("Invalid realtime credentials response from API");
95
- }
96
- return result;
97
- }
98
- /**
99
- * Create an API client from daemon config.
100
- */
101
- export function createApiClient(config) {
102
- return new HarmonyApiClient({ apiKey: config.apiKey, apiUrl: config.apiUrl });
103
- }
@@ -1,84 +0,0 @@
1
- import type { HarmonyApiClient } from "@gethmy/mcp/src/api-client.js";
2
- import type { Card } from "@harmony/shared";
3
- import type { CostUpdate } from "./stream-parser.js";
4
- import type { EpisodeMeta, EpisodeOutcome } from "./types.js";
5
- import type { VerificationResult } from "./verification.js";
6
- interface ImplementEpisodeInput {
7
- kind: "implement";
8
- card: Card;
9
- workspaceId: string;
10
- outcome: EpisodeOutcome;
11
- approachSummary: string;
12
- result: VerificationResult;
13
- cost: CostUpdate | null;
14
- filesEdited: number;
15
- errorMessage?: string;
16
- agentSessionId?: string | null;
17
- }
18
- interface ReviewEpisodeInput {
19
- kind: "review";
20
- card: Card;
21
- workspaceId: string;
22
- verdict: "approved" | "rejected";
23
- summary: string;
24
- cost: CostUpdate | null;
25
- agentSessionId?: string | null;
26
- reviewSessionId?: string | null;
27
- originalEpisodeId?: string | null;
28
- }
29
- export type EpisodeInput = ImplementEpisodeInput | ReviewEpisodeInput;
30
- /**
31
- * Rule-derived quality score (0..1) for an implement run. Failures default to 0.
32
- * Plan §"Quality score": +0.4 if build passed, +0.2 if lint passed, +0.2 if no
33
- * error thrown, +0.2 if run completed cleanly.
34
- */
35
- export declare function computeQualityScore(result: VerificationResult, opts: {
36
- errorThrown: boolean;
37
- runCompletedCleanly: boolean;
38
- }): number;
39
- /**
40
- * Trim a free-form summary down to the documented 400-char cap. v1 uses a
41
- * last-turn trim rather than an LLM rewrite (plan §"Write hook"). Empty or
42
- * whitespace-only input collapses to a marker so the episode still surfaces
43
- * as a recallable hit (rather than an empty bullet) in future prompts.
44
- */
45
- export declare function trimApproachSummary(text: string): string;
46
- /**
47
- * Build the entity payload for one episode. Pure — returned object can be
48
- * snapshotted in tests without hitting the network.
49
- */
50
- export declare function buildEpisodePayload(input: EpisodeInput, projectId: string): {
51
- workspace_id: string;
52
- project_id?: string;
53
- type: string;
54
- memory_tier: string;
55
- scope: string;
56
- title: string;
57
- content: string;
58
- metadata: EpisodeMeta;
59
- importance: number;
60
- confidence: number;
61
- tags: string[];
62
- agent_identifier: string;
63
- };
64
- /**
65
- * Write one episode entity. Best-effort: any failure is logged and swallowed
66
- * so the calling pipeline can complete (plan D8: episode writes never block
67
- * run completion).
68
- *
69
- * Returns the entity id on success, or null on swallowed failure.
70
- */
71
- export declare function writeEpisode(client: HarmonyApiClient, input: EpisodeInput): Promise<string | null>;
72
- /**
73
- * Find the most recent implement episode for a given card so the review
74
- * pipeline can back-fill its verdict. Returns null when none exists or the
75
- * lookup throws — back-fill is best-effort.
76
- */
77
- export declare function findLatestImplementEpisode(client: HarmonyApiClient, workspaceId: string, projectId: string, cardShortId: number): Promise<string | null>;
78
- /**
79
- * Apply the review verdict to an earlier implement episode (plan §"Read hook"
80
- * back-fill block). Approved nudges the original episode's confidence up;
81
- * rejected tombstones it via superseded_by.
82
- */
83
- export declare function backfillReviewVerdict(client: HarmonyApiClient, originalEpisodeId: string, verdict: "approved" | "rejected", reviewEpisodeId: string | null): Promise<void>;
84
- export {};