@gethmy/agent 1.0.3 → 1.0.4
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/index.js +1 -1
- package/dist/pool.d.ts +1 -1
- package/dist/pool.js +2 -2
- package/dist/prompt.d.ts +9 -2
- package/dist/prompt.js +34 -2
- package/dist/review-worker.d.ts +1 -1
- package/dist/review-worker.js +79 -36
- package/dist/worker.d.ts +3 -1
- package/dist/worker.js +6 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -87,7 +87,7 @@ async function main() {
|
|
|
87
87
|
const realtimeCreds = await fetchRealtimeCredentials(client);
|
|
88
88
|
log.info(TAG, "Realtime credentials fetched");
|
|
89
89
|
// Create pool
|
|
90
|
-
const pool = new Pool(config.agent, client, config.userEmail);
|
|
90
|
+
const pool = new Pool(config.agent, client, config.userEmail, config.workspaceId, config.projectId);
|
|
91
91
|
// Create reconciler
|
|
92
92
|
const reviewColumns = config.agent.review.enabled
|
|
93
93
|
? config.agent.review.pickupColumns
|
package/dist/pool.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare class Pool {
|
|
|
6
6
|
private reviewWorkers;
|
|
7
7
|
private implQueue;
|
|
8
8
|
private reviewQueue;
|
|
9
|
-
constructor(config: AgentConfig, client: HarmonyApiClient, userEmail: string);
|
|
9
|
+
constructor(config: AgentConfig, client: HarmonyApiClient, userEmail: string, workspaceId: string, projectId: string);
|
|
10
10
|
/**
|
|
11
11
|
* Enqueue a card for processing with the given mode.
|
|
12
12
|
*/
|
package/dist/pool.js
CHANGED
|
@@ -8,14 +8,14 @@ export class Pool {
|
|
|
8
8
|
reviewWorkers = [];
|
|
9
9
|
implQueue;
|
|
10
10
|
reviewQueue;
|
|
11
|
-
constructor(config, client, userEmail) {
|
|
11
|
+
constructor(config, client, userEmail, workspaceId, projectId) {
|
|
12
12
|
this.implQueue = new PriorityQueue(config);
|
|
13
13
|
this.reviewQueue = new PriorityQueue(config);
|
|
14
14
|
// Create implementation workers
|
|
15
15
|
for (let i = 0; i < config.poolSize; i++) {
|
|
16
16
|
this.implWorkers.push(new Worker(i, config, client, userEmail, () => {
|
|
17
17
|
this.tryDispatchFor(this.implWorkers, this.implQueue, "impl");
|
|
18
|
-
}));
|
|
18
|
+
}, workspaceId, projectId));
|
|
19
19
|
}
|
|
20
20
|
// Create review worker(s) — 1 review worker per pool
|
|
21
21
|
if (config.review.enabled) {
|
package/dist/prompt.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import type { HarmonyApiClient } from "@gethmy/mcp/src/api-client.js";
|
|
1
2
|
import type { EnrichedCard } from "./types.js";
|
|
2
3
|
/**
|
|
3
|
-
* Build the prompt
|
|
4
|
+
* Build the prompt for a card using the shared prompt generation pipeline.
|
|
5
|
+
*
|
|
6
|
+
* This calls `client.generateCardPrompt()` which assembles relevant memories
|
|
7
|
+
* from the knowledge graph, applies role-based framing, and produces a
|
|
8
|
+
* context-rich prompt — the same pipeline used by the MCP server.
|
|
9
|
+
*
|
|
10
|
+
* Falls back to a minimal local prompt if the API call fails.
|
|
4
11
|
*/
|
|
5
|
-
export declare function buildPrompt(enriched: EnrichedCard, branchName: string, worktreePath: string): string
|
|
12
|
+
export declare function buildPrompt(enriched: EnrichedCard, branchName: string, worktreePath: string, client: HarmonyApiClient, workspaceId: string, projectId?: string): Promise<string>;
|
package/dist/prompt.js
CHANGED
|
@@ -1,7 +1,39 @@
|
|
|
1
|
+
import { log } from "./log.js";
|
|
2
|
+
const TAG = "prompt";
|
|
1
3
|
/**
|
|
2
|
-
* Build the prompt
|
|
4
|
+
* Build the prompt for a card using the shared prompt generation pipeline.
|
|
5
|
+
*
|
|
6
|
+
* This calls `client.generateCardPrompt()` which assembles relevant memories
|
|
7
|
+
* from the knowledge graph, applies role-based framing, and produces a
|
|
8
|
+
* context-rich prompt — the same pipeline used by the MCP server.
|
|
9
|
+
*
|
|
10
|
+
* Falls back to a minimal local prompt if the API call fails.
|
|
3
11
|
*/
|
|
4
|
-
export function buildPrompt(enriched, branchName, worktreePath) {
|
|
12
|
+
export async function buildPrompt(enriched, branchName, worktreePath, client, workspaceId, projectId) {
|
|
13
|
+
const { card } = enriched;
|
|
14
|
+
try {
|
|
15
|
+
const result = await client.generateCardPrompt({
|
|
16
|
+
cardId: card.id,
|
|
17
|
+
workspaceId,
|
|
18
|
+
projectId,
|
|
19
|
+
variant: "execute",
|
|
20
|
+
customConstraints: `You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
|
|
21
|
+
Do NOT push to main. All your work stays on \`${branchName}\`.
|
|
22
|
+
When finished, call harmony_end_agent_session with status="completed".`,
|
|
23
|
+
});
|
|
24
|
+
log.info(TAG, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
|
|
25
|
+
return result.prompt;
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
29
|
+
log.warn(TAG, `Failed to generate prompt via API, using fallback: ${msg}`);
|
|
30
|
+
return buildFallbackPrompt(enriched, branchName, worktreePath);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Minimal fallback prompt when the API-based generation is unavailable.
|
|
35
|
+
*/
|
|
36
|
+
function buildFallbackPrompt(enriched, branchName, worktreePath) {
|
|
5
37
|
const { card, column, labels, subtasks } = enriched;
|
|
6
38
|
const labelStr = labels.length > 0 ? labels.map((l) => l.name).join(", ") : "none";
|
|
7
39
|
const subtaskStr = subtasks.length > 0
|
package/dist/review-worker.d.ts
CHANGED
|
@@ -38,9 +38,9 @@ export declare class ReviewWorker {
|
|
|
38
38
|
* Cancel the current review. Sends escalating signals to both processes.
|
|
39
39
|
*/
|
|
40
40
|
cancel(): Promise<void>;
|
|
41
|
-
private checkDevServer;
|
|
42
41
|
private spawnClaude;
|
|
43
42
|
private waitForExit;
|
|
44
43
|
private killDevServer;
|
|
44
|
+
private resolveLocalChanges;
|
|
45
45
|
private cleanup;
|
|
46
46
|
}
|
package/dist/review-worker.js
CHANGED
|
@@ -63,25 +63,47 @@ export class ReviewWorker {
|
|
|
63
63
|
// Extract branch name from card description
|
|
64
64
|
this.branchName = extractBranchFromDescription(card.description);
|
|
65
65
|
const localMode = !this.branchName;
|
|
66
|
+
let localDiff = null;
|
|
66
67
|
if (localMode) {
|
|
67
68
|
// LOCAL FALLBACK: no branch → review local working tree
|
|
68
69
|
log.info(this.tag, `No branch found for #${card.short_id}, attempting local review`);
|
|
69
70
|
// Use repo root as working directory
|
|
70
71
|
this.worktreePath = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
71
|
-
cwd: this.config.worktree.basePath,
|
|
72
72
|
encoding: "utf-8",
|
|
73
73
|
timeout: 5_000,
|
|
74
74
|
}).trim();
|
|
75
|
-
//
|
|
76
|
-
const
|
|
77
|
-
if (!
|
|
78
|
-
|
|
75
|
+
// Try to find reviewable changes
|
|
76
|
+
const resolved = this.resolveLocalChanges(this.worktreePath, card.short_id);
|
|
77
|
+
if (!resolved) {
|
|
78
|
+
// No changes found — post helpful note on the card and bail
|
|
79
|
+
log.info(this.tag, `No local changes found for #${card.short_id} — posting note and moving to "${this.config.review.failColumn}"`);
|
|
80
|
+
// Strip any previous agent note before appending a new one
|
|
81
|
+
const AGENT_NOTE_SEPARATOR = "\n\n---\n**Agent Note: No branch found**";
|
|
82
|
+
const existingDesc = card.description ?? "";
|
|
83
|
+
const cleanDesc = existingDesc.includes(AGENT_NOTE_SEPARATOR)
|
|
84
|
+
? existingDesc.slice(0, existingDesc.indexOf(AGENT_NOTE_SEPARATOR))
|
|
85
|
+
: existingDesc;
|
|
86
|
+
const note = [
|
|
87
|
+
cleanDesc,
|
|
88
|
+
"",
|
|
89
|
+
"---",
|
|
90
|
+
"**Agent Note: No branch found**",
|
|
91
|
+
"The review agent could not find a branch reference (`Branch: ...`) in this card's description,",
|
|
92
|
+
`and no local uncommitted changes or recent commits referencing #${card.short_id} were found.`,
|
|
93
|
+
"",
|
|
94
|
+
"Please either:",
|
|
95
|
+
"- Push the implementation branch and add `Branch: your-branch-name` to the description",
|
|
96
|
+
'- Or move this card back to "To Do" for the implement worker to pick up',
|
|
97
|
+
].join("\n");
|
|
79
98
|
await Promise.all([
|
|
99
|
+
this.client.updateCard(card.id, { description: note }),
|
|
80
100
|
addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR),
|
|
81
101
|
moveCardToColumn(this.client, card, this.config.review.failColumn),
|
|
82
102
|
]);
|
|
83
103
|
return;
|
|
84
104
|
}
|
|
105
|
+
log.info(this.tag, `Found local changes via ${resolved.source} for #${card.short_id}`);
|
|
106
|
+
localDiff = resolved.diff;
|
|
85
107
|
}
|
|
86
108
|
if (this.branchName) {
|
|
87
109
|
log.info(this.tag, `Review branch: ${this.branchName}`);
|
|
@@ -125,26 +147,8 @@ export class ReviewWorker {
|
|
|
125
147
|
let diff = "";
|
|
126
148
|
try {
|
|
127
149
|
if (localMode) {
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
cwd,
|
|
131
|
-
encoding: "utf-8",
|
|
132
|
-
timeout: 30_000,
|
|
133
|
-
});
|
|
134
|
-
const staged = execFileSync("git", ["diff", "--cached"], {
|
|
135
|
-
cwd,
|
|
136
|
-
encoding: "utf-8",
|
|
137
|
-
timeout: 30_000,
|
|
138
|
-
});
|
|
139
|
-
diff = [staged, uncommitted].filter(Boolean).join("\n");
|
|
140
|
-
if (!diff) {
|
|
141
|
-
// No uncommitted changes — get last commit's diff
|
|
142
|
-
diff = execFileSync("git", ["diff", "HEAD~1..HEAD"], {
|
|
143
|
-
cwd,
|
|
144
|
-
encoding: "utf-8",
|
|
145
|
-
timeout: 30_000,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
150
|
+
// Use pre-computed diff from resolveLocalChanges
|
|
151
|
+
diff = localDiff ?? "";
|
|
148
152
|
}
|
|
149
153
|
else {
|
|
150
154
|
diff = execFileSync("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30_000 });
|
|
@@ -324,17 +328,6 @@ export class ReviewWorker {
|
|
|
324
328
|
}
|
|
325
329
|
}
|
|
326
330
|
}
|
|
327
|
-
async checkDevServer(port) {
|
|
328
|
-
try {
|
|
329
|
-
const resp = await fetch(`http://localhost:${port}`, {
|
|
330
|
-
signal: AbortSignal.timeout(3_000),
|
|
331
|
-
});
|
|
332
|
-
return resp.ok;
|
|
333
|
-
}
|
|
334
|
-
catch {
|
|
335
|
-
return false;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
331
|
spawnClaude(prompt, systemPrompt, tracker) {
|
|
339
332
|
return new Promise((resolve, reject) => {
|
|
340
333
|
const args = [
|
|
@@ -413,6 +406,56 @@ export class ReviewWorker {
|
|
|
413
406
|
log.debug(this.tag, "Killed dev server");
|
|
414
407
|
}
|
|
415
408
|
}
|
|
409
|
+
resolveLocalChanges(repoRoot, shortId) {
|
|
410
|
+
// 1. Check uncommitted changes (staged + unstaged combined)
|
|
411
|
+
try {
|
|
412
|
+
const localChanges = execFileSync("git", ["diff", "HEAD"], {
|
|
413
|
+
cwd: repoRoot,
|
|
414
|
+
encoding: "utf-8",
|
|
415
|
+
timeout: 5_000,
|
|
416
|
+
});
|
|
417
|
+
if (localChanges) {
|
|
418
|
+
return { diff: localChanges, source: "uncommitted changes" };
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
log.warn(this.tag, "Failed to check uncommitted changes");
|
|
423
|
+
}
|
|
424
|
+
// 2. Search recent commits for card reference
|
|
425
|
+
try {
|
|
426
|
+
const matchingCommits = execFileSync("git", ["log", "--format=%H", "-20", `--grep=#${shortId}`], { cwd: repoRoot, encoding: "utf-8", timeout: 10_000 }).trim();
|
|
427
|
+
if (matchingCommits) {
|
|
428
|
+
const hashes = matchingCommits
|
|
429
|
+
.split("\n")
|
|
430
|
+
.filter((h) => /^[0-9a-f]{4,40}$/i.test(h));
|
|
431
|
+
if (hashes.length === 0)
|
|
432
|
+
return null;
|
|
433
|
+
log.info(this.tag, `Found ${hashes.length} commit(s) referencing #${shortId}`);
|
|
434
|
+
// Generate a combined diff from all matching commits
|
|
435
|
+
const diffs = [];
|
|
436
|
+
for (const hash of hashes) {
|
|
437
|
+
try {
|
|
438
|
+
const commitDiff = execFileSync("git", ["diff", `${hash}~1..${hash}`], { cwd: repoRoot, encoding: "utf-8", timeout: 30_000 });
|
|
439
|
+
if (commitDiff)
|
|
440
|
+
diffs.push(commitDiff);
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
log.warn(this.tag, `Failed to diff commit ${hash}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (diffs.length > 0) {
|
|
447
|
+
return {
|
|
448
|
+
diff: diffs.join("\n"),
|
|
449
|
+
source: `${diffs.length} commit(s) matching #${shortId}`,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
log.warn(this.tag, "Failed to search recent commits");
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
416
459
|
cleanup() {
|
|
417
460
|
if (this.timeoutTimer) {
|
|
418
461
|
clearTimeout(this.timeoutTimer);
|
package/dist/worker.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export declare class Worker {
|
|
|
5
5
|
private config;
|
|
6
6
|
private client;
|
|
7
7
|
private onDone;
|
|
8
|
+
private workspaceId;
|
|
9
|
+
private projectId;
|
|
8
10
|
id: number;
|
|
9
11
|
state: WorkerState;
|
|
10
12
|
cardId: string | null;
|
|
@@ -16,7 +18,7 @@ export declare class Worker {
|
|
|
16
18
|
private progressTracker;
|
|
17
19
|
private lastSessionStats;
|
|
18
20
|
private aborted;
|
|
19
|
-
constructor(id: number, config: AgentConfig, client: HarmonyApiClient, _userEmail: string, onDone: (worker: Worker) => void);
|
|
21
|
+
constructor(id: number, config: AgentConfig, client: HarmonyApiClient, _userEmail: string, onDone: (worker: Worker) => void, workspaceId: string, projectId: string);
|
|
20
22
|
get tag(): string;
|
|
21
23
|
get isIdle(): boolean;
|
|
22
24
|
get isActive(): boolean;
|
package/dist/worker.js
CHANGED
|
@@ -14,6 +14,8 @@ export class Worker {
|
|
|
14
14
|
config;
|
|
15
15
|
client;
|
|
16
16
|
onDone;
|
|
17
|
+
workspaceId;
|
|
18
|
+
projectId;
|
|
17
19
|
id;
|
|
18
20
|
state = "idle";
|
|
19
21
|
cardId = null;
|
|
@@ -25,10 +27,12 @@ export class Worker {
|
|
|
25
27
|
progressTracker = null;
|
|
26
28
|
lastSessionStats;
|
|
27
29
|
aborted = false;
|
|
28
|
-
constructor(id, config, client, _userEmail, onDone) {
|
|
30
|
+
constructor(id, config, client, _userEmail, onDone, workspaceId, projectId) {
|
|
29
31
|
this.config = config;
|
|
30
32
|
this.client = client;
|
|
31
33
|
this.onDone = onDone;
|
|
34
|
+
this.workspaceId = workspaceId;
|
|
35
|
+
this.projectId = projectId;
|
|
32
36
|
this.id = id;
|
|
33
37
|
}
|
|
34
38
|
get tag() {
|
|
@@ -84,7 +88,7 @@ export class Worker {
|
|
|
84
88
|
subtasks,
|
|
85
89
|
mode: "implement",
|
|
86
90
|
};
|
|
87
|
-
const prompt = buildPrompt(enriched, this.branchName, this.worktreePath);
|
|
91
|
+
const prompt = await buildPrompt(enriched, this.branchName, this.worktreePath, this.client, this.workspaceId, this.projectId);
|
|
88
92
|
await this.client.updateAgentProgress(card.id, {
|
|
89
93
|
agentIdentifier: agentIdentifier(this.id),
|
|
90
94
|
agentName: AGENT_NAME,
|