@gethmy/mcp 3.8.0 → 3.9.0
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/README.md +1 -1
- package/dist/cli.js +530 -155
- package/dist/index.js +135 -95
- package/dist/lib/api-client.js +110 -14
- package/dist/lib/config.js +109 -13
- package/dist/lib/oauth-refresh.js +109 -13
- package/package.json +1 -1
- package/src/api-client.ts +9 -0
- package/src/config.ts +243 -12
- package/src/prompt-builder.ts +1 -1
- package/src/server.ts +43 -4
- package/src/skills.ts +6 -80
- package/src/tui/agent-instructions.ts +335 -0
- package/src/tui/setup.ts +144 -63
- package/src/tui/writer.ts +118 -2
package/dist/cli.js
CHANGED
|
@@ -18,8 +18,9 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/config.ts
|
|
21
|
+
import { execFileSync } from "node:child_process";
|
|
21
22
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
22
|
-
import { homedir } from "node:os";
|
|
23
|
+
import { homedir, tmpdir } from "node:os";
|
|
23
24
|
import { dirname, join, parse, resolve } from "node:path";
|
|
24
25
|
function noteLegacyConfigDir(path) {
|
|
25
26
|
if (warnedLegacyConfigDir)
|
|
@@ -36,6 +37,23 @@ function noteLegacyLocalPin(path) {
|
|
|
36
37
|
function noteLocalPinRename(from, to) {
|
|
37
38
|
console.error(`Harmony: wrote the repo pin to ${to} (the pre-#1082 ${from} is now ignored and can be deleted).`);
|
|
38
39
|
}
|
|
40
|
+
function noteUntrackedLocalPin(path) {
|
|
41
|
+
if (warnedUntrackedLocalPin)
|
|
42
|
+
return;
|
|
43
|
+
warnedUntrackedLocalPin = true;
|
|
44
|
+
console.error(`Harmony: ${path} is ignored by git, so it will not travel with a branch — ` + `a fresh clone, and every worktree the agent daemon cuts, will not have it. ` + `Commit it if you want it to describe this repo everywhere.`);
|
|
45
|
+
}
|
|
46
|
+
function isGitIgnored(path) {
|
|
47
|
+
try {
|
|
48
|
+
execFileSync("git", ["check-ignore", "--quiet", path], {
|
|
49
|
+
cwd: dirname(path),
|
|
50
|
+
stdio: "ignore"
|
|
51
|
+
});
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
39
57
|
function getHmyRootDir() {
|
|
40
58
|
return join(homedir(), CONFIG_DIR_NAME);
|
|
41
59
|
}
|
|
@@ -150,19 +168,96 @@ function saveLocalConfig(config, cwd) {
|
|
|
150
168
|
if (foundPath !== null && foundPath !== localConfigPath) {
|
|
151
169
|
noteLocalPinRename(foundPath, localConfigPath);
|
|
152
170
|
}
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
171
|
+
const existing = readRawLocalConfig(foundPath ?? localConfigPath);
|
|
172
|
+
const merged = { ...existing };
|
|
173
|
+
if ("workspaceId" in config) {
|
|
174
|
+
if (config.workspaceId)
|
|
175
|
+
merged.workspaceId = config.workspaceId;
|
|
176
|
+
else
|
|
177
|
+
delete merged.workspaceId;
|
|
178
|
+
}
|
|
179
|
+
if ("projectId" in config) {
|
|
180
|
+
if (config.projectId)
|
|
181
|
+
merged.projectId = config.projectId;
|
|
182
|
+
else
|
|
183
|
+
delete merged.projectId;
|
|
184
|
+
}
|
|
185
|
+
writeFileSync(localConfigPath, `${JSON.stringify(merged, null, localIndent(localConfigPath))}
|
|
186
|
+
`);
|
|
187
|
+
if (isGitIgnored(localConfigPath))
|
|
188
|
+
noteUntrackedLocalPin(localConfigPath);
|
|
164
189
|
return localConfigPath;
|
|
165
190
|
}
|
|
191
|
+
function readRawLocalConfig(path) {
|
|
192
|
+
let text;
|
|
193
|
+
try {
|
|
194
|
+
text = readFileSync(path, "utf-8");
|
|
195
|
+
} catch {
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
const parsed = JSON.parse(text);
|
|
200
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
201
|
+
return parsed;
|
|
202
|
+
}
|
|
203
|
+
} catch {}
|
|
204
|
+
noteUnparsableLocalPin(path, text);
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
function noteUnparsableLocalPin(path, contents) {
|
|
208
|
+
let backup = null;
|
|
209
|
+
try {
|
|
210
|
+
backup = join(tmpdir(), `hmy-pin-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json.bak`);
|
|
211
|
+
writeFileSync(backup, contents);
|
|
212
|
+
} catch {
|
|
213
|
+
backup = null;
|
|
214
|
+
}
|
|
215
|
+
console.error(`Harmony: ${path} could not be parsed as JSON, so the pin write REPLACED it. ` + (backup ? `The previous contents are in ${backup}.` : "The previous contents could not be backed up and are gone.") + ` If it carried a "commands" block, re-add it.`);
|
|
216
|
+
}
|
|
217
|
+
function localIndent(configPath) {
|
|
218
|
+
const root = dirname(configPath);
|
|
219
|
+
for (const name of ["biome.json", "biome.jsonc"]) {
|
|
220
|
+
const parsed = readJsonish(join(root, name));
|
|
221
|
+
if (!parsed || typeof parsed !== "object")
|
|
222
|
+
continue;
|
|
223
|
+
const formatter = parsed.formatter;
|
|
224
|
+
if (formatter?.indentStyle === "space") {
|
|
225
|
+
const width = formatter.indentWidth;
|
|
226
|
+
return typeof width === "number" && width > 0 ? width : 2;
|
|
227
|
+
}
|
|
228
|
+
return "\t";
|
|
229
|
+
}
|
|
230
|
+
const editorconfig = readTextSafely(join(root, ".editorconfig"));
|
|
231
|
+
if (editorconfig) {
|
|
232
|
+
if (/^\s*indent_style\s*=\s*tab\s*$/im.test(editorconfig))
|
|
233
|
+
return "\t";
|
|
234
|
+
const size = editorconfig.match(/^\s*indent_size\s*=\s*(\d+)\s*$/im);
|
|
235
|
+
if (size) {
|
|
236
|
+
const width = Number(size[1]);
|
|
237
|
+
if (width > 0)
|
|
238
|
+
return width;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return 2;
|
|
242
|
+
}
|
|
243
|
+
function readJsonish(path) {
|
|
244
|
+
const text = readTextSafely(path);
|
|
245
|
+
if (text === null)
|
|
246
|
+
return null;
|
|
247
|
+
try {
|
|
248
|
+
const stripped = text.replace(/^\s*\/\/.*$/gm, "").replace(/,(\s*[}\]])/g, "$1");
|
|
249
|
+
return JSON.parse(stripped);
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function readTextSafely(path) {
|
|
255
|
+
try {
|
|
256
|
+
return readFileSync(path, "utf-8");
|
|
257
|
+
} catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
166
261
|
function hasLocalConfig(cwd) {
|
|
167
262
|
return findLocalConfigPath(cwd) !== null;
|
|
168
263
|
}
|
|
@@ -296,7 +391,7 @@ function getMemoryDir() {
|
|
|
296
391
|
return config.memoryDir;
|
|
297
392
|
return join(homedir(), ".harmony", "memory");
|
|
298
393
|
}
|
|
299
|
-
var DEFAULT_API_URL = "https://app.gethmy.com/api", LOCAL_CONFIG_FILENAME = ".hmy.json", LEGACY_LOCAL_CONFIG_FILENAME = ".harmony-mcp.json", CONFIG_DIR_NAME = ".hmy", CONFIG_DIR_SUBDIR = "agent", LEGACY_CONFIG_DIR_NAME = ".harmony-mcp", warnedLegacyConfigDir = false, warnedLegacyLocalPin = false;
|
|
394
|
+
var DEFAULT_API_URL = "https://app.gethmy.com/api", LOCAL_CONFIG_FILENAME = ".hmy.json", LEGACY_LOCAL_CONFIG_FILENAME = ".harmony-mcp.json", CONFIG_DIR_NAME = ".hmy", CONFIG_DIR_SUBDIR = "agent", LEGACY_CONFIG_DIR_NAME = ".harmony-mcp", warnedLegacyConfigDir = false, warnedLegacyLocalPin = false, warnedUntrackedLocalPin = false;
|
|
300
395
|
var init_config = () => {};
|
|
301
396
|
|
|
302
397
|
// src/prompt-builder.ts
|
|
@@ -726,7 +821,7 @@ var init_prompt_builder = __esm(() => {
|
|
|
726
821
|
};
|
|
727
822
|
VARIANT_INSTRUCTIONS = {
|
|
728
823
|
analysis: `ANALYSIS MODE: Analyze this task thoroughly. Identify requirements, constraints, edge cases, and potential challenges. Do NOT implement anything yet - focus on understanding and planning.`,
|
|
729
|
-
draft: `DRAFT MODE:
|
|
824
|
+
draft: `DRAFT MODE: Draft the approach for review before implementing. Cover the key decisions with their reasons, the data model and the API contracts, and success criteria a test can check. A short signature or schema sketch is fine wherever an interpretation gap would otherwise remain; function bodies, control flow and test code are not - an implementer transcribes a plan faithfully, defects included.`,
|
|
730
825
|
execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`
|
|
731
826
|
};
|
|
732
827
|
});
|
|
@@ -1037,7 +1132,7 @@ __export(exports_run_state, {
|
|
|
1037
1132
|
RUN_STATE_DIR_ENV: () => RUN_STATE_DIR_ENV,
|
|
1038
1133
|
MAX_POINTER_AGE_MS: () => MAX_POINTER_AGE_MS
|
|
1039
1134
|
});
|
|
1040
|
-
import { execFileSync } from "node:child_process";
|
|
1135
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1041
1136
|
import {
|
|
1042
1137
|
existsSync as existsSync3,
|
|
1043
1138
|
mkdirSync as mkdirSync3,
|
|
@@ -1094,7 +1189,7 @@ function psParentTable() {
|
|
|
1094
1189
|
return psTableCache;
|
|
1095
1190
|
const table = new Map;
|
|
1096
1191
|
try {
|
|
1097
|
-
const out =
|
|
1192
|
+
const out = execFileSync2("ps", ["-Ao", "pid=,ppid="], {
|
|
1098
1193
|
encoding: "utf-8",
|
|
1099
1194
|
timeout: 2000,
|
|
1100
1195
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -4686,81 +4781,6 @@ function parseHmyConfig(text) {
|
|
|
4686
4781
|
}
|
|
4687
4782
|
|
|
4688
4783
|
// src/skills.ts
|
|
4689
|
-
var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
|
|
4690
|
-
|
|
4691
|
-
Start work on a Harmony card. Card reference: $ARGUMENTS
|
|
4692
|
-
|
|
4693
|
-
## 1. Find & Fetch Card
|
|
4694
|
-
|
|
4695
|
-
Parse the reference and fetch the card:
|
|
4696
|
-
- \`#42\` or \`42\` → \`harmony_get_card\` with \`shortId: 42\`
|
|
4697
|
-
- UUID → \`harmony_get_card\` with \`cardId\`
|
|
4698
|
-
- Name/text → \`harmony_search_cards\` with \`query\`
|
|
4699
|
-
|
|
4700
|
-
## 2. Get Board State
|
|
4701
|
-
|
|
4702
|
-
Call \`harmony_get_board\` to get columns and labels. From the response:
|
|
4703
|
-
- Find the "In Progress" (or "Progress") column ID
|
|
4704
|
-
- Find the "agent" label ID
|
|
4705
|
-
|
|
4706
|
-
## 3. Setup Card for Work
|
|
4707
|
-
|
|
4708
|
-
Execute these in sequence:
|
|
4709
|
-
1. \`harmony_move_card\` → Move to "In Progress" column
|
|
4710
|
-
2. \`harmony_add_label_to_card\` → Add "agent" label
|
|
4711
|
-
3. \`harmony_start_agent_session\`:
|
|
4712
|
-
- \`cardId\`: Card UUID
|
|
4713
|
-
- \`agentIdentifier\`: Your agent identifier
|
|
4714
|
-
- \`agentName\`: Your agent name
|
|
4715
|
-
- \`currentTask\`: "Analyzing card requirements"
|
|
4716
|
-
|
|
4717
|
-
## 4. Generate Work Prompt
|
|
4718
|
-
|
|
4719
|
-
Call \`harmony_generate_prompt\` with:
|
|
4720
|
-
- \`cardId\` or \`shortId\` (+ \`projectId\` if using shortId)
|
|
4721
|
-
- \`variant\`: Select based on task:
|
|
4722
|
-
- \`"execute"\` (default) → Clear tasks, bug fixes, well-defined work
|
|
4723
|
-
- \`"analysis"\` → Complex features, unclear requirements
|
|
4724
|
-
- \`"draft"\` → Medium complexity, want feedback first
|
|
4725
|
-
|
|
4726
|
-
The generated prompt provides role framing, focus areas, subtasks, linked cards, and suggested outputs.
|
|
4727
|
-
|
|
4728
|
-
## 5. Display Card Summary
|
|
4729
|
-
|
|
4730
|
-
Show the user: Card title, short ID, role, priority, labels, due date, description, and subtasks.
|
|
4731
|
-
|
|
4732
|
-
## 6. Implement Solution
|
|
4733
|
-
|
|
4734
|
-
Work on the card following the generated prompt's guidance. Update progress at milestones:
|
|
4735
|
-
- \`harmony_update_agent_progress\` with \`progressPercent\` (0-100), \`currentTask\`, \`status\`, \`blockers\`
|
|
4736
|
-
|
|
4737
|
-
**Progress checkpoints:** 20% (exploration), 50% (implementation), 80% (testing), 100% (done)
|
|
4738
|
-
|
|
4739
|
-
## 7. Complete Work
|
|
4740
|
-
|
|
4741
|
-
When finished:
|
|
4742
|
-
1. \`harmony_end_agent_session\` with \`status: "completed"\`, \`progressPercent: 100\`
|
|
4743
|
-
2. \`harmony_move_card\` to "Review" column
|
|
4744
|
-
3. Summarize accomplishments
|
|
4745
|
-
|
|
4746
|
-
If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
|
|
4747
|
-
|
|
4748
|
-
## Key Tools Reference
|
|
4749
|
-
|
|
4750
|
-
**Cards:** \`harmony_get_card\` (by \`cardId\`, \`shortId\`, or \`shortIds\`), \`harmony_search_cards\`, \`harmony_create_card\`, \`harmony_update_card\`, \`harmony_move_card\`, \`harmony_delete_card\`, \`harmony_assign_card\`
|
|
4751
|
-
|
|
4752
|
-
**Subtasks:** \`harmony_create_subtask\`, \`harmony_toggle_subtask\`, \`harmony_delete_subtask\`
|
|
4753
|
-
|
|
4754
|
-
**Labels:** \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\`, \`harmony_create_label\`
|
|
4755
|
-
|
|
4756
|
-
**Links:** \`harmony_add_link_to_card\`, \`harmony_remove_link_from_card\`, \`harmony_get_card_links\`
|
|
4757
|
-
|
|
4758
|
-
**Board:** \`harmony_get_board\`, \`harmony_list_projects\`, \`harmony_get_context\`, \`harmony_set_project_context\`
|
|
4759
|
-
|
|
4760
|
-
**Sessions:** \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\`, \`harmony_get_agent_session\`
|
|
4761
|
-
|
|
4762
|
-
**AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
|
|
4763
|
-
`;
|
|
4764
4784
|
function buildSkillFile(skill) {
|
|
4765
4785
|
const content = stripSkillPreamble(skill.content);
|
|
4766
4786
|
if (skill.skillVersion !== undefined && !hasMetadataVersion(content)) {
|
|
@@ -6615,7 +6635,7 @@ var TOOLS = {
|
|
|
6615
6635
|
}
|
|
6616
6636
|
},
|
|
6617
6637
|
harmony_create_plan: {
|
|
6618
|
-
description: "Create a new project plan. Use this to upload
|
|
6638
|
+
description: "Create a new project plan. Use this to upload a plan written during planning. Returns a URL where the plan can be viewed and edited in Harmony.",
|
|
6619
6639
|
inputSchema: {
|
|
6620
6640
|
type: "object",
|
|
6621
6641
|
properties: {
|
|
@@ -6638,7 +6658,10 @@ var TOOLS = {
|
|
|
6638
6658
|
items: {
|
|
6639
6659
|
type: "object",
|
|
6640
6660
|
properties: {
|
|
6641
|
-
content: {
|
|
6661
|
+
content: {
|
|
6662
|
+
type: "string",
|
|
6663
|
+
description: 'One success criterion, as a statement about the finished product that a test can check ("the mirror matches the migration chain"), never a work package ("write the mirror script"). One criterion may take several cards.'
|
|
6664
|
+
},
|
|
6642
6665
|
priority: {
|
|
6643
6666
|
type: "string",
|
|
6644
6667
|
enum: ["high", "medium", "low"],
|
|
@@ -6652,7 +6675,7 @@ var TOOLS = {
|
|
|
6652
6675
|
},
|
|
6653
6676
|
required: ["content"]
|
|
6654
6677
|
},
|
|
6655
|
-
description: "
|
|
6678
|
+
description: "The plan's success criteria, one entry each - what must be true when the plan is done, not a breakdown of the work to do it."
|
|
6656
6679
|
}
|
|
6657
6680
|
},
|
|
6658
6681
|
required: ["title"]
|
|
@@ -6673,7 +6696,7 @@ var TOOLS = {
|
|
|
6673
6696
|
}
|
|
6674
6697
|
},
|
|
6675
6698
|
harmony_update_plan: {
|
|
6676
|
-
description: "Update an existing plan
|
|
6699
|
+
description: "Update an existing plan: its title, content, status, or the timeline dates its bar spans. " + "`startDate`/`endDate` are the plan's OWN schedule, the same pair a person sets by dragging the bar in the timeline view. " + "A plan is pinned on both or on neither: send both to schedule it, or both as null to return it to the span derived from its linked cards. " + "Sending one alone is refused unless the plan is already pinned. " + "They are never adjusted automatically — a card running past `endDate` is drawn as an overrun, and only a person extends the plan.",
|
|
6677
6700
|
inputSchema: {
|
|
6678
6701
|
type: "object",
|
|
6679
6702
|
properties: {
|
|
@@ -6687,6 +6710,16 @@ var TOOLS = {
|
|
|
6687
6710
|
type: "string",
|
|
6688
6711
|
enum: ["draft", "active", "archived"],
|
|
6689
6712
|
description: "New status"
|
|
6713
|
+
},
|
|
6714
|
+
startDate: {
|
|
6715
|
+
type: "string",
|
|
6716
|
+
nullable: true,
|
|
6717
|
+
description: "Timeline start as YYYY-MM-DD, or null to unpin (send endDate null too)."
|
|
6718
|
+
},
|
|
6719
|
+
endDate: {
|
|
6720
|
+
type: "string",
|
|
6721
|
+
nullable: true,
|
|
6722
|
+
description: "Timeline end as YYYY-MM-DD, or null to unpin (send startDate null too). Must not precede startDate."
|
|
6690
6723
|
}
|
|
6691
6724
|
},
|
|
6692
6725
|
required: ["planId"]
|
|
@@ -8671,6 +8704,13 @@ ${options}
|
|
|
8671
8704
|
if (args.status !== undefined) {
|
|
8672
8705
|
updates.status = z.enum(["draft", "active", "archived"]).parse(args.status);
|
|
8673
8706
|
}
|
|
8707
|
+
const planDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, {
|
|
8708
|
+
message: "expected a date as YYYY-MM-DD"
|
|
8709
|
+
});
|
|
8710
|
+
if (args.startDate !== undefined)
|
|
8711
|
+
updates.startDate = planDate.nullable().parse(args.startDate);
|
|
8712
|
+
if (args.endDate !== undefined)
|
|
8713
|
+
updates.endDate = planDate.nullable().parse(args.endDate);
|
|
8674
8714
|
const result = await client3.updatePlan(planId, updates);
|
|
8675
8715
|
return { success: true, plan: result.plan };
|
|
8676
8716
|
}
|
|
@@ -8970,6 +9010,7 @@ class HarmonyMCPServer {
|
|
|
8970
9010
|
}
|
|
8971
9011
|
|
|
8972
9012
|
// src/tui/setup.ts
|
|
9013
|
+
import { spawnSync } from "node:child_process";
|
|
8973
9014
|
import { createHash as createHash5 } from "node:crypto";
|
|
8974
9015
|
import {
|
|
8975
9016
|
existsSync as existsSync9,
|
|
@@ -8984,6 +9025,288 @@ import * as p4 from "@clack/prompts";
|
|
|
8984
9025
|
init_config();
|
|
8985
9026
|
init_oauth_login();
|
|
8986
9027
|
|
|
9028
|
+
// src/tui/agent-instructions.ts
|
|
9029
|
+
var HARMONY_PLAN_RULE = `**A plan says what must be true and why. It does not contain the code.** In it: the architecture
|
|
9030
|
+
and technology decisions with their reasons, the data model and the API contracts, a short
|
|
9031
|
+
signature or schema sketch wherever an interpretation gap would otherwise remain, and success
|
|
9032
|
+
criteria a test can check. Not in it: function bodies, control flow, error handling, test code.
|
|
9033
|
+
Detail follows risk — a throwaway script gets a rough plan, while auth, money, migrations and
|
|
9034
|
+
anything security-relevant get their contracts and edge cases written out.
|
|
9035
|
+
|
|
9036
|
+
Code in a plan carries the authority of a plan and the quality of a draft that no compiler, test
|
|
9037
|
+
or review has read, and an implementer transcribes it faithfully, defects included. So when the
|
|
9038
|
+
plan and the code disagree, the code is the evidence: check it, record the decision as a comment,
|
|
9039
|
+
and correct the plan as well as the code.`;
|
|
9040
|
+
var HARMONY_AGENTS_SECTION = `## Harmony
|
|
9041
|
+
|
|
9042
|
+
This project uses Harmony for task management. The \`harmony_*\` MCP tools are in your tool
|
|
9043
|
+
listing with live schemas — read them there. This section covers only what the schemas do not say.
|
|
9044
|
+
|
|
9045
|
+
### Identify as yourself
|
|
9046
|
+
|
|
9047
|
+
Every session call takes \`agentIdentifier\` + \`agentName\`. Use your OWN, never a value copied
|
|
9048
|
+
from this file. The board shows agents as teammates, so a session attributed to the wrong runtime
|
|
9049
|
+
misattributes the work in front of the whole team.
|
|
9050
|
+
|
|
9051
|
+
Known values: \`claude-code\` / "Claude Code" · \`codex\` / "OpenAI Codex" · \`cursor\` / "Cursor" ·
|
|
9052
|
+
\`claude-desktop\` / "Claude Desktop". If you are none of these, use your own name.
|
|
9053
|
+
|
|
9054
|
+
### Starting work — one call, not three
|
|
9055
|
+
|
|
9056
|
+
\`harmony_start_agent_session\` moves the card and adds the labels itself. Do not call
|
|
9057
|
+
\`harmony_move_card\` or \`harmony_add_label_to_card\` first, and do not fetch the board for a
|
|
9058
|
+
column id or a label id — both arguments match by name.
|
|
9059
|
+
|
|
9060
|
+
\`\`\`
|
|
9061
|
+
harmony_start_agent_session({
|
|
9062
|
+
cardId,
|
|
9063
|
+
agentIdentifier, agentName, // your own
|
|
9064
|
+
currentTask: "Reading the auth middleware to find the affected routes",
|
|
9065
|
+
moveToColumn: "In Progress",
|
|
9066
|
+
addLabels: ["agent"],
|
|
9067
|
+
steerable: true, // only if you will poll for steering — see below
|
|
9068
|
+
})
|
|
9069
|
+
\`\`\`
|
|
9070
|
+
|
|
9071
|
+
**Then read the reply, because the setup half fails quietly.** \`movedTo\` names the column it
|
|
9072
|
+
actually moved to and \`labelsAdded\` the labels it actually added; a miss leaves them null or
|
|
9073
|
+
empty and raises no error. The column match is a case-insensitive **substring**, so a board with
|
|
9074
|
+
"Ready for Review" ahead of "Review" can take the wrong one. If \`movedTo\` is null or not the
|
|
9075
|
+
column you meant, call \`harmony_move_card\` — it matches exactly first and fails loudly, listing
|
|
9076
|
+
the columns.
|
|
9077
|
+
|
|
9078
|
+
Keep the returned \`session.id\`; the steering poll needs it. Then call
|
|
9079
|
+
\`harmony_generate_prompt\` for role framing and focus areas — \`variant\` is \`execute\`
|
|
9080
|
+
(default), \`analysis\`, or \`draft\`.
|
|
9081
|
+
|
|
9082
|
+
### Progress — \`actions\` is what survives as evidence
|
|
9083
|
+
|
|
9084
|
+
On the card itself, \`progressPercent\` and \`currentTask\` each overwrite one field, so the live
|
|
9085
|
+
status shows only your latest checkpoint. The timeline keeps more: a checkpoint that carries both
|
|
9086
|
+
a \`progressPercent\` and a \`currentTask\` different from the last one leaves a row saying what you
|
|
9087
|
+
were **about to do**, and **each entry in \`actions\` leaves a row saying what you actually did**.
|
|
9088
|
+
Report four checkpoints with no \`actions\` and a two-hour run reads as four intentions and no
|
|
9089
|
+
evidence.
|
|
9090
|
+
|
|
9091
|
+
Name what you DID since the last checkpoint: the file you edited and why, the gate you ran and
|
|
9092
|
+
what it said, the approach you ruled out and on what evidence.
|
|
9093
|
+
|
|
9094
|
+
\`\`\`
|
|
9095
|
+
harmony_update_agent_progress({
|
|
9096
|
+
cardId, agentIdentifier, agentName,
|
|
9097
|
+
progressPercent: 50,
|
|
9098
|
+
currentTask: "Extracting refreshIfExpired() in auth.ts",
|
|
9099
|
+
actions: [
|
|
9100
|
+
{ description: "Read auth.ts and middleware/session.ts — the refresh path is duplicated in both, which is the actual bug" },
|
|
9101
|
+
{ description: "Ruled out patching verifyToken(): three routes depend on its current behaviour" },
|
|
9102
|
+
{ description: "Ran bun run lint — green, exit 0" },
|
|
9103
|
+
],
|
|
9104
|
+
})
|
|
9105
|
+
\`\`\`
|
|
9106
|
+
|
|
9107
|
+
Three to six entries per checkpoint, one sentence each; past 512 characters an entry is silently truncated. Facts, not
|
|
9108
|
+
intentions — one vague entry is worse than none. Checkpoints: 20% explored · 50% implementing ·
|
|
9109
|
+
80% verifying · 100% done. \`currentTask\` is what you are doing now — never leave it generic.
|
|
9110
|
+
|
|
9111
|
+
### Steering and Stop
|
|
9112
|
+
|
|
9113
|
+
If you passed \`steerable: true\`, poll right after every progress update:
|
|
9114
|
+
|
|
9115
|
+
\`\`\`
|
|
9116
|
+
harmony_get_pending_messages({ cardId, sessionId, sinceSeq }) // sinceSeq starts at 0
|
|
9117
|
+
\`\`\`
|
|
9118
|
+
|
|
9119
|
+
Messages come back oldest first. Fold them into the next step and advance \`sinceSeq\` to the
|
|
9120
|
+
largest \`seq\` returned, so each is handled exactly once.
|
|
9121
|
+
|
|
9122
|
+
Two flags come back and mean opposite things:
|
|
9123
|
+
|
|
9124
|
+
| flag | meaning | what to do |
|
|
9125
|
+
|---|---|---|
|
|
9126
|
+
| \`stopped: true\` | a human pressed Stop | **Terminal.** Make no further edits, commits, pushes, card moves, comments or progress writes. Report what is finished and where any uncommitted work lives. |
|
|
9127
|
+
| \`sessionStale: true\` | your session id is no longer live — usually the inactivity sweep | **Nobody stopped you.** Carry on — with a new id. |
|
|
9128
|
+
|
|
9129
|
+
\`harmony_update_agent_progress\` reports the same two flags, and the recovery from a stale session
|
|
9130
|
+
differs by which call told you:
|
|
9131
|
+
|
|
9132
|
+
- From the **progress** call, a replacement session has already been opened for you and inherited
|
|
9133
|
+
the steering channel. Take the new \`session.id\` from that reply and keep going.
|
|
9134
|
+
- From the **poll**, nothing was opened. Call \`harmony_start_agent_session\` yourself and poll
|
|
9135
|
+
with the id it returns.
|
|
9136
|
+
|
|
9137
|
+
If the two flags ever disagree, the stop wins.
|
|
9138
|
+
|
|
9139
|
+
### Finishing
|
|
9140
|
+
|
|
9141
|
+
\`\`\`
|
|
9142
|
+
harmony_end_agent_session({ cardId, status: "completed", progressPercent: 100, moveToColumn: "Review" })
|
|
9143
|
+
\`\`\`
|
|
9144
|
+
|
|
9145
|
+
One call: it moves the card, and on \`status: "completed"\` it also removes the \`agent\` label. Use
|
|
9146
|
+
\`status: "paused"\` when you stop mid-flight — that leaves the label on, which is what you want.
|
|
9147
|
+
|
|
9148
|
+
Attach a PR **after** the session end has moved the card, both ways: \`harmony_add_external_link\`
|
|
9149
|
+
(durable — it survives a later description edit) and a \`PR: <url>\` line in the description.
|
|
9150
|
+
|
|
9151
|
+
### Writing a plan
|
|
9152
|
+
|
|
9153
|
+
${HARMONY_PLAN_RULE}
|
|
9154
|
+
|
|
9155
|
+
### Traps
|
|
9156
|
+
|
|
9157
|
+
- **A \`shortId\` is project-scoped, and the two ways it can miss are opposites.** With **no**
|
|
9158
|
+
active project the number is resolved across every project you can reach, so it may come back
|
|
9159
|
+
\`needsDisambiguation\` with \`candidates\` — ask which one is meant. With an active project the
|
|
9160
|
+
number resolves only inside it, and a card that lives elsewhere fails with an error naming the
|
|
9161
|
+
projects it is really in — switch with \`harmony_set_project_context\` or pass \`projectId\`.
|
|
9162
|
+
Either way, check \`resolvedProject\` matches the card you meant before starting work.
|
|
9163
|
+
- **Fetch many cards in one call:** \`harmony_get_card({ shortIds: [400, 401, 402] })\`, max 100.
|
|
9164
|
+
- **Moving a card to a terminal column ends your session** — a column that marks cards done, or
|
|
9165
|
+
one named \`done\`, \`completed\` or \`review\`. The response says \`sessionEnded\`.
|
|
9166
|
+
- **Read \`harmony_get_comments\` before you act.** Steering messages do not include comments, and
|
|
9167
|
+
a later comment outranks an earlier one it contradicts.
|
|
9168
|
+
- **Report findings and decisions as comments, not description edits.** \`harmony_add_comment\`
|
|
9169
|
+
takes a \`commentType\`: \`question\` and \`blocker\` signal that you need a human; \`decision\`,
|
|
9170
|
+
\`finding\`, \`summary\`, \`progress\` and \`message\` are the rest.`;
|
|
9171
|
+
var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
|
|
9172
|
+
|
|
9173
|
+
Work a Harmony card. Card reference: $ARGUMENTS
|
|
9174
|
+
|
|
9175
|
+
The \`harmony_*\` MCP tools are in your tool listing with live schemas — read them there. This
|
|
9176
|
+
prompt covers only what the schemas do not say.
|
|
9177
|
+
|
|
9178
|
+
## 1. Fetch the card
|
|
9179
|
+
|
|
9180
|
+
- \`#42\` or \`42\` → \`harmony_get_card({ shortId: 42 })\`
|
|
9181
|
+
- UUID → \`harmony_get_card({ cardId })\`
|
|
9182
|
+
- A name or phrase → \`harmony_search_cards({ query })\`
|
|
9183
|
+
- Several at once → \`harmony_get_card({ shortIds: [40, 41, 42] })\`, max 100
|
|
9184
|
+
|
|
9185
|
+
A \`shortId\` is project-scoped, and the two ways it can miss are opposites. With **no** active
|
|
9186
|
+
project the number is resolved across every project you can reach, so it may come back
|
|
9187
|
+
\`needsDisambiguation\` with \`candidates\` — ask which one is meant. With an active project the
|
|
9188
|
+
number resolves only inside it, and a card that lives elsewhere fails with an error naming the
|
|
9189
|
+
projects it is really in; switch with \`harmony_set_project_context\` or pass \`projectId\`.
|
|
9190
|
+
Either way, check \`resolvedProject\` before you start.
|
|
9191
|
+
|
|
9192
|
+
Read \`harmony_get_comments\` too: a later comment outranks an earlier one it contradicts.
|
|
9193
|
+
|
|
9194
|
+
## 2. Start the session — one call, not three
|
|
9195
|
+
|
|
9196
|
+
\`harmony_start_agent_session\` moves the card and adds the labels itself. Do not call
|
|
9197
|
+
\`harmony_move_card\` or \`harmony_add_label_to_card\` first, and do not fetch the board for a
|
|
9198
|
+
column id or a label id — both arguments match by name.
|
|
9199
|
+
|
|
9200
|
+
\`\`\`
|
|
9201
|
+
harmony_start_agent_session({
|
|
9202
|
+
cardId,
|
|
9203
|
+
agentIdentifier: "$AGENT_IDENTIFIER",
|
|
9204
|
+
agentName: "$AGENT_NAME",
|
|
9205
|
+
currentTask: "Reading the auth middleware to find the affected routes",
|
|
9206
|
+
moveToColumn: "In Progress",
|
|
9207
|
+
addLabels: ["agent"],
|
|
9208
|
+
steerable: true,
|
|
9209
|
+
})
|
|
9210
|
+
\`\`\`
|
|
9211
|
+
|
|
9212
|
+
\`currentTask\` says what you are about to do, specifically. Never "Analyzing card requirements".
|
|
9213
|
+
Keep the returned \`session.id\` — step 4 needs it.
|
|
9214
|
+
|
|
9215
|
+
**Read the reply, because the setup half fails quietly.** \`movedTo\` names the column it actually
|
|
9216
|
+
moved to and \`labelsAdded\` the labels it actually added; a miss leaves them null or empty and
|
|
9217
|
+
raises no error. The column match is a case-insensitive **substring**, so a board with "Ready for
|
|
9218
|
+
Review" ahead of "Review" can take the wrong one. If \`movedTo\` is null or not the column you
|
|
9219
|
+
meant, call \`harmony_move_card\` — it matches exactly first and fails loudly, listing the columns.
|
|
9220
|
+
|
|
9221
|
+
## 3. Get the work prompt
|
|
9222
|
+
|
|
9223
|
+
\`harmony_generate_prompt\` with \`cardId\` (or \`shortId\` plus \`projectId\`) and a \`variant\`:
|
|
9224
|
+
\`execute\` (default) for well-defined work, \`analysis\` for unclear requirements, \`draft\` when
|
|
9225
|
+
you want feedback on a design first. It returns role framing, focus areas, subtasks and links.
|
|
9226
|
+
|
|
9227
|
+
Then show the user the card: title, short id, priority, labels, due date, description, subtasks.
|
|
9228
|
+
|
|
9229
|
+
## 4. Implement, and check in at every milestone
|
|
9230
|
+
|
|
9231
|
+
Checkpoints: 20% explored · 50% implementing · 80% verifying · 100% done.
|
|
9232
|
+
|
|
9233
|
+
On the card itself, \`progressPercent\` and \`currentTask\` each overwrite one field, so the live
|
|
9234
|
+
status shows only your latest checkpoint. The timeline keeps more: a checkpoint that carries both
|
|
9235
|
+
a \`progressPercent\` and a \`currentTask\` different from the last one leaves a row saying what you
|
|
9236
|
+
were **about to do**, and **each entry in \`actions\` leaves a row saying what you actually did** —
|
|
9237
|
+
that is the evidence the team can still read afterwards.
|
|
9238
|
+
|
|
9239
|
+
\`\`\`
|
|
9240
|
+
harmony_update_agent_progress({
|
|
9241
|
+
cardId, agentIdentifier: "$AGENT_IDENTIFIER", agentName: "$AGENT_NAME",
|
|
9242
|
+
progressPercent: 50,
|
|
9243
|
+
currentTask: "Extracting refreshIfExpired() in auth.ts",
|
|
9244
|
+
actions: [
|
|
9245
|
+
{ description: "Read auth.ts and middleware/session.ts — the refresh path is duplicated in both, which is the actual bug" },
|
|
9246
|
+
{ description: "Ruled out patching verifyToken(): three routes depend on its current behaviour" },
|
|
9247
|
+
{ description: "Ran bun run lint — green, exit 0" },
|
|
9248
|
+
],
|
|
9249
|
+
status: "working", // or blocked / waiting / paused
|
|
9250
|
+
blockers: [],
|
|
9251
|
+
})
|
|
9252
|
+
\`\`\`
|
|
9253
|
+
|
|
9254
|
+
Three to six entries per checkpoint, one sentence each; past 512 characters an entry is silently truncated. Say what you did,
|
|
9255
|
+
not what you intend to do.
|
|
9256
|
+
|
|
9257
|
+
Right after each update, poll for steering:
|
|
9258
|
+
|
|
9259
|
+
\`\`\`
|
|
9260
|
+
harmony_get_pending_messages({ cardId, sessionId, sinceSeq }) // sinceSeq starts at 0
|
|
9261
|
+
\`\`\`
|
|
9262
|
+
|
|
9263
|
+
Fold any messages into the next step and advance \`sinceSeq\` to the largest \`seq\` returned. Two
|
|
9264
|
+
flags come back and mean opposite things:
|
|
9265
|
+
|
|
9266
|
+
- \`stopped: true\` — a human pressed Stop. **Terminal.** Make no further edits, commits, pushes,
|
|
9267
|
+
card moves, comments or progress writes; report what is finished and where any uncommitted work
|
|
9268
|
+
lives.
|
|
9269
|
+
- \`sessionStale: true\` — your session id is no longer live, usually the inactivity sweep. **Nobody stopped you.**
|
|
9270
|
+
Carry on, with a new id: the **poll** opens nothing, so call \`harmony_start_agent_session\`
|
|
9271
|
+
yourself; the **progress** call has already opened a replacement that inherited the steering
|
|
9272
|
+
channel, so just take the new \`session.id\` from its reply.
|
|
9273
|
+
|
|
9274
|
+
If the two flags ever disagree, the stop wins.
|
|
9275
|
+
|
|
9276
|
+
Report findings and decisions with \`harmony_add_comment\` (\`commentType\`: \`question\` and
|
|
9277
|
+
\`blocker\` signal that you need a human; \`decision\`, \`finding\`, \`summary\`, \`progress\`,
|
|
9278
|
+
\`message\`), not by editing the card description.
|
|
9279
|
+
|
|
9280
|
+
## 5. Finish
|
|
9281
|
+
|
|
9282
|
+
\`\`\`
|
|
9283
|
+
harmony_end_agent_session({ cardId, status: "completed", progressPercent: 100, moveToColumn: "Review" })
|
|
9284
|
+
\`\`\`
|
|
9285
|
+
|
|
9286
|
+
One call: it moves the card, and on \`status: "completed"\` it also removes the \`agent\` label. Use
|
|
9287
|
+
\`status: "paused"\` when you stop mid-flight — that leaves the label on, which is what you want.
|
|
9288
|
+
|
|
9289
|
+
Opened a PR? Attach it **after** the session end has moved the card, both ways:
|
|
9290
|
+
\`harmony_add_external_link\` (durable — it survives a later description edit) and a
|
|
9291
|
+
\`PR: <url>\` line in the description.
|
|
9292
|
+
|
|
9293
|
+
Then summarise what changed.
|
|
9294
|
+
|
|
9295
|
+
## Writing a plan
|
|
9296
|
+
|
|
9297
|
+
${HARMONY_PLAN_RULE}
|
|
9298
|
+
|
|
9299
|
+
## Worth knowing
|
|
9300
|
+
|
|
9301
|
+
- Moving a card to a terminal column ends your session — a column that marks cards done, or one
|
|
9302
|
+
named \`done\`, \`completed\` or \`review\`. The response says \`sessionEnded\`.
|
|
9303
|
+
- \`harmony_add_label_to_card\` and \`harmony_start_agent_session\`'s \`addLabels\` both CREATE a
|
|
9304
|
+
label that does not exist yet. Check the spelling.
|
|
9305
|
+
- \`harmony_add_comment\` works on any card you can see, including one you hold no session on.`;
|
|
9306
|
+
function renderWorkflowPrompt(opts) {
|
|
9307
|
+
return HARMONY_WORKFLOW_PROMPT.replaceAll("$ARGUMENTS", opts.cardArgument).replaceAll("$AGENT_IDENTIFIER", opts.agentIdentifier).replaceAll("$AGENT_NAME", opts.agentName);
|
|
9308
|
+
}
|
|
9309
|
+
|
|
8987
9310
|
// src/tui/agents.ts
|
|
8988
9311
|
import { existsSync as existsSync6 } from "node:fs";
|
|
8989
9312
|
import { homedir as homedir5 } from "node:os";
|
|
@@ -9883,6 +10206,67 @@ function appendToToml(filePath, section, content, options = {}) {
|
|
|
9883
10206
|
};
|
|
9884
10207
|
}
|
|
9885
10208
|
}
|
|
10209
|
+
var MARKDOWN_SECTION_START = "<!-- harmony:start -->";
|
|
10210
|
+
var MARKDOWN_SECTION_END = "<!-- harmony:end -->";
|
|
10211
|
+
function findSection(text) {
|
|
10212
|
+
let from = 0;
|
|
10213
|
+
while (true) {
|
|
10214
|
+
const start = text.indexOf(MARKDOWN_SECTION_START, from);
|
|
10215
|
+
if (start === -1)
|
|
10216
|
+
return null;
|
|
10217
|
+
const bodyFrom = start + MARKDOWN_SECTION_START.length;
|
|
10218
|
+
const end = text.indexOf(MARKDOWN_SECTION_END, bodyFrom);
|
|
10219
|
+
if (end === -1)
|
|
10220
|
+
return null;
|
|
10221
|
+
const nextStart = text.indexOf(MARKDOWN_SECTION_START, bodyFrom);
|
|
10222
|
+
if (nextStart === -1 || nextStart > end)
|
|
10223
|
+
return { start, end };
|
|
10224
|
+
from = nextStart;
|
|
10225
|
+
}
|
|
10226
|
+
}
|
|
10227
|
+
function mergeMarkdownSection(filePath, content, options = {}) {
|
|
10228
|
+
const section = `${MARKDOWN_SECTION_START}
|
|
10229
|
+
${content.trim()}
|
|
10230
|
+
${MARKDOWN_SECTION_END}
|
|
10231
|
+
`;
|
|
10232
|
+
if (!existsSync8(filePath)) {
|
|
10233
|
+
try {
|
|
10234
|
+
ensureDir(dirname3(filePath));
|
|
10235
|
+
writeFileSync5(filePath, section, { mode: 420 });
|
|
10236
|
+
return { path: filePath, action: "create" };
|
|
10237
|
+
} catch (error) {
|
|
10238
|
+
return {
|
|
10239
|
+
path: filePath,
|
|
10240
|
+
action: "skip",
|
|
10241
|
+
error: error instanceof Error ? error.message : String(error)
|
|
10242
|
+
};
|
|
10243
|
+
}
|
|
10244
|
+
}
|
|
10245
|
+
try {
|
|
10246
|
+
const existing = readFileSync7(filePath, "utf-8");
|
|
10247
|
+
const found = findSection(existing);
|
|
10248
|
+
if (found) {
|
|
10249
|
+
if (!options.force)
|
|
10250
|
+
return { path: filePath, action: "skip" };
|
|
10251
|
+
const updated = existing.slice(0, found.start) + section.trimEnd() + existing.slice(found.end + MARKDOWN_SECTION_END.length);
|
|
10252
|
+
writeFileSync5(filePath, updated, { mode: 420 });
|
|
10253
|
+
return { path: filePath, action: "update" };
|
|
10254
|
+
}
|
|
10255
|
+
const separator = existing.endsWith(`
|
|
10256
|
+
`) ? `
|
|
10257
|
+
` : `
|
|
10258
|
+
|
|
10259
|
+
`;
|
|
10260
|
+
writeFileSync5(filePath, existing + separator + section, { mode: 420 });
|
|
10261
|
+
return { path: filePath, action: "merge" };
|
|
10262
|
+
} catch (error) {
|
|
10263
|
+
return {
|
|
10264
|
+
path: filePath,
|
|
10265
|
+
action: "skip",
|
|
10266
|
+
error: error instanceof Error ? error.message : String(error)
|
|
10267
|
+
};
|
|
10268
|
+
}
|
|
10269
|
+
}
|
|
9886
10270
|
async function writeFilesWithProgress(files, options = {}) {
|
|
9887
10271
|
const results = [];
|
|
9888
10272
|
const home = homedir6();
|
|
@@ -9895,6 +10279,8 @@ async function writeFilesWithProgress(files, options = {}) {
|
|
|
9895
10279
|
result = mergeJsonFile(file.path, jsonContent, options);
|
|
9896
10280
|
} else if (file.type === "toml" && file.tomlSection) {
|
|
9897
10281
|
result = appendToToml(file.path, file.tomlSection, file.content, options);
|
|
10282
|
+
} else if (file.type === "markdown") {
|
|
10283
|
+
result = mergeMarkdownSection(file.path, file.content, options);
|
|
9898
10284
|
} else {
|
|
9899
10285
|
result = writeFile(file.path, file.content, {
|
|
9900
10286
|
...options,
|
|
@@ -9912,7 +10298,7 @@ async function writeFilesWithProgress(files, options = {}) {
|
|
|
9912
10298
|
} else if (result.action === "skip") {
|
|
9913
10299
|
console.log(messages.fileSkipped(displayPath));
|
|
9914
10300
|
} else {
|
|
9915
|
-
const actionLabel = result.action === "merge" ? "
|
|
10301
|
+
const actionLabel = result.action === "create" ? "created" : result.action === "merge" ? "merged" : "updated";
|
|
9916
10302
|
console.log(` ${colors.success("✓")} ${colors.dim(displayPath)} ${colors.dim(`(${actionLabel})`)}`);
|
|
9917
10303
|
}
|
|
9918
10304
|
}
|
|
@@ -9940,7 +10326,6 @@ function getWriteSummary(files, options = {}) {
|
|
|
9940
10326
|
// src/tui/setup.ts
|
|
9941
10327
|
var SAFE_HARMONY_TOOLS = [
|
|
9942
10328
|
"harmony_get_card",
|
|
9943
|
-
"harmony_get_card_by_short_id",
|
|
9944
10329
|
"harmony_search_cards",
|
|
9945
10330
|
"harmony_get_board",
|
|
9946
10331
|
"harmony_get_context",
|
|
@@ -9952,12 +10337,17 @@ var SAFE_HARMONY_TOOLS = [
|
|
|
9952
10337
|
"harmony_get_comments",
|
|
9953
10338
|
"harmony_get_plan",
|
|
9954
10339
|
"harmony_list_plans",
|
|
10340
|
+
"harmony_get_playbook",
|
|
10341
|
+
"harmony_list_playbook",
|
|
9955
10342
|
"harmony_get_agent_session",
|
|
10343
|
+
"harmony_get_pending_messages",
|
|
9956
10344
|
"harmony_get_workspace_members",
|
|
9957
10345
|
"harmony_list_agents",
|
|
9958
10346
|
"harmony_resolve_links",
|
|
10347
|
+
"harmony_suggest_relations",
|
|
9959
10348
|
"harmony_recall",
|
|
9960
10349
|
"harmony_memory_search",
|
|
10350
|
+
"harmony_vault_index",
|
|
9961
10351
|
"harmony_generate_prompt",
|
|
9962
10352
|
"harmony_create_card",
|
|
9963
10353
|
"harmony_update_card",
|
|
@@ -9965,6 +10355,7 @@ var SAFE_HARMONY_TOOLS = [
|
|
|
9965
10355
|
"harmony_assign_card",
|
|
9966
10356
|
"harmony_create_subtask",
|
|
9967
10357
|
"harmony_toggle_subtask",
|
|
10358
|
+
"harmony_update_subtask",
|
|
9968
10359
|
"harmony_add_label_to_card",
|
|
9969
10360
|
"harmony_remove_label_from_card",
|
|
9970
10361
|
"harmony_create_label",
|
|
@@ -9972,6 +10363,8 @@ var SAFE_HARMONY_TOOLS = [
|
|
|
9972
10363
|
"harmony_update_comment",
|
|
9973
10364
|
"harmony_add_link_to_card",
|
|
9974
10365
|
"harmony_remove_link_from_card",
|
|
10366
|
+
"harmony_add_external_link",
|
|
10367
|
+
"harmony_remove_external_link",
|
|
9975
10368
|
"harmony_start_agent_session",
|
|
9976
10369
|
"harmony_update_agent_progress",
|
|
9977
10370
|
"harmony_end_agent_session",
|
|
@@ -9984,6 +10377,7 @@ var SAFE_HARMONY_TOOLS = [
|
|
|
9984
10377
|
"harmony_remember",
|
|
9985
10378
|
"harmony_relate",
|
|
9986
10379
|
"harmony_update_memory",
|
|
10380
|
+
"harmony_recall_feedback",
|
|
9987
10381
|
"harmony_process_command",
|
|
9988
10382
|
"harmony_sync"
|
|
9989
10383
|
];
|
|
@@ -10197,63 +10591,10 @@ ${summary}`);
|
|
|
10197
10591
|
break;
|
|
10198
10592
|
}
|
|
10199
10593
|
case "codex": {
|
|
10200
|
-
const agentsContent = `# Harmony Integration
|
|
10201
|
-
|
|
10202
|
-
This project uses Harmony for task management. When working on tasks:
|
|
10203
|
-
|
|
10204
|
-
## Agent identity — always identify as yourself
|
|
10205
|
-
|
|
10206
|
-
Every \`harmony_start_agent_session\` call passes \`agentIdentifier\` + \`agentName\`. **Use your own
|
|
10207
|
-
identity, never a hardcoded one from this file.** AGENTS.md is a cross-runtime convention file, so
|
|
10208
|
-
more than one kind of agent will read it; the board shows agents as teammates, and a session
|
|
10209
|
-
attributed to the wrong runtime misattributes the work in front of the whole team.
|
|
10210
|
-
|
|
10211
|
-
- \`agentIdentifier\` — a stable kebab-case id for the runtime you actually are
|
|
10212
|
-
- \`agentName\` — its human-readable name
|
|
10213
|
-
|
|
10214
|
-
Known values: \`claude-code\` / "Claude Code", \`codex\` / "OpenAI Codex", \`cursor\` / "Cursor",
|
|
10215
|
-
\`claude-desktop\` / "Claude Desktop". If you are a runtime not listed here, use your own name rather
|
|
10216
|
-
than borrowing the closest entry.
|
|
10217
|
-
|
|
10218
|
-
## Starting Work on a Card
|
|
10219
|
-
|
|
10220
|
-
When given a card reference (e.g., #42 or a card name), follow this workflow:
|
|
10221
|
-
|
|
10222
|
-
1. Use \`harmony_get_card\` or \`harmony_search_cards\` to find the card
|
|
10223
|
-
2. Move the card to "In Progress" using \`harmony_move_card\`
|
|
10224
|
-
3. Add the "agent" label using \`harmony_add_label_to_card\`
|
|
10225
|
-
4. Start a session with \`harmony_start_agent_session\`, passing **your own** \`agentIdentifier\` +
|
|
10226
|
-
\`agentName\` (see "Agent identity" above)
|
|
10227
|
-
5. Show the card details to the user
|
|
10228
|
-
6. Use \`harmony_generate_prompt\` to get guidance, then implement the solution
|
|
10229
|
-
7. Update progress periodically with \`harmony_update_agent_progress\`
|
|
10230
|
-
8. When done, call \`harmony_end_agent_session\` and move to "Review"
|
|
10231
|
-
|
|
10232
|
-
## Auto-Detect Card for Implementation Tasks
|
|
10233
|
-
|
|
10234
|
-
Before implementing a plan or feature, check if it maps to an existing Harmony card:
|
|
10235
|
-
|
|
10236
|
-
1. Use \`harmony_search_cards\` with keywords from the task description
|
|
10237
|
-
2. If a match is found, call \`harmony_start_agent_session\` with **your own** \`agentIdentifier\` +
|
|
10238
|
-
\`agentName\` (see "Agent identity" above), plus \`moveToColumn: "In Progress"\`, \`addLabels: ["agent"]\`
|
|
10239
|
-
3. Update progress with \`harmony_update_agent_progress\` at milestones
|
|
10240
|
-
4. When done, call \`harmony_end_agent_session\` with status: "completed", moveToColumn: "Review"
|
|
10241
|
-
|
|
10242
|
-
Skip if: work was already started with a card reference, or no matching card exists.
|
|
10243
|
-
|
|
10244
|
-
## Available Harmony Tools
|
|
10245
|
-
|
|
10246
|
-
- \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\` - Find cards
|
|
10247
|
-
- \`harmony_move_card\` - Move cards between columns
|
|
10248
|
-
- \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\` - Manage labels
|
|
10249
|
-
- \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\` - Track work
|
|
10250
|
-
- \`harmony_get_board\` - Get board state
|
|
10251
|
-
- \`harmony_generate_prompt\` - Get role-based guidance and focus areas for the card
|
|
10252
|
-
`;
|
|
10253
10594
|
files.push({
|
|
10254
10595
|
path: join9(cwd, "AGENTS.md"),
|
|
10255
|
-
content:
|
|
10256
|
-
type: "
|
|
10596
|
+
content: HARMONY_AGENTS_SECTION,
|
|
10597
|
+
type: "markdown"
|
|
10257
10598
|
});
|
|
10258
10599
|
const promptContent = `---
|
|
10259
10600
|
name: hmy
|
|
@@ -10264,7 +10605,7 @@ arguments:
|
|
|
10264
10605
|
required: true
|
|
10265
10606
|
---
|
|
10266
10607
|
|
|
10267
|
-
${
|
|
10608
|
+
${renderWorkflowPrompt({ cardArgument: "{{card}}", agentIdentifier: "codex", agentName: "OpenAI Codex" })}
|
|
10268
10609
|
`;
|
|
10269
10610
|
if (installMode === "global") {
|
|
10270
10611
|
files.push({
|
|
@@ -10321,7 +10662,7 @@ alwaysApply: false
|
|
|
10321
10662
|
|
|
10322
10663
|
When the user asks you to work on a Harmony card (references like #42, card names, or UUIDs):
|
|
10323
10664
|
|
|
10324
|
-
${
|
|
10665
|
+
${renderWorkflowPrompt({ cardArgument: "the card reference", agentIdentifier: "cursor", agentName: "Cursor" })}
|
|
10325
10666
|
`;
|
|
10326
10667
|
if (installMode === "global") {
|
|
10327
10668
|
files.push({
|
|
@@ -10366,7 +10707,7 @@ description: Activate when user asks to work on a Harmony card (references like
|
|
|
10366
10707
|
|
|
10367
10708
|
When working on a Harmony card:
|
|
10368
10709
|
|
|
10369
|
-
${
|
|
10710
|
+
${renderWorkflowPrompt({ cardArgument: "the card reference", agentIdentifier: "windsurf", agentName: "Windsurf" })}
|
|
10370
10711
|
`;
|
|
10371
10712
|
if (installMode === "global") {
|
|
10372
10713
|
files.push({
|
|
@@ -10963,6 +11304,7 @@ Specify the workspace with --workspace <id>, or select one below.`);
|
|
|
10963
11304
|
projectId: selectedProjectId ?? null
|
|
10964
11305
|
}, { global: true });
|
|
10965
11306
|
}
|
|
11307
|
+
await offerCommandScan(dirname4(writtenLocalConfigPath), assumeYes);
|
|
10966
11308
|
}
|
|
10967
11309
|
console.log("");
|
|
10968
11310
|
p4.outro(colors.success("Setup complete!"));
|
|
@@ -11010,6 +11352,39 @@ Specify the workspace with --workspace <id>, or select one below.`);
|
|
|
11010
11352
|
}
|
|
11011
11353
|
console.log("");
|
|
11012
11354
|
}
|
|
11355
|
+
var SCAN_ARGV = ["--yes", "@gethmy/agent@latest", "scan-commands"];
|
|
11356
|
+
function scanTip() {
|
|
11357
|
+
console.log(` ${colors.dim("Tip: run")} ${colors.highlight(`npx ${SCAN_ARGV.slice(1).join(" ")}`)} ${colors.dim("to have the daemon prove which build, test and dev commands this repo has, and write them into the pin.")}`);
|
|
11358
|
+
}
|
|
11359
|
+
async function offerCommandScan(repoDir, assumeYes) {
|
|
11360
|
+
if (assumeYes) {
|
|
11361
|
+
scanTip();
|
|
11362
|
+
return;
|
|
11363
|
+
}
|
|
11364
|
+
const scan = await confirmOrDefault(assumeYes, {
|
|
11365
|
+
message: "Scan this repo's commands now? It runs your build, test and dev scripts once to prove which ones exist. Nothing is written.",
|
|
11366
|
+
initialValue: true
|
|
11367
|
+
});
|
|
11368
|
+
if (p4.isCancel(scan)) {
|
|
11369
|
+
p4.cancel("Setup cancelled.");
|
|
11370
|
+
process.exit(0);
|
|
11371
|
+
}
|
|
11372
|
+
if (!scan) {
|
|
11373
|
+
scanTip();
|
|
11374
|
+
return;
|
|
11375
|
+
}
|
|
11376
|
+
console.log(` ${colors.dim("Running")} ${colors.highlight(`npx ${SCAN_ARGV.slice(1).join(" ")}`)}${colors.dim(" …")}`);
|
|
11377
|
+
const result = spawnSync("npx", [...SCAN_ARGV], {
|
|
11378
|
+
cwd: repoDir,
|
|
11379
|
+
stdio: "inherit"
|
|
11380
|
+
});
|
|
11381
|
+
if (result.error || result.status !== 0) {
|
|
11382
|
+
console.log(` ${colors.dim("The scan did not run — your installed @gethmy/agent may predate it.")}`);
|
|
11383
|
+
scanTip();
|
|
11384
|
+
return;
|
|
11385
|
+
}
|
|
11386
|
+
console.log(` ${colors.dim("Re-run it with")} ${colors.highlight("--write")} ${colors.dim("to merge that block into the pin.")}`);
|
|
11387
|
+
}
|
|
11013
11388
|
|
|
11014
11389
|
// src/cli.ts
|
|
11015
11390
|
var require2 = createRequire2(import.meta.url);
|