@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
package/dist/log.js DELETED
@@ -1,100 +0,0 @@
1
- /**
2
- * Structured logging for the agent daemon.
3
- *
4
- * When stderr is a TTY, output is ANSI-coloured single-line text — the
5
- * readable default for humans running the daemon interactively. When
6
- * stderr is piped/redirected (files, logshippers, systemd), output
7
- * falls back to one JSON object per line — trivially `jq`-able,
8
- * indexable, and machine-readable.
9
- *
10
- * Overrides: `--pretty` / HARMONY_AGENT_PRETTY=1 force pretty;
11
- * `--json` / HARMONY_AGENT_JSON=1 force JSON. Explicit flags beat
12
- * TTY detection either way.
13
- *
14
- * Stdout is left clean so consumers can pipe structured data if they
15
- * want to (future use).
16
- */
17
- const COLORS = {
18
- reset: "\x1b[0m",
19
- dim: "\x1b[2m",
20
- red: "\x1b[31m",
21
- green: "\x1b[32m",
22
- yellow: "\x1b[33m",
23
- blue: "\x1b[34m",
24
- cyan: "\x1b[36m",
25
- };
26
- const LEVEL_COLOR = {
27
- debug: COLORS.dim,
28
- info: COLORS.green,
29
- warn: COLORS.yellow,
30
- error: COLORS.red,
31
- };
32
- function pretty() {
33
- if (process.env.HARMONY_AGENT_JSON === "1" ||
34
- process.argv.includes("--json")) {
35
- return false;
36
- }
37
- if (process.env.HARMONY_AGENT_PRETTY === "1")
38
- return true;
39
- if (process.argv.includes("--pretty"))
40
- return true;
41
- return Boolean(process.stderr.isTTY);
42
- }
43
- function shortTime(iso) {
44
- return iso.slice(11, 23);
45
- }
46
- function emit(rec) {
47
- if (rec.level === "debug" && !process.env.DEBUG)
48
- return;
49
- if (pretty()) {
50
- const color = LEVEL_COLOR[rec.level];
51
- const label = rec.level.toUpperCase().padEnd(5, " ");
52
- const ctx = [];
53
- if (rec.event)
54
- ctx.push(`event=${rec.event}`);
55
- if (rec.runId)
56
- ctx.push(`run=${rec.runId}`);
57
- if (rec.cardId)
58
- ctx.push(`card=${rec.cardId}`);
59
- const tail = ctx.length
60
- ? ` ${COLORS.dim}(${ctx.join(" ")})${COLORS.reset}`
61
- : "";
62
- process.stderr.write(`${COLORS.dim}${shortTime(rec.ts)}${COLORS.reset} ${color}${label}${COLORS.reset} ${COLORS.cyan}[${rec.tag}]${COLORS.reset} ${rec.msg}${tail}\n`);
63
- return;
64
- }
65
- process.stderr.write(`${JSON.stringify(rec)}\n`);
66
- }
67
- function record(level, tag, msg, ctx) {
68
- const rec = {
69
- ts: new Date().toISOString(),
70
- level,
71
- tag,
72
- msg,
73
- ...(ctx ?? {}),
74
- };
75
- emit(rec);
76
- }
77
- export const log = {
78
- info(tag, msg, ctx) {
79
- record("info", tag, msg, ctx);
80
- },
81
- warn(tag, msg, ctx) {
82
- record("warn", tag, msg, ctx);
83
- },
84
- error(tag, msg, ctx) {
85
- record("error", tag, msg, ctx);
86
- },
87
- debug(tag, msg, ctx) {
88
- record("debug", tag, msg, ctx);
89
- },
90
- /**
91
- * Emit a named event. Semantically the same as `info` but signals to
92
- * downstream tooling that this is a structured event worth indexing.
93
- */
94
- event(tag, event, ctx) {
95
- record("info", tag, event, { ...ctx, event });
96
- },
97
- };
98
- export function isPretty() {
99
- return pretty();
100
- }
@@ -1,23 +0,0 @@
1
- import type { HarmonyApiClient } from "@gethmy/mcp/src/api-client.js";
2
- import type { AgentConfig } from "./types.js";
3
- /**
4
- * Polls "Ready to Merge" cards and detects when their PRs get merged,
5
- * then moves the card to Done and swaps labels.
6
- */
7
- export declare class MergeMonitor {
8
- private client;
9
- private projectId;
10
- private config;
11
- private intervalMs;
12
- private timer;
13
- private running;
14
- private readonly provider;
15
- private readonly cwd;
16
- constructor(client: HarmonyApiClient, projectId: string, config: AgentConfig, intervalMs?: number);
17
- start(): void;
18
- stop(): void;
19
- /** Schedule the next tick after `delayMs`, avoiding overlapping ticks. */
20
- private scheduleNext;
21
- private tick;
22
- private completeMergedCard;
23
- }
@@ -1,169 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { promisify } from "node:util";
3
- import { addLabelByName, buildLabelMap, hasLabel, moveCardToColumn, resolveCardLabels, } from "./board-helpers.js";
4
- import { checkPrMergeStatus, detectGitProvider, extractPrUrl, } from "./git-pr.js";
5
- import { log } from "./log.js";
6
- import { extractBranchFromDescription } from "./review-worktree.js";
7
- const TAG = "merge-monitor";
8
- const execFileAsync = promisify(execFile);
9
- /**
10
- * Polls "Ready to Merge" cards and detects when their PRs get merged,
11
- * then moves the card to Done and swaps labels.
12
- */
13
- export class MergeMonitor {
14
- client;
15
- projectId;
16
- config;
17
- intervalMs;
18
- timer = null;
19
- running = false;
20
- provider;
21
- cwd;
22
- constructor(client, projectId, config, intervalMs = 60_000) {
23
- this.client = client;
24
- this.projectId = projectId;
25
- this.config = config;
26
- this.intervalMs = intervalMs;
27
- this.provider = detectGitProvider();
28
- this.cwd = process.cwd();
29
- }
30
- start() {
31
- this.running = true;
32
- void this.scheduleNext(0);
33
- }
34
- stop() {
35
- this.running = false;
36
- if (this.timer) {
37
- clearTimeout(this.timer);
38
- this.timer = null;
39
- }
40
- log.info(TAG, "Merge monitor stopped");
41
- }
42
- /** Schedule the next tick after `delayMs`, avoiding overlapping ticks. */
43
- async scheduleNext(delayMs) {
44
- await new Promise((resolve) => {
45
- this.timer = setTimeout(() => resolve(), delayMs);
46
- });
47
- if (!this.running)
48
- return; // stopped while waiting
49
- await this.tick();
50
- if (this.running) {
51
- void this.scheduleNext(this.intervalMs);
52
- }
53
- }
54
- async tick() {
55
- try {
56
- const board = await this.client.getBoard(this.projectId, {
57
- labelName: this.config.review.approvedLabel,
58
- });
59
- const cards = (board.cards ?? []);
60
- const columns = (board.columns ?? []);
61
- // Build label lookup (id → Label) to resolve card.labelIds
62
- const labelMap = buildLabelMap((board.labels ?? []));
63
- // Find review column IDs
64
- const reviewColumnIds = new Set(columns
65
- .filter((c) => this.config.review.pickupColumns.some((name) => name.toLowerCase() === c.name.toLowerCase()))
66
- .map((c) => c.id));
67
- // Cards are already pre-filtered by the approved label server-side via
68
- // the `labelName` param. The column + label checks below are kept as
69
- // defense-in-depth against API changes or stale cache.
70
- const approvedLabel = this.config.review.approvedLabel;
71
- const candidatesWithLabels = [];
72
- for (const c of cards) {
73
- if (c.archived_at || !reviewColumnIds.has(c.column_id))
74
- continue;
75
- const labels = resolveCardLabels(c, labelMap);
76
- if (hasLabel(labels, approvedLabel)) {
77
- candidatesWithLabels.push({ card: c, labels });
78
- }
79
- }
80
- if (candidatesWithLabels.length === 0) {
81
- log.debug(TAG, "No Ready to Merge cards found");
82
- return;
83
- }
84
- // Process max 5 per tick to respect rate limits
85
- const batch = candidatesWithLabels.slice(0, 5);
86
- log.debug(TAG, `Checking ${batch.length} Ready to Merge card(s)`);
87
- // Check PR states concurrently (async, non-blocking)
88
- const results = await Promise.allSettled(batch.map(async ({ card, labels }) => {
89
- const prUrl = extractPrUrl(card.description ?? null);
90
- if (!prUrl) {
91
- log.debug(TAG, `#${card.short_id} has no PR URL — skipping`);
92
- return;
93
- }
94
- const state = await checkPrMergeStatus(prUrl, this.cwd, this.provider);
95
- if (state === "merged") {
96
- log.info(TAG, `#${card.short_id} PR merged — completing`);
97
- await this.completeMergedCard(card, labels);
98
- }
99
- else {
100
- log.debug(TAG, `#${card.short_id} PR state: ${state}`);
101
- }
102
- }));
103
- for (const r of results) {
104
- if (r.status === "rejected") {
105
- log.warn(TAG, `Card processing failed: ${r.reason}`);
106
- }
107
- }
108
- }
109
- catch (err) {
110
- log.error(TAG, `Tick failed: ${err instanceof Error ? err.message : err}`);
111
- }
112
- }
113
- async completeMergedCard(card, resolvedLabels) {
114
- // 1. Move to Done — bail if this fails since subsequent steps assume card is in Done
115
- try {
116
- await moveCardToColumn(this.client, card, this.config.review.moveToColumn);
117
- }
118
- catch (err) {
119
- log.error(TAG, `Failed to move #${card.short_id} to Done: ${err instanceof Error ? err.message : err}`);
120
- return;
121
- }
122
- // 2. Add "Merged" label
123
- await addLabelByName(this.client, card, this.config.review.mergedLabel, this.config.review.mergedLabelColor);
124
- // 3. Remove "Ready to Merge" label
125
- const approvedLabelName = this.config.review.approvedLabel.toLowerCase();
126
- const approvedLabelObj = resolvedLabels.find((l) => l.name.toLowerCase() === approvedLabelName);
127
- if (approvedLabelObj) {
128
- try {
129
- await this.client.removeLabelFromCard(card.id, approvedLabelObj.id);
130
- log.info(TAG, `Removed "${this.config.review.approvedLabel}" from #${card.short_id}`);
131
- }
132
- catch (err) {
133
- log.warn(TAG, `Failed to remove label: ${err instanceof Error ? err.message : err}`);
134
- }
135
- }
136
- // 4. Append merge timestamp to description (idempotent).
137
- // Do NOT force done=true here — the column's `mark_cards_done` flag is the
138
- // user's source of truth, and the backend `moveCard` in step 1 already
139
- // applied it. Overriding here would ignore the user's column setting (#129).
140
- const existing = card.description || "";
141
- const alreadyStamped = existing.includes("Merged at");
142
- if (!alreadyStamped) {
143
- try {
144
- const timestamp = new Date().toISOString();
145
- const separator = existing ? "\n" : "";
146
- await this.client.updateCard(card.id, {
147
- description: `${existing}${separator}Merged at ${timestamp}`,
148
- });
149
- }
150
- catch (err) {
151
- log.warn(TAG, `Failed to update card: ${err instanceof Error ? err.message : err}`);
152
- }
153
- }
154
- // 5. Best-effort: clean up local branch (uses shared extraction with validation)
155
- const branchName = extractBranchFromDescription(card.description);
156
- if (branchName) {
157
- try {
158
- await execFileAsync("git", ["branch", "-D", "--", branchName], {
159
- cwd: this.cwd,
160
- });
161
- log.info(TAG, `Deleted local branch ${branchName}`);
162
- }
163
- catch {
164
- // best-effort
165
- }
166
- }
167
- log.info(TAG, `#${card.short_id} completed (merged)`);
168
- }
169
- }
package/dist/pm.d.ts DELETED
@@ -1,14 +0,0 @@
1
- export type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
2
- /**
3
- * Detect the package manager based on lockfiles in the repo root.
4
- */
5
- export declare function detectPackageManager(): PackageManager;
6
- /**
7
- * Return the install command string for the detected package manager.
8
- */
9
- export declare function installCommand(): string;
10
- /**
11
- * Return [cmd, args[]] suitable for spawn() or execFileSync() to run a package script.
12
- * Adds `--` separator for npm and pnpm when extra args are present.
13
- */
14
- export declare function spawnRunArgs(script: string, ...extra: string[]): [string, string[]];
package/dist/pm.js DELETED
@@ -1,63 +0,0 @@
1
- import { execFileSync } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { log } from "./log.js";
4
- const TAG = "pm";
5
- let cached = null;
6
- /**
7
- * Detect the package manager based on lockfiles in the repo root.
8
- */
9
- export function detectPackageManager() {
10
- if (cached)
11
- return cached;
12
- let repoRoot;
13
- try {
14
- repoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
15
- encoding: "utf-8",
16
- }).trim();
17
- }
18
- catch {
19
- repoRoot = process.cwd();
20
- }
21
- if (existsSync(`${repoRoot}/bun.lock`) ||
22
- existsSync(`${repoRoot}/bun.lockb`)) {
23
- cached = "bun";
24
- }
25
- else if (existsSync(`${repoRoot}/pnpm-lock.yaml`)) {
26
- cached = "pnpm";
27
- }
28
- else if (existsSync(`${repoRoot}/yarn.lock`)) {
29
- cached = "yarn";
30
- }
31
- else {
32
- cached = "npm";
33
- }
34
- log.info(TAG, `Detected package manager: ${cached}`);
35
- return cached;
36
- }
37
- /**
38
- * Return the install command string for the detected package manager.
39
- */
40
- export function installCommand() {
41
- const pm = detectPackageManager();
42
- switch (pm) {
43
- case "bun":
44
- return "bun install --frozen-lockfile";
45
- case "pnpm":
46
- return "pnpm install --frozen-lockfile";
47
- case "yarn":
48
- return "yarn install --frozen-lockfile";
49
- case "npm":
50
- return "npm ci";
51
- }
52
- }
53
- /**
54
- * Return [cmd, args[]] suitable for spawn() or execFileSync() to run a package script.
55
- * Adds `--` separator for npm and pnpm when extra args are present.
56
- */
57
- export function spawnRunArgs(script, ...extra) {
58
- const pm = detectPackageManager();
59
- if (extra.length > 0 && (pm === "npm" || pm === "pnpm")) {
60
- return [pm, ["run", script, "--", ...extra]];
61
- }
62
- return [pm, ["run", script, ...extra]];
63
- }
package/dist/pool.d.ts DELETED
@@ -1,71 +0,0 @@
1
- import type { HarmonyApiClient } from "@gethmy/mcp/src/api-client.js";
2
- import type { Card, Column, Label, Subtask } from "@harmony/shared";
3
- import { PriorityQueue } from "./queue.js";
4
- import type { StateStore } from "./state-store.js";
5
- import { type AgentConfig, type WorkMode } from "./types.js";
6
- export declare class Pool {
7
- private client;
8
- private stateStore;
9
- private implWorkers;
10
- private reviewWorkers;
11
- private implQueue;
12
- private reviewQueue;
13
- private budget;
14
- constructor(config: AgentConfig, client: HarmonyApiClient, userEmail: string, workspaceId: string, projectId: string, stateStore: StateStore);
15
- /**
16
- * Enqueue a card for processing with the given mode.
17
- *
18
- * Returns async so callers can await the give-up comment / waiting
19
- * signal on skip. Budget checks happen here so the reconciler, realtime
20
- * watcher, and manual API calls all go through the same gate.
21
- */
22
- enqueue(card: Card, column: Column, labels: Label[], subtasks: Subtask[], mode?: WorkMode): Promise<void>;
23
- /**
24
- * Best-effort waiting-state emit. Failures are swallowed because we don't
25
- * want a board-API hiccup to drop the queue/budget event in pool.ts.
26
- */
27
- private emitWaiting;
28
- /**
29
- * Remove a card from any queue or cancel an active worker.
30
- */
31
- removeCard(cardId: string): Promise<void>;
32
- /**
33
- * Check if a card is currently being worked on by any worker.
34
- */
35
- isCardActive(cardId: string): boolean;
36
- /**
37
- * Check if a card is known to the pool (queued or active).
38
- */
39
- isCardKnown(cardId: string): boolean;
40
- /**
41
- * Get all card IDs that are either queued or active.
42
- */
43
- knownCardIds(): Set<string>;
44
- /**
45
- * Handle an agent command (pause/resume/stop) for a specific card.
46
- */
47
- handleAgentCommand(cardId: string, command: "pause" | "resume" | "stop"): Promise<void>;
48
- /**
49
- * Point-in-time snapshot for the HTTP /status endpoint. Safe to call
50
- * from anywhere — reads in-memory state only.
51
- */
52
- snapshotWorkers(): Array<{
53
- id: number;
54
- pipeline: "implement" | "review";
55
- state: string;
56
- cardId: string | null;
57
- cardShortId: number | null;
58
- startedAt: number | null;
59
- branchName: string | null;
60
- }>;
61
- snapshotQueues(): {
62
- impl: ReturnType<PriorityQueue["snapshot"]>;
63
- review: ReturnType<PriorityQueue["snapshot"]>;
64
- };
65
- /**
66
- * Gracefully shutdown all workers.
67
- */
68
- shutdown(): Promise<void>;
69
- private cardDataCache;
70
- private tryDispatchFor;
71
- }