@gethmy/mcp 2.8.0 → 2.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1121,6 +1121,87 @@ function getDisplayLinkType(linkType, direction) {
1121
1121
  return linkType;
1122
1122
  return LINK_TYPE_INVERSES[linkType];
1123
1123
  }
1124
+ // ../harmony-shared/dist/commentSerializer.js
1125
+ var CONFLICT_INSTRUCTION = "When two comments conflict, prefer the latest created_at, UNLESS a later " + "comment explicitly confirms or restates the earlier finding. Evaluate " + "substance, not just recency. Cite the comment id(s) you relied on.";
1126
+ function authorLabel(c) {
1127
+ if (c.author_type === "agent")
1128
+ return "AI agent";
1129
+ return c.author?.full_name || c.author?.email || "teammate";
1130
+ }
1131
+ function criticalIds(comments) {
1132
+ const keep = new Set;
1133
+ for (const c of comments) {
1134
+ if (c.comment_type === "decision")
1135
+ keep.add(c.id);
1136
+ if (c.supersedes_id) {
1137
+ keep.add(c.id);
1138
+ keep.add(c.supersedes_id);
1139
+ }
1140
+ if (c.confirms_id) {
1141
+ keep.add(c.id);
1142
+ keep.add(c.confirms_id);
1143
+ }
1144
+ }
1145
+ return keep;
1146
+ }
1147
+ function serializeCommentThread(comments, options = {}) {
1148
+ const { heading = "Conversation", includeInstructions = true, activity = [], maxComments } = options;
1149
+ const visible = comments.filter((c) => !c.deleted_at).slice().sort((a, b) => a.created_at.localeCompare(b.created_at));
1150
+ if (visible.length === 0)
1151
+ return "";
1152
+ const indexById = new Map;
1153
+ visible.forEach((c, i) => {
1154
+ indexById.set(c.id, i + 1);
1155
+ });
1156
+ let rendered = visible;
1157
+ let elidedCount = 0;
1158
+ if (maxComments && visible.length > maxComments) {
1159
+ const keep = criticalIds(visible);
1160
+ const recentThreshold = visible.length - maxComments;
1161
+ rendered = visible.filter((c, i) => i >= recentThreshold || keep.has(c.id));
1162
+ elidedCount = visible.length - rendered.length;
1163
+ }
1164
+ const ref = (id) => {
1165
+ const n = indexById.get(id);
1166
+ return n ? `#${n}` : `#${id.slice(0, 8)}`;
1167
+ };
1168
+ const lines = [];
1169
+ if (elidedCount > 0) {
1170
+ lines.push({
1171
+ at: visible[0]?.created_at ?? "",
1172
+ text: `(${elidedCount} earlier comment(s) omitted for brevity)`
1173
+ });
1174
+ }
1175
+ for (const c of rendered) {
1176
+ const tags = [];
1177
+ if (c.edited_at)
1178
+ tags.push("edited");
1179
+ if (c.supersedes_id)
1180
+ tags.push(`supersedes ${ref(c.supersedes_id)}`);
1181
+ if (c.confirms_id)
1182
+ tags.push(`confirms ${ref(c.confirms_id)}`);
1183
+ if (c.resolved_at)
1184
+ tags.push("resolved");
1185
+ const tagStr = tags.length ? ` | ${tags.join(" | ")}` : "";
1186
+ const header = `[${ref(c.id)} | ${c.author_type} | ${authorLabel(c)} | ${c.comment_type} | ${c.created_at}${tagStr}]`;
1187
+ lines.push({ at: c.created_at, text: `${header}
1188
+ ${c.body.trim()}` });
1189
+ }
1190
+ for (const a of activity) {
1191
+ const actor = a.actor ? `${a.actor} ` : "";
1192
+ lines.push({ at: a.at, text: `· (system) ${a.at} — ${actor}${a.text}` });
1193
+ }
1194
+ lines.sort((a, b) => a.at.localeCompare(b.at));
1195
+ const body = lines.map((l) => l.text).join(`
1196
+
1197
+ `);
1198
+ const instruction = includeInstructions ? `
1199
+
1200
+ ${CONFLICT_INSTRUCTION}` : "";
1201
+ return `## ${heading} (oldest → newest)
1202
+
1203
+ ${body}${instruction}`;
1204
+ }
1124
1205
  // ../harmony-shared/dist/constants.js
1125
1206
  var TIMINGS = {
1126
1207
  SEARCH_DEBOUNCE: 300,
@@ -1473,6 +1554,28 @@ class HarmonyApiClient {
1473
1554
  async deleteSubtask(subtaskId) {
1474
1555
  return this.request("DELETE", `/subtasks/${subtaskId}`);
1475
1556
  }
1557
+ async addComment(cardId, body, opts) {
1558
+ return this.request("POST", `/cards/${cardId}/comments`, {
1559
+ body,
1560
+ authorType: "agent",
1561
+ commentType: opts?.commentType,
1562
+ supersedesId: opts?.supersedesId,
1563
+ confirmsId: opts?.confirmsId,
1564
+ agentSessionId: opts?.agentSessionId
1565
+ });
1566
+ }
1567
+ async getComments(cardId, opts) {
1568
+ const qs = new URLSearchParams;
1569
+ if (opts?.limit != null)
1570
+ qs.set("limit", String(opts.limit));
1571
+ if (opts?.offset != null)
1572
+ qs.set("offset", String(opts.offset));
1573
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
1574
+ return this.request("GET", `/cards/${cardId}/comments${suffix}`);
1575
+ }
1576
+ async updateComment(commentId, updates) {
1577
+ return this.request("PATCH", `/comments/${commentId}`, updates);
1578
+ }
1476
1579
  async startAgentSession(cardId, data) {
1477
1580
  return this.request("POST", `/cards/${cardId}/agent-context`, data);
1478
1581
  }
@@ -1830,6 +1933,24 @@ class HarmonyApiClient {
1830
1933
  assembledContext: assembledContextStr,
1831
1934
  assemblyId
1832
1935
  });
1936
+ try {
1937
+ const { comments } = await this.getComments(options.cardId, {
1938
+ limit: 200
1939
+ });
1940
+ if (Array.isArray(comments) && comments.length > 0) {
1941
+ const section = serializeCommentThread(comments, {
1942
+ heading: "Comments",
1943
+ maxComments: 40
1944
+ });
1945
+ if (section)
1946
+ result.prompt = `${result.prompt}
1947
+
1948
+ ${section}`;
1949
+ }
1950
+ } catch (err) {
1951
+ const msg = err instanceof Error ? err.message : String(err);
1952
+ console.debug(`[generateCardPrompt] comments fetch failed: ${msg}`);
1953
+ }
1833
1954
  try {
1834
1955
  await this.recordPromptHistory({
1835
1956
  cardId: cardData.id,
@@ -2378,6 +2499,294 @@ async function onboardNewUser(params) {
2378
2499
  };
2379
2500
  }
2380
2501
 
2502
+ // src/skills.ts
2503
+ import {
2504
+ existsSync as existsSync4,
2505
+ mkdirSync as mkdirSync3,
2506
+ readFileSync as readFileSync4,
2507
+ renameSync,
2508
+ writeFileSync as writeFileSync3
2509
+ } from "node:fs";
2510
+ import { homedir as homedir3 } from "node:os";
2511
+ import { dirname, join as join4 } from "node:path";
2512
+
2513
+ // src/hmy-config.ts
2514
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
2515
+ import { homedir as homedir2 } from "node:os";
2516
+ import { join as join3 } from "node:path";
2517
+ var DEFAULTS = { updateCheck: true, pin: null };
2518
+ function getHmyConfigPath() {
2519
+ return join3(homedir2(), ".hmy", "config.yaml");
2520
+ }
2521
+ function loadHmyConfig() {
2522
+ const path = getHmyConfigPath();
2523
+ if (!existsSync3(path))
2524
+ return { ...DEFAULTS };
2525
+ try {
2526
+ return parseHmyConfig(readFileSync3(path, "utf-8"));
2527
+ } catch {
2528
+ return { ...DEFAULTS };
2529
+ }
2530
+ }
2531
+ function parseHmyConfig(text) {
2532
+ const cfg = { ...DEFAULTS };
2533
+ for (const rawLine of text.split(`
2534
+ `)) {
2535
+ const line = rawLine.trim();
2536
+ if (!line || line.startsWith("#"))
2537
+ continue;
2538
+ const sep2 = line.indexOf(":");
2539
+ if (sep2 === -1)
2540
+ continue;
2541
+ const key = line.slice(0, sep2).trim();
2542
+ let value = line.slice(sep2 + 1).trim();
2543
+ const hash = value.indexOf(" #");
2544
+ if (hash !== -1)
2545
+ value = value.slice(0, hash).trim();
2546
+ value = value.replace(/^["']|["']$/g, "");
2547
+ switch (key) {
2548
+ case "update_check":
2549
+ cfg.updateCheck = value !== "false";
2550
+ break;
2551
+ case "pin":
2552
+ case "pin_version":
2553
+ cfg.pin = value || null;
2554
+ break;
2555
+ }
2556
+ }
2557
+ return cfg;
2558
+ }
2559
+
2560
+ // src/skills.ts
2561
+ var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
2562
+
2563
+ Start work on a Harmony card. Card reference: $ARGUMENTS
2564
+
2565
+ ## 1. Find & Fetch Card
2566
+
2567
+ Parse the reference and fetch the card:
2568
+ - \`#42\` or \`42\` → \`harmony_get_card_by_short_id\` with \`shortId: 42\`
2569
+ - UUID → \`harmony_get_card\` with \`cardId\`
2570
+ - Name/text → \`harmony_search_cards\` with \`query\`
2571
+
2572
+ ## 2. Get Board State
2573
+
2574
+ Call \`harmony_get_board\` to get columns and labels. From the response:
2575
+ - Find the "In Progress" (or "Progress") column ID
2576
+ - Find the "agent" label ID
2577
+
2578
+ ## 3. Setup Card for Work
2579
+
2580
+ Execute these in sequence:
2581
+ 1. \`harmony_move_card\` → Move to "In Progress" column
2582
+ 2. \`harmony_add_label_to_card\` → Add "agent" label
2583
+ 3. \`harmony_start_agent_session\`:
2584
+ - \`cardId\`: Card UUID
2585
+ - \`agentIdentifier\`: Your agent identifier
2586
+ - \`agentName\`: Your agent name
2587
+ - \`currentTask\`: "Analyzing card requirements"
2588
+
2589
+ ## 4. Generate Work Prompt
2590
+
2591
+ Call \`harmony_generate_prompt\` with:
2592
+ - \`cardId\` or \`shortId\` (+ \`projectId\` if using shortId)
2593
+ - \`variant\`: Select based on task:
2594
+ - \`"execute"\` (default) → Clear tasks, bug fixes, well-defined work
2595
+ - \`"analysis"\` → Complex features, unclear requirements
2596
+ - \`"draft"\` → Medium complexity, want feedback first
2597
+
2598
+ The generated prompt provides role framing, focus areas, subtasks, linked cards, and suggested outputs.
2599
+
2600
+ ## 5. Display Card Summary
2601
+
2602
+ Show the user: Card title, short ID, role, priority, labels, due date, description, and subtasks.
2603
+
2604
+ ## 6. Implement Solution
2605
+
2606
+ Work on the card following the generated prompt's guidance. Update progress at milestones:
2607
+ - \`harmony_update_agent_progress\` with \`progressPercent\` (0-100), \`currentTask\`, \`status\`, \`blockers\`
2608
+
2609
+ **Progress checkpoints:** 20% (exploration), 50% (implementation), 80% (testing), 100% (done)
2610
+
2611
+ ## 7. Complete Work
2612
+
2613
+ When finished:
2614
+ 1. \`harmony_end_agent_session\` with \`status: "completed"\`, \`progressPercent: 100\`
2615
+ 2. \`harmony_move_card\` to "Review" column
2616
+ 3. Summarize accomplishments
2617
+
2618
+ If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
2619
+
2620
+ ## Key Tools Reference
2621
+
2622
+ **Cards:** \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\`, \`harmony_create_card\`, \`harmony_update_card\`, \`harmony_move_card\`, \`harmony_delete_card\`, \`harmony_assign_card\`
2623
+
2624
+ **Subtasks:** \`harmony_create_subtask\`, \`harmony_toggle_subtask\`, \`harmony_delete_subtask\`
2625
+
2626
+ **Labels:** \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\`, \`harmony_create_label\`
2627
+
2628
+ **Links:** \`harmony_add_link_to_card\`, \`harmony_remove_link_from_card\`, \`harmony_get_card_links\`
2629
+
2630
+ **Board:** \`harmony_get_board\`, \`harmony_list_projects\`, \`harmony_get_context\`, \`harmony_set_project_context\`
2631
+
2632
+ **Sessions:** \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\`, \`harmony_get_agent_session\`
2633
+
2634
+ **AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
2635
+ `;
2636
+ function buildSkillFile(skill) {
2637
+ const content = stripSkillPreamble(skill.content);
2638
+ if (skill.skillVersion !== undefined && !hasMetadataVersion(content)) {
2639
+ return injectMetadataVersion(content, skill.skillVersion);
2640
+ }
2641
+ return content;
2642
+ }
2643
+ var PREAMBLE_START = "<!-- hmy-skills-preamble:start -->";
2644
+ var PREAMBLE_END = "<!-- hmy-skills-preamble:end -->";
2645
+ function stripSkillPreamble(content) {
2646
+ const start = content.indexOf(PREAMBLE_START);
2647
+ const end = content.indexOf(PREAMBLE_END);
2648
+ if (start === -1 || end === -1 || end < start)
2649
+ return content;
2650
+ const stripped = content.slice(0, start) + content.slice(end + PREAMBLE_END.length);
2651
+ return `${stripped.replace(/\n{3,}/g, `
2652
+
2653
+ `).replace(/\s+$/, "")}
2654
+ `;
2655
+ }
2656
+ function atomicWrite(filePath, content) {
2657
+ const dir = dirname(filePath);
2658
+ if (!existsSync4(dir))
2659
+ mkdirSync3(dir, { recursive: true });
2660
+ const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
2661
+ writeFileSync3(tmp, content);
2662
+ renameSync(tmp, filePath);
2663
+ }
2664
+ function hasMetadataVersion(content) {
2665
+ return parseSkillVersion(content) !== null;
2666
+ }
2667
+ function injectMetadataVersion(content, version) {
2668
+ const fmMatch = content.match(/^(---\n[\s\S]*?\n)(---\n)([\s\S]*)$/);
2669
+ if (!fmMatch)
2670
+ return content;
2671
+ const [, head, close, body] = fmMatch;
2672
+ const block = `metadata:
2673
+ version: "${version}"
2674
+ `;
2675
+ return `${head}${block}${close}${body}`;
2676
+ }
2677
+ function parseSkillVersion(content) {
2678
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
2679
+ if (fmMatch) {
2680
+ const fm = fmMatch[1];
2681
+ const verMatch = fm.match(/^metadata:[\s\S]*?\n[ \t]+version:[ \t]*["']?(\d+)["']?\s*$/m);
2682
+ if (verMatch)
2683
+ return verMatch[1];
2684
+ }
2685
+ const legacy = content.match(/<!-- skills-version:(\d+) -->/);
2686
+ return legacy ? legacy[1] : null;
2687
+ }
2688
+ function findSkillFiles(paths, knownNames) {
2689
+ const results = [];
2690
+ for (const filePath of paths) {
2691
+ if (!existsSync4(filePath))
2692
+ continue;
2693
+ for (const name of knownNames) {
2694
+ if (filePath.includes(`/${name}/`) || filePath.includes(`/${name}.md`)) {
2695
+ results.push({ name, filePath });
2696
+ break;
2697
+ }
2698
+ }
2699
+ }
2700
+ return results;
2701
+ }
2702
+ var HMY_DIR = join4(homedir3(), ".hmy");
2703
+ var HMY_VERSION_FILE = join4(HMY_DIR, "VERSION");
2704
+ var LAST_CHECK_FILE = join4(HMY_DIR, "last-update-check");
2705
+ var CHECK_TTL_MS = 24 * 60 * 60 * 1000;
2706
+ function checkedRecently(now = Date.now()) {
2707
+ try {
2708
+ if (!existsSync4(LAST_CHECK_FILE))
2709
+ return false;
2710
+ const ts = Number.parseInt(readFileSync4(LAST_CHECK_FILE, "utf-8").trim(), 10);
2711
+ if (!Number.isFinite(ts))
2712
+ return false;
2713
+ return now - ts < CHECK_TTL_MS;
2714
+ } catch {
2715
+ return false;
2716
+ }
2717
+ }
2718
+ function recordCheck(now = Date.now()) {
2719
+ try {
2720
+ if (!existsSync4(HMY_DIR))
2721
+ mkdirSync3(HMY_DIR, { recursive: true });
2722
+ writeFileSync3(LAST_CHECK_FILE, String(now));
2723
+ } catch {}
2724
+ }
2725
+ async function refreshSkills(opts = {}) {
2726
+ try {
2727
+ if (!isConfigured())
2728
+ return { updated: false };
2729
+ const cfg = loadHmyConfig();
2730
+ if (!cfg.updateCheck || cfg.pin)
2731
+ return { updated: false };
2732
+ if (!opts.force && checkedRecently())
2733
+ return { updated: false };
2734
+ const status = areSkillsInstalled();
2735
+ if (!status.installed)
2736
+ return { updated: false };
2737
+ const client3 = new HarmonyApiClient;
2738
+ const versionInfo = await client3.fetchSkillsVersion();
2739
+ recordCheck();
2740
+ const skillFiles = findSkillFiles(status.paths, versionInfo.skills);
2741
+ if (skillFiles.length > 0) {
2742
+ const samplePath = skillFiles[0].filePath;
2743
+ for (const name of versionInfo.skills) {
2744
+ if (skillFiles.some((sf) => sf.name === name))
2745
+ continue;
2746
+ let siblingPath;
2747
+ if (samplePath.endsWith("SKILL.md")) {
2748
+ const parentDir = dirname(dirname(samplePath));
2749
+ siblingPath = `${parentDir}/${name}/SKILL.md`;
2750
+ } else {
2751
+ const parentDir = dirname(samplePath);
2752
+ siblingPath = `${parentDir}/${name}.md`;
2753
+ }
2754
+ if (existsSync4(siblingPath)) {
2755
+ skillFiles.push({ name, filePath: siblingPath });
2756
+ }
2757
+ }
2758
+ }
2759
+ if (skillFiles.length === 0)
2760
+ return { updated: false };
2761
+ let updated = false;
2762
+ for (const { name, filePath } of skillFiles) {
2763
+ try {
2764
+ const currentContent = readFileSync4(filePath, "utf-8");
2765
+ const localVersion = parseSkillVersion(currentContent);
2766
+ const fetched = await client3.fetchSkill(name);
2767
+ const remoteVersion = fetched.skillVersion;
2768
+ if (remoteVersion !== undefined && localVersion !== null && Number(localVersion) >= remoteVersion) {
2769
+ continue;
2770
+ }
2771
+ atomicWrite(filePath, buildSkillFile(fetched));
2772
+ updated = true;
2773
+ } catch (err) {
2774
+ const msg = err instanceof Error ? err.message : String(err);
2775
+ console.error(`Harmony: skill "${name}" refresh failed: ${msg}`);
2776
+ }
2777
+ }
2778
+ if (updated) {
2779
+ try {
2780
+ atomicWrite(HMY_VERSION_FILE, versionInfo.version);
2781
+ } catch {}
2782
+ console.error("Harmony: Refreshed skills from server");
2783
+ }
2784
+ return { updated };
2785
+ } catch {
2786
+ return { updated: false };
2787
+ }
2788
+ }
2789
+
2381
2790
  // src/server.ts
2382
2791
  var memorySessions = new Map;
2383
2792
  function parseLabelList(raw) {
@@ -2515,7 +2924,11 @@ var TOOLS = {
2515
2924
  enum: ["low", "medium", "high", "urgent"],
2516
2925
  description: "Priority level"
2517
2926
  },
2518
- assigneeId: { type: "string", description: "Assignee user ID" }
2927
+ assigneeId: { type: "string", description: "Assignee user ID" },
2928
+ planId: {
2929
+ type: "string",
2930
+ description: "Plan ID to link this card to (optional). Links the card to that plan via its plan_id."
2931
+ }
2519
2932
  },
2520
2933
  required: ["title"]
2521
2934
  }
@@ -2840,6 +3253,66 @@ var TOOLS = {
2840
3253
  required: ["subtaskId"]
2841
3254
  }
2842
3255
  },
3256
+ harmony_add_comment: {
3257
+ description: "Post a comment on a card as the agent. Use this to converse with the human in the open: report progress, ask a question, record a decision, or note a finding — instead of editing the card description. Set supersedesId to correct an earlier comment, confirmsId to reaffirm one. When the thread conflicts, prefer the latest comment unless a later one confirms an earlier finding; cite the comment id(s) you relied on.",
3258
+ inputSchema: {
3259
+ type: "object",
3260
+ properties: {
3261
+ cardId: { type: "string", description: "Card UUID to comment on" },
3262
+ body: { type: "string", description: "Comment body (Markdown)" },
3263
+ commentType: {
3264
+ type: "string",
3265
+ enum: [
3266
+ "message",
3267
+ "progress",
3268
+ "question",
3269
+ "blocker",
3270
+ "decision",
3271
+ "summary",
3272
+ "finding"
3273
+ ],
3274
+ description: "Type of comment. 'question'/'blocker' signal you need a human; default 'message'."
3275
+ },
3276
+ supersedesId: {
3277
+ type: "string",
3278
+ description: "Comment id this comment corrects/updates"
3279
+ },
3280
+ confirmsId: {
3281
+ type: "string",
3282
+ description: "Comment id this comment reaffirms"
3283
+ }
3284
+ },
3285
+ required: ["cardId", "body"]
3286
+ }
3287
+ },
3288
+ harmony_get_comments: {
3289
+ description: "Get the comment thread on a card (oldest → newest). Returns human + agent comments with author, type, edited/resolved state, and supersede/confirm links. Read this before acting so you weigh later comments over earlier ones they contradict.",
3290
+ inputSchema: {
3291
+ type: "object",
3292
+ properties: {
3293
+ cardId: { type: "string" },
3294
+ limit: { type: "number" },
3295
+ offset: { type: "number" }
3296
+ },
3297
+ required: ["cardId"]
3298
+ }
3299
+ },
3300
+ harmony_update_comment: {
3301
+ description: "Update one of your own comments: edit the body, pin it, or resolve a question/blocker once it has been answered.",
3302
+ inputSchema: {
3303
+ type: "object",
3304
+ properties: {
3305
+ commentId: { type: "string" },
3306
+ body: { type: "string" },
3307
+ pinned: { type: "boolean" },
3308
+ resolve: {
3309
+ type: "boolean",
3310
+ description: "Mark (true) or clear (false) the resolved state"
3311
+ }
3312
+ },
3313
+ required: ["commentId"]
3314
+ }
3315
+ },
2843
3316
  harmony_list_workspaces: {
2844
3317
  description: "List all workspaces the user has access to",
2845
3318
  inputSchema: {
@@ -3830,7 +4303,7 @@ function registerHandlers(server, deps) {
3830
4303
  {
3831
4304
  uri,
3832
4305
  mimeType: "text/markdown",
3833
- text: fetched.content
4306
+ text: stripSkillPreamble(fetched.content)
3834
4307
  }
3835
4308
  ]
3836
4309
  };
@@ -3880,7 +4353,8 @@ async function handleToolCall(name, args, deps) {
3880
4353
  columnId: args.columnId,
3881
4354
  description: args.description,
3882
4355
  priority: args.priority,
3883
- assigneeId: args.assigneeId
4356
+ assigneeId: args.assigneeId,
4357
+ planId: args.planId
3884
4358
  });
3885
4359
  return { success: true, ...result };
3886
4360
  }
@@ -4100,6 +4574,49 @@ async function handleToolCall(name, args, deps) {
4100
4574
  await client3.deleteSubtask(subtaskId);
4101
4575
  return { success: true };
4102
4576
  }
4577
+ case "harmony_add_comment": {
4578
+ const cardId = z.string().uuid().parse(args.cardId);
4579
+ const body = z.string().min(1).max(1e4).parse(args.body);
4580
+ const commentType = args.commentType !== undefined ? z.enum([
4581
+ "message",
4582
+ "progress",
4583
+ "question",
4584
+ "blocker",
4585
+ "decision",
4586
+ "summary",
4587
+ "finding"
4588
+ ]).parse(args.commentType) : undefined;
4589
+ const supersedesId = args.supersedesId !== undefined ? z.string().uuid().parse(args.supersedesId) : undefined;
4590
+ const confirmsId = args.confirmsId !== undefined ? z.string().uuid().parse(args.confirmsId) : undefined;
4591
+ const result = await client3.addComment(cardId, body, {
4592
+ commentType,
4593
+ supersedesId,
4594
+ confirmsId
4595
+ });
4596
+ return { success: true, ...result };
4597
+ }
4598
+ case "harmony_get_comments": {
4599
+ const cardId = z.string().uuid().parse(args.cardId);
4600
+ const limit = args.limit !== undefined ? z.number().int().min(1).max(500).parse(args.limit) : undefined;
4601
+ const offset = args.offset !== undefined ? z.number().int().min(0).parse(args.offset) : undefined;
4602
+ const result = await client3.getComments(cardId, { limit, offset });
4603
+ return { success: true, ...result };
4604
+ }
4605
+ case "harmony_update_comment": {
4606
+ const commentId = z.string().uuid().parse(args.commentId);
4607
+ const updates = {};
4608
+ if (args.body !== undefined) {
4609
+ updates.body = z.string().min(1).max(1e4).parse(args.body);
4610
+ }
4611
+ if (args.pinned !== undefined) {
4612
+ updates.pinned = z.boolean().parse(args.pinned);
4613
+ }
4614
+ if (args.resolve !== undefined) {
4615
+ updates.resolve = z.boolean().parse(args.resolve);
4616
+ }
4617
+ const result = await client3.updateComment(commentId, updates);
4618
+ return { success: true, ...result };
4619
+ }
4103
4620
  case "harmony_list_workspaces": {
4104
4621
  const result = await client3.listWorkspaces();
4105
4622
  return { success: true, ...result };
@@ -4906,13 +5423,16 @@ function createConfigDeps() {
4906
5423
  class HarmonyMCPServer {
4907
5424
  server;
4908
5425
  constructor() {
4909
- this.server = new Server({ name: "@gethmy/mcp", version: "2.0.0" }, { capabilities: { tools: {}, resources: {} } });
5426
+ this.server = new Server({ name: "@gethmy/mcp", version: "2.0.0" }, { capabilities: { tools: {}, resources: { listChanged: true } } });
4910
5427
  registerHandlers(this.server, createConfigDeps());
4911
5428
  }
4912
- async run() {
5429
+ async run(opts = {}) {
4913
5430
  const transport = new StdioServerTransport;
4914
5431
  await this.server.connect(transport);
4915
5432
  console.error("Harmony MCP server running on stdio");
5433
+ if (opts.skillsUpdated) {
5434
+ this.server.sendResourceListChanged().catch(() => {});
5435
+ }
4916
5436
  const configDeps = createConfigDeps();
4917
5437
  initAutoSession(async (client3, cardId, status) => {
4918
5438
  await runEndSessionPipeline(client3, configDeps, cardId, status);
@@ -4959,209 +5479,30 @@ class HarmonyMCPServer {
4959
5479
  }
4960
5480
  }
4961
5481
 
4962
- // src/skills.ts
4963
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
4964
- import { dirname } from "node:path";
4965
- var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
4966
-
4967
- Start work on a Harmony card. Card reference: $ARGUMENTS
4968
-
4969
- ## 1. Find & Fetch Card
4970
-
4971
- Parse the reference and fetch the card:
4972
- - \`#42\` or \`42\` → \`harmony_get_card_by_short_id\` with \`shortId: 42\`
4973
- - UUID → \`harmony_get_card\` with \`cardId\`
4974
- - Name/text → \`harmony_search_cards\` with \`query\`
4975
-
4976
- ## 2. Get Board State
4977
-
4978
- Call \`harmony_get_board\` to get columns and labels. From the response:
4979
- - Find the "In Progress" (or "Progress") column ID
4980
- - Find the "agent" label ID
4981
-
4982
- ## 3. Setup Card for Work
4983
-
4984
- Execute these in sequence:
4985
- 1. \`harmony_move_card\` → Move to "In Progress" column
4986
- 2. \`harmony_add_label_to_card\` → Add "agent" label
4987
- 3. \`harmony_start_agent_session\`:
4988
- - \`cardId\`: Card UUID
4989
- - \`agentIdentifier\`: Your agent identifier
4990
- - \`agentName\`: Your agent name
4991
- - \`currentTask\`: "Analyzing card requirements"
4992
-
4993
- ## 4. Generate Work Prompt
4994
-
4995
- Call \`harmony_generate_prompt\` with:
4996
- - \`cardId\` or \`shortId\` (+ \`projectId\` if using shortId)
4997
- - \`variant\`: Select based on task:
4998
- - \`"execute"\` (default) → Clear tasks, bug fixes, well-defined work
4999
- - \`"analysis"\` → Complex features, unclear requirements
5000
- - \`"draft"\` → Medium complexity, want feedback first
5001
-
5002
- The generated prompt provides role framing, focus areas, subtasks, linked cards, and suggested outputs.
5003
-
5004
- ## 5. Display Card Summary
5005
-
5006
- Show the user: Card title, short ID, role, priority, labels, due date, description, and subtasks.
5007
-
5008
- ## 6. Implement Solution
5009
-
5010
- Work on the card following the generated prompt's guidance. Update progress at milestones:
5011
- - \`harmony_update_agent_progress\` with \`progressPercent\` (0-100), \`currentTask\`, \`status\`, \`blockers\`
5012
-
5013
- **Progress checkpoints:** 20% (exploration), 50% (implementation), 80% (testing), 100% (done)
5014
-
5015
- ## 7. Complete Work
5016
-
5017
- When finished:
5018
- 1. \`harmony_end_agent_session\` with \`status: "completed"\`, \`progressPercent: 100\`
5019
- 2. \`harmony_move_card\` to "Review" column
5020
- 3. Summarize accomplishments
5021
-
5022
- If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
5023
-
5024
- ## Key Tools Reference
5025
-
5026
- **Cards:** \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\`, \`harmony_create_card\`, \`harmony_update_card\`, \`harmony_move_card\`, \`harmony_delete_card\`, \`harmony_assign_card\`
5027
-
5028
- **Subtasks:** \`harmony_create_subtask\`, \`harmony_toggle_subtask\`, \`harmony_delete_subtask\`
5029
-
5030
- **Labels:** \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\`, \`harmony_create_label\`
5031
-
5032
- **Links:** \`harmony_add_link_to_card\`, \`harmony_remove_link_from_card\`, \`harmony_get_card_links\`
5033
-
5034
- **Board:** \`harmony_get_board\`, \`harmony_list_projects\`, \`harmony_get_context\`, \`harmony_set_project_context\`
5035
-
5036
- **Sessions:** \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\`, \`harmony_get_agent_session\`
5037
-
5038
- **AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
5039
- `;
5040
- function buildSkillFile(skill) {
5041
- if (skill.skillVersion !== undefined && !hasMetadataVersion(skill.content)) {
5042
- return injectMetadataVersion(skill.content, skill.skillVersion);
5043
- }
5044
- return skill.content;
5045
- }
5046
- function hasMetadataVersion(content) {
5047
- return parseSkillVersion(content) !== null;
5048
- }
5049
- function injectMetadataVersion(content, version) {
5050
- const fmMatch = content.match(/^(---\n[\s\S]*?\n)(---\n)([\s\S]*)$/);
5051
- if (!fmMatch)
5052
- return content;
5053
- const [, head, close, body] = fmMatch;
5054
- const block = `metadata:
5055
- version: "${version}"
5056
- `;
5057
- return `${head}${block}${close}${body}`;
5058
- }
5059
- function parseSkillVersion(content) {
5060
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
5061
- if (fmMatch) {
5062
- const fm = fmMatch[1];
5063
- const verMatch = fm.match(/^metadata:[\s\S]*?\n[ \t]+version:[ \t]*["']?(\d+)["']?\s*$/m);
5064
- if (verMatch)
5065
- return verMatch[1];
5066
- }
5067
- const legacy = content.match(/<!-- skills-version:(\d+) -->/);
5068
- return legacy ? legacy[1] : null;
5069
- }
5070
- function findSkillFiles(paths, knownNames) {
5071
- const results = [];
5072
- for (const filePath of paths) {
5073
- if (!existsSync3(filePath))
5074
- continue;
5075
- for (const name of knownNames) {
5076
- if (filePath.includes(`/${name}/`) || filePath.includes(`/${name}.md`)) {
5077
- results.push({ name, filePath });
5078
- break;
5079
- }
5080
- }
5081
- }
5082
- return results;
5083
- }
5084
- async function refreshSkills() {
5085
- try {
5086
- if (!isConfigured())
5087
- return;
5088
- const status = areSkillsInstalled();
5089
- if (!status.installed)
5090
- return;
5091
- const client3 = new HarmonyApiClient;
5092
- const versionInfo = await client3.fetchSkillsVersion();
5093
- const skillFiles = findSkillFiles(status.paths, versionInfo.skills);
5094
- if (skillFiles.length > 0) {
5095
- const samplePath = skillFiles[0].filePath;
5096
- for (const name of versionInfo.skills) {
5097
- if (skillFiles.some((sf) => sf.name === name))
5098
- continue;
5099
- let siblingPath;
5100
- if (samplePath.endsWith("SKILL.md")) {
5101
- const parentDir = dirname(dirname(samplePath));
5102
- siblingPath = `${parentDir}/${name}/SKILL.md`;
5103
- } else {
5104
- const parentDir = dirname(samplePath);
5105
- siblingPath = `${parentDir}/${name}.md`;
5106
- }
5107
- if (existsSync3(siblingPath)) {
5108
- skillFiles.push({ name, filePath: siblingPath });
5109
- }
5110
- }
5111
- }
5112
- if (skillFiles.length === 0)
5113
- return;
5114
- let updated = false;
5115
- for (const { name, filePath } of skillFiles) {
5116
- try {
5117
- const currentContent = readFileSync3(filePath, "utf-8");
5118
- const localVersion = parseSkillVersion(currentContent);
5119
- const fetched = await client3.fetchSkill(name);
5120
- const remoteVersion = fetched.skillVersion;
5121
- if (remoteVersion !== undefined && localVersion !== null && Number(localVersion) >= remoteVersion) {
5122
- continue;
5123
- }
5124
- const newContent = buildSkillFile(fetched);
5125
- const dir = dirname(filePath);
5126
- if (!existsSync3(dir))
5127
- mkdirSync3(dir, { recursive: true });
5128
- writeFileSync3(filePath, newContent);
5129
- updated = true;
5130
- } catch (err) {
5131
- const msg = err instanceof Error ? err.message : String(err);
5132
- console.error(`Harmony: skill "${name}" refresh failed: ${msg}`);
5133
- }
5134
- }
5135
- if (updated) {
5136
- console.error("Harmony: Refreshed skills from server");
5137
- }
5138
- } catch {}
5139
- }
5140
-
5141
5482
  // src/tui/setup.ts
5142
5483
  import { createHash as createHash3 } from "node:crypto";
5143
5484
  import {
5144
- existsSync as existsSync7,
5485
+ existsSync as existsSync8,
5145
5486
  lstatSync,
5146
5487
  mkdirSync as mkdirSync5,
5147
5488
  symlinkSync,
5148
5489
  unlinkSync
5149
5490
  } from "node:fs";
5150
- import { homedir as homedir4 } from "node:os";
5151
- import { dirname as dirname3, join as join5 } from "node:path";
5491
+ import { homedir as homedir6 } from "node:os";
5492
+ import { dirname as dirname3, join as join7 } from "node:path";
5152
5493
  import * as p3 from "@clack/prompts";
5153
5494
 
5154
5495
  // src/tui/agents.ts
5155
- import { existsSync as existsSync4 } from "node:fs";
5156
- import { homedir as homedir2 } from "node:os";
5157
- import { join as join3 } from "node:path";
5496
+ import { existsSync as existsSync5 } from "node:fs";
5497
+ import { homedir as homedir4 } from "node:os";
5498
+ import { join as join5 } from "node:path";
5158
5499
  var AGENT_DEFINITIONS = [
5159
5500
  {
5160
5501
  id: "claude",
5161
5502
  name: "Claude Code",
5162
5503
  description: "Anthropic CLI agent",
5163
5504
  hint: "/hmy <card>",
5164
- globalPaths: [join3(homedir2(), ".claude")],
5505
+ globalPaths: [join5(homedir4(), ".claude")],
5165
5506
  localPaths: [".claude"]
5166
5507
  },
5167
5508
  {
@@ -5169,7 +5510,7 @@ var AGENT_DEFINITIONS = [
5169
5510
  name: "Codex",
5170
5511
  description: "OpenAI coding agent",
5171
5512
  hint: "/prompts:hmy <card>",
5172
- globalPaths: [join3(homedir2(), ".codex")],
5513
+ globalPaths: [join5(homedir4(), ".codex")],
5173
5514
  localPaths: ["AGENTS.md"]
5174
5515
  },
5175
5516
  {
@@ -5185,20 +5526,20 @@ var AGENT_DEFINITIONS = [
5185
5526
  name: "Windsurf",
5186
5527
  description: "Codeium AI IDE",
5187
5528
  hint: "MCP tools available automatically",
5188
- globalPaths: [join3(homedir2(), ".codeium", "windsurf")],
5529
+ globalPaths: [join5(homedir4(), ".codeium", "windsurf")],
5189
5530
  localPaths: [".windsurf", ".windsurfrules"]
5190
5531
  }
5191
5532
  ];
5192
5533
  function detectAgents(cwd = process.cwd()) {
5193
5534
  return AGENT_DEFINITIONS.map((def) => {
5194
- const globalPath = def.globalPaths.find((p) => existsSync4(p)) || null;
5195
- const localPath = def.localPaths.find((p) => existsSync4(join3(cwd, p))) || null;
5535
+ const globalPath = def.globalPaths.find((p) => existsSync5(p)) || null;
5536
+ const localPath = def.localPaths.find((p) => existsSync5(join5(cwd, p))) || null;
5196
5537
  return {
5197
5538
  id: def.id,
5198
5539
  name: def.name,
5199
5540
  detected: !!(globalPath || localPath),
5200
5541
  globalPath,
5201
- localPath: localPath ? join3(cwd, localPath) : null,
5542
+ localPath: localPath ? join5(cwd, localPath) : null,
5202
5543
  description: def.description,
5203
5544
  hint: def.hint
5204
5545
  };
@@ -5206,8 +5547,8 @@ function detectAgents(cwd = process.cwd()) {
5206
5547
  }
5207
5548
 
5208
5549
  // src/tui/docs.ts
5209
- import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync } from "node:fs";
5210
- import { isAbsolute, join as join4, resolve, sep as sep2 } from "node:path";
5550
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync } from "node:fs";
5551
+ import { isAbsolute, join as join6, resolve, sep as sep2 } from "node:path";
5211
5552
  import * as p from "@clack/prompts";
5212
5553
 
5213
5554
  // src/tui/theme.ts
@@ -5294,14 +5635,14 @@ var IGNORED_DIRS = new Set([
5294
5635
  ]);
5295
5636
  function readJson(filePath) {
5296
5637
  try {
5297
- return JSON.parse(readFileSync4(filePath, "utf-8"));
5638
+ return JSON.parse(readFileSync5(filePath, "utf-8"));
5298
5639
  } catch {
5299
5640
  return null;
5300
5641
  }
5301
5642
  }
5302
5643
  function readText(filePath) {
5303
5644
  try {
5304
- return readFileSync4(filePath, "utf-8");
5645
+ return readFileSync5(filePath, "utf-8");
5305
5646
  } catch {
5306
5647
  return null;
5307
5648
  }
@@ -5312,7 +5653,7 @@ function listDirs(dirPath) {
5312
5653
  if (IGNORED_DIRS.has(entry) || entry.startsWith("."))
5313
5654
  return false;
5314
5655
  try {
5315
- return statSync(join4(dirPath, entry)).isDirectory();
5656
+ return statSync(join6(dirPath, entry)).isDirectory();
5316
5657
  } catch {
5317
5658
  return false;
5318
5659
  }
@@ -5360,25 +5701,25 @@ function describeDir(name) {
5360
5701
  }
5361
5702
  function scanProject(cwd) {
5362
5703
  let packageManager = null;
5363
- if (existsSync5(join4(cwd, "bun.lock")) || existsSync5(join4(cwd, "bun.lockb"))) {
5704
+ if (existsSync6(join6(cwd, "bun.lock")) || existsSync6(join6(cwd, "bun.lockb"))) {
5364
5705
  packageManager = "bun";
5365
- } else if (existsSync5(join4(cwd, "pnpm-lock.yaml"))) {
5706
+ } else if (existsSync6(join6(cwd, "pnpm-lock.yaml"))) {
5366
5707
  packageManager = "pnpm";
5367
- } else if (existsSync5(join4(cwd, "yarn.lock"))) {
5708
+ } else if (existsSync6(join6(cwd, "yarn.lock"))) {
5368
5709
  packageManager = "yarn";
5369
- } else if (existsSync5(join4(cwd, "package.json"))) {
5710
+ } else if (existsSync6(join6(cwd, "package.json"))) {
5370
5711
  packageManager = "npm";
5371
5712
  }
5372
- const pkg = readJson(join4(cwd, "package.json"));
5713
+ const pkg = readJson(join6(cwd, "package.json"));
5373
5714
  const scripts = pkg && typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {};
5374
5715
  let language = "unknown";
5375
- if (existsSync5(join4(cwd, "tsconfig.json"))) {
5716
+ if (existsSync6(join6(cwd, "tsconfig.json"))) {
5376
5717
  language = "typescript";
5377
- } else if (existsSync5(join4(cwd, "go.mod"))) {
5718
+ } else if (existsSync6(join6(cwd, "go.mod"))) {
5378
5719
  language = "go";
5379
- } else if (existsSync5(join4(cwd, "Cargo.toml"))) {
5720
+ } else if (existsSync6(join6(cwd, "Cargo.toml"))) {
5380
5721
  language = "rust";
5381
- } else if (existsSync5(join4(cwd, "setup.py")) || existsSync5(join4(cwd, "pyproject.toml"))) {
5722
+ } else if (existsSync6(join6(cwd, "setup.py")) || existsSync6(join6(cwd, "pyproject.toml"))) {
5382
5723
  language = "python";
5383
5724
  } else if (pkg) {
5384
5725
  language = "javascript";
@@ -5414,7 +5755,7 @@ function scanProject(cwd) {
5414
5755
  }
5415
5756
  }
5416
5757
  let linter = null;
5417
- if (existsSync5(join4(cwd, "biome.json")) || existsSync5(join4(cwd, "biome.jsonc"))) {
5758
+ if (existsSync6(join6(cwd, "biome.json")) || existsSync6(join6(cwd, "biome.jsonc"))) {
5418
5759
  linter = "biome";
5419
5760
  } else {
5420
5761
  const eslintFiles = [
@@ -5429,7 +5770,7 @@ function scanProject(cwd) {
5429
5770
  "eslint.config.cjs",
5430
5771
  "eslint.config.ts"
5431
5772
  ];
5432
- if (eslintFiles.some((f) => existsSync5(join4(cwd, f)))) {
5773
+ if (eslintFiles.some((f) => existsSync6(join6(cwd, f)))) {
5433
5774
  linter = "eslint";
5434
5775
  } else {
5435
5776
  const prettierFiles = [
@@ -5441,13 +5782,13 @@ function scanProject(cwd) {
5441
5782
  "prettier.config.js",
5442
5783
  "prettier.config.mjs"
5443
5784
  ];
5444
- if (prettierFiles.some((f) => existsSync5(join4(cwd, f)))) {
5785
+ if (prettierFiles.some((f) => existsSync6(join6(cwd, f)))) {
5445
5786
  linter = "prettier";
5446
5787
  }
5447
5788
  }
5448
5789
  }
5449
5790
  let indentStyle = null;
5450
- const biome = readJson(join4(cwd, "biome.json")) ?? readJson(join4(cwd, "biome.jsonc"));
5791
+ const biome = readJson(join6(cwd, "biome.json")) ?? readJson(join6(cwd, "biome.jsonc"));
5451
5792
  if (biome) {
5452
5793
  const formatter = biome.formatter;
5453
5794
  if (formatter) {
@@ -5457,7 +5798,7 @@ function scanProject(cwd) {
5457
5798
  }
5458
5799
  }
5459
5800
  if (!indentStyle) {
5460
- const editorConfig = readText(join4(cwd, ".editorconfig"));
5801
+ const editorConfig = readText(join6(cwd, ".editorconfig"));
5461
5802
  if (editorConfig) {
5462
5803
  const styleMatch = editorConfig.match(/indent_style\s*=\s*(space|tab)/);
5463
5804
  const sizeMatch = editorConfig.match(/indent_size\s*=\s*(\d+)/);
@@ -5470,13 +5811,13 @@ function scanProject(cwd) {
5470
5811
  }
5471
5812
  }
5472
5813
  const dirs = listDirs(cwd);
5473
- const srcDirs = existsSync5(join4(cwd, "src")) ? listDirs(join4(cwd, "src")) : [];
5474
- const monorepo = existsSync5(join4(cwd, "packages")) || existsSync5(join4(cwd, "apps"));
5814
+ const srcDirs = existsSync6(join6(cwd, "src")) ? listDirs(join6(cwd, "src")) : [];
5815
+ const monorepo = existsSync6(join6(cwd, "packages")) || existsSync6(join6(cwd, "apps"));
5475
5816
  const existingDocs = {
5476
- agentsMd: existsSync5(join4(cwd, "AGENTS.md")),
5477
- claudeMd: existsSync5(join4(cwd, "CLAUDE.md")),
5478
- docsDir: existsSync5(join4(cwd, "docs")),
5479
- architectureMd: existsSync5(join4(cwd, "docs", "architecture.md"))
5817
+ agentsMd: existsSync6(join6(cwd, "AGENTS.md")),
5818
+ claudeMd: existsSync6(join6(cwd, "CLAUDE.md")),
5819
+ docsDir: existsSync6(join6(cwd, "docs")),
5820
+ architectureMd: existsSync6(join6(cwd, "docs", "architecture.md"))
5480
5821
  };
5481
5822
  return {
5482
5823
  packageManager,
@@ -5627,9 +5968,9 @@ var VAGUE_STANDARDS = [
5627
5968
  ];
5628
5969
  function verifyDocs(cwd) {
5629
5970
  const issues = [];
5630
- const claudeMd = readText(join4(cwd, "CLAUDE.md"));
5631
- const agentsMd = readText(join4(cwd, "AGENTS.md"));
5632
- const pkg = readJson(join4(cwd, "package.json"));
5971
+ const claudeMd = readText(join6(cwd, "CLAUDE.md"));
5972
+ const agentsMd = readText(join6(cwd, "AGENTS.md"));
5973
+ const pkg = readJson(join6(cwd, "package.json"));
5633
5974
  const pkgScripts = pkg && typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {};
5634
5975
  const projectRoot = resolve(cwd);
5635
5976
  if (claudeMd) {
@@ -5659,7 +6000,7 @@ function verifyDocs(cwd) {
5659
6000
  continue;
5660
6001
  }
5661
6002
  importedFiles.push({ ref: refPath, resolved: resolvedPath });
5662
- if (!existsSync5(resolvedPath)) {
6003
+ if (!existsSync6(resolvedPath)) {
5663
6004
  issues.push({
5664
6005
  severity: "error",
5665
6006
  file: "CLAUDE.md",
@@ -5795,7 +6136,7 @@ function verifyDocs(cwd) {
5795
6136
  }
5796
6137
  checkBacktickPaths(agentsMd, "AGENTS.md", cwd, issues);
5797
6138
  }
5798
- const archMd = readText(join4(cwd, "docs", "architecture.md"));
6139
+ const archMd = readText(join6(cwd, "docs", "architecture.md"));
5799
6140
  if (archMd) {
5800
6141
  checkBacktickPaths(archMd, "docs/architecture.md", cwd, issues);
5801
6142
  }
@@ -5842,7 +6183,7 @@ function checkBacktickPaths(content, file, cwd, issues) {
5842
6183
  const resolvedRef = resolve(root, refPath);
5843
6184
  if (resolvedRef !== root && !resolvedRef.startsWith(root + sep2))
5844
6185
  continue;
5845
- if (!existsSync5(resolvedRef)) {
6186
+ if (!existsSync6(resolvedRef)) {
5846
6187
  issues.push({
5847
6188
  severity: "warning",
5848
6189
  file,
@@ -5865,18 +6206,18 @@ async function runDocsStep(cwd) {
5865
6206
  }
5866
6207
  const files = [];
5867
6208
  files.push({
5868
- path: join4(cwd, "AGENTS.md"),
6209
+ path: join6(cwd, "AGENTS.md"),
5869
6210
  content: generateAgentsMd(info, cwd),
5870
6211
  type: "text"
5871
6212
  });
5872
6213
  files.push({
5873
- path: join4(cwd, "CLAUDE.md"),
6214
+ path: join6(cwd, "CLAUDE.md"),
5874
6215
  content: generateClaudeMd(info),
5875
6216
  type: "text"
5876
6217
  });
5877
6218
  if (info.dirs.includes("docs") || info.srcDirs.length > 0) {
5878
6219
  files.push({
5879
- path: join4(cwd, "docs", "architecture.md"),
6220
+ path: join6(cwd, "docs", "architecture.md"),
5880
6221
  content: generateArchitectureMd(info, cwd),
5881
6222
  type: "text"
5882
6223
  });
@@ -5914,21 +6255,21 @@ async function runDocsStep(cwd) {
5914
6255
  // src/tui/writer.ts
5915
6256
  import {
5916
6257
  chmodSync,
5917
- existsSync as existsSync6,
6258
+ existsSync as existsSync7,
5918
6259
  mkdirSync as mkdirSync4,
5919
- readFileSync as readFileSync5,
6260
+ readFileSync as readFileSync6,
5920
6261
  writeFileSync as writeFileSync4
5921
6262
  } from "node:fs";
5922
- import { homedir as homedir3 } from "node:os";
6263
+ import { homedir as homedir5 } from "node:os";
5923
6264
  import { dirname as dirname2 } from "node:path";
5924
6265
  import * as p2 from "@clack/prompts";
5925
6266
  function ensureDir(dirPath) {
5926
- if (!existsSync6(dirPath)) {
6267
+ if (!existsSync7(dirPath)) {
5927
6268
  mkdirSync4(dirPath, { recursive: true, mode: 493 });
5928
6269
  }
5929
6270
  }
5930
6271
  function writeFile(filePath, content, options = {}) {
5931
- const exists = existsSync6(filePath);
6272
+ const exists = existsSync7(filePath);
5932
6273
  if (exists && !options.force) {
5933
6274
  return { path: filePath, action: "skip" };
5934
6275
  }
@@ -5950,7 +6291,7 @@ function writeFile(filePath, content, options = {}) {
5950
6291
  }
5951
6292
  }
5952
6293
  function mergeJsonFile(filePath, updates, options = {}) {
5953
- const exists = existsSync6(filePath);
6294
+ const exists = existsSync7(filePath);
5954
6295
  if (!exists) {
5955
6296
  try {
5956
6297
  ensureDir(dirname2(filePath));
@@ -5967,7 +6308,7 @@ function mergeJsonFile(filePath, updates, options = {}) {
5967
6308
  }
5968
6309
  }
5969
6310
  try {
5970
- const existing = JSON.parse(readFileSync5(filePath, "utf-8"));
6311
+ const existing = JSON.parse(readFileSync6(filePath, "utf-8"));
5971
6312
  if (updates.mcpServers && existing.mcpServers) {
5972
6313
  const existingServers = existing.mcpServers;
5973
6314
  const updateServers = updates.mcpServers;
@@ -6000,7 +6341,7 @@ function mergeJsonFile(filePath, updates, options = {}) {
6000
6341
  }
6001
6342
  }
6002
6343
  function appendToToml(filePath, section, content, options = {}) {
6003
- const exists = existsSync6(filePath);
6344
+ const exists = existsSync7(filePath);
6004
6345
  if (!exists) {
6005
6346
  try {
6006
6347
  ensureDir(dirname2(filePath));
@@ -6015,7 +6356,7 @@ function appendToToml(filePath, section, content, options = {}) {
6015
6356
  }
6016
6357
  }
6017
6358
  try {
6018
- const existing = readFileSync5(filePath, "utf-8");
6359
+ const existing = readFileSync6(filePath, "utf-8");
6019
6360
  if (existing.includes(section)) {
6020
6361
  if (options.force) {
6021
6362
  const updated = existing.replace(new RegExp(`\\[${section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\][\\s\\S]*?(?=\\[|$)`), content.trim() + `
@@ -6039,7 +6380,7 @@ function appendToToml(filePath, section, content, options = {}) {
6039
6380
  }
6040
6381
  async function writeFilesWithProgress(files, options = {}) {
6041
6382
  const results = [];
6042
- const home = homedir3();
6383
+ const home = homedir5();
6043
6384
  const spinner2 = p2.spinner();
6044
6385
  spinner2.start("Writing configuration files...");
6045
6386
  for (const file of files) {
@@ -6076,10 +6417,10 @@ function getWriteSummary(files, options = {}) {
6076
6417
  const toCreate = [];
6077
6418
  const toUpdate = [];
6078
6419
  const toSkip = [];
6079
- const home = homedir3();
6420
+ const home = homedir5();
6080
6421
  for (const file of files) {
6081
6422
  const displayPath = formatPath(file.path, home);
6082
- const exists = existsSync6(file.path);
6423
+ const exists = existsSync7(file.path);
6083
6424
  if (exists && !options.force) {
6084
6425
  toSkip.push(displayPath);
6085
6426
  } else if (exists) {
@@ -6092,7 +6433,7 @@ function getWriteSummary(files, options = {}) {
6092
6433
  }
6093
6434
 
6094
6435
  // src/tui/setup.ts
6095
- var GLOBAL_SKILLS_DIR = join5(homedir4(), ".agents", "skills");
6436
+ var GLOBAL_SKILLS_DIR = join7(homedir6(), ".agents", "skills");
6096
6437
  var API_URL = "https://app.gethmy.com/api";
6097
6438
  async function registerMcpServer() {
6098
6439
  try {
@@ -6106,16 +6447,16 @@ async function registerMcpServer() {
6106
6447
  }
6107
6448
  }
6108
6449
  async function writeMcpConfigFallback(home) {
6109
- const { readFileSync: readFileSync6, writeFileSync: writeFileSync5, mkdirSync: mkdirSync6, existsSync: existsSync8 } = await import("node:fs");
6110
- const settingsPath = join5(home, ".claude", "settings.json");
6450
+ const { readFileSync: readFileSync7, writeFileSync: writeFileSync5, mkdirSync: mkdirSync6, existsSync: existsSync9 } = await import("node:fs");
6451
+ const settingsPath = join7(home, ".claude", "settings.json");
6111
6452
  const settingsDir = dirname3(settingsPath);
6112
- if (!existsSync8(settingsDir)) {
6453
+ if (!existsSync9(settingsDir)) {
6113
6454
  mkdirSync6(settingsDir, { recursive: true });
6114
6455
  }
6115
6456
  let settings = {};
6116
- if (existsSync8(settingsPath)) {
6457
+ if (existsSync9(settingsPath)) {
6117
6458
  try {
6118
- settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
6459
+ settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
6119
6460
  } catch {}
6120
6461
  }
6121
6462
  const mcpServers = settings.mcpServers || {};
@@ -6206,7 +6547,7 @@ async function resolveProjectSlug(apiKey, slug) {
6206
6547
  return { workspaceId: data.workspaceId, projectId: data.projectId };
6207
6548
  }
6208
6549
  async function getAgentFiles(agentId, cwd, installMode = "global") {
6209
- const home = homedir4();
6550
+ const home = homedir6();
6210
6551
  const files = [];
6211
6552
  const symlinks = [];
6212
6553
  switch (agentId) {
@@ -6221,17 +6562,17 @@ async function getAgentFiles(agentId, cwd, installMode = "global") {
6221
6562
  const content = buildSkillFile(fetched);
6222
6563
  if (installMode === "global") {
6223
6564
  files.push({
6224
- path: join5(GLOBAL_SKILLS_DIR, name, "SKILL.md"),
6565
+ path: join7(GLOBAL_SKILLS_DIR, name, "SKILL.md"),
6225
6566
  content,
6226
6567
  type: "text"
6227
6568
  });
6228
6569
  symlinks.push({
6229
- target: join5(GLOBAL_SKILLS_DIR, name),
6230
- link: join5(home, ".claude", "skills", name)
6570
+ target: join7(GLOBAL_SKILLS_DIR, name),
6571
+ link: join7(home, ".claude", "skills", name)
6231
6572
  });
6232
6573
  } else {
6233
6574
  files.push({
6234
- path: join5(cwd, ".claude", "skills", name, "SKILL.md"),
6575
+ path: join7(cwd, ".claude", "skills", name, "SKILL.md"),
6235
6576
  content,
6236
6577
  type: "text"
6237
6578
  });
@@ -6254,13 +6595,13 @@ ${summary}`);
6254
6595
  throw new Error(`hmy-update-check integrity check failed: expected ${updateCheckFetched.sha256}, got ${actualHash}`);
6255
6596
  }
6256
6597
  files.push({
6257
- path: join5(home, ".hmy", "bin", "hmy-update-check"),
6598
+ path: join7(home, ".hmy", "bin", "hmy-update-check"),
6258
6599
  content: updateCheckFetched.content,
6259
6600
  type: "text",
6260
6601
  mode: 493
6261
6602
  });
6262
6603
  files.push({
6263
- path: join5(home, ".hmy", "VERSION"),
6604
+ path: join7(home, ".hmy", "VERSION"),
6264
6605
  content: versionInfo.version,
6265
6606
  type: "text"
6266
6607
  });
@@ -6306,7 +6647,7 @@ Skip if: work was already started with a card reference, or no matching card exi
6306
6647
  - \`harmony_generate_prompt\` - Get role-based guidance and focus areas for the card
6307
6648
  `;
6308
6649
  files.push({
6309
- path: join5(cwd, "AGENTS.md"),
6650
+ path: join7(cwd, "AGENTS.md"),
6310
6651
  content: agentsContent,
6311
6652
  type: "text"
6312
6653
  });
@@ -6323,17 +6664,17 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "{{card}}").replace("Your agent
6323
6664
  `;
6324
6665
  if (installMode === "global") {
6325
6666
  files.push({
6326
- path: join5(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
6667
+ path: join7(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
6327
6668
  content: promptContent,
6328
6669
  type: "text"
6329
6670
  });
6330
6671
  symlinks.push({
6331
- target: join5(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
6332
- link: join5(home, ".codex", "prompts", "hmy.md")
6672
+ target: join7(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
6673
+ link: join7(home, ".codex", "prompts", "hmy.md")
6333
6674
  });
6334
6675
  } else {
6335
6676
  files.push({
6336
- path: join5(home, ".codex", "prompts", "hmy.md"),
6677
+ path: join7(home, ".codex", "prompts", "hmy.md"),
6337
6678
  content: promptContent,
6338
6679
  type: "text"
6339
6680
  });
@@ -6345,7 +6686,7 @@ command = "npx"
6345
6686
  args = ["-y", "@gethmy/mcp@latest", "serve"]
6346
6687
  `;
6347
6688
  files.push({
6348
- path: join5(home, ".codex", "config.toml"),
6689
+ path: join7(home, ".codex", "config.toml"),
6349
6690
  content: tomlContent,
6350
6691
  type: "toml",
6351
6692
  tomlSection: "mcp_servers.harmony"
@@ -6354,7 +6695,7 @@ args = ["-y", "@gethmy/mcp@latest", "serve"]
6354
6695
  }
6355
6696
  case "cursor": {
6356
6697
  files.push({
6357
- path: join5(cwd, ".cursor", "mcp.json"),
6698
+ path: join7(cwd, ".cursor", "mcp.json"),
6358
6699
  content: JSON.stringify({
6359
6700
  mcpServers: {
6360
6701
  harmony: {
@@ -6380,17 +6721,17 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
6380
6721
  `;
6381
6722
  if (installMode === "global") {
6382
6723
  files.push({
6383
- path: join5(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
6724
+ path: join7(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
6384
6725
  content: ruleContent,
6385
6726
  type: "text"
6386
6727
  });
6387
6728
  symlinks.push({
6388
- target: join5(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
6389
- link: join5(home, ".cursor", "rules", "harmony.mdc")
6729
+ target: join7(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
6730
+ link: join7(home, ".cursor", "rules", "harmony.mdc")
6390
6731
  });
6391
6732
  } else {
6392
6733
  files.push({
6393
- path: join5(cwd, ".cursor", "rules", "harmony.mdc"),
6734
+ path: join7(cwd, ".cursor", "rules", "harmony.mdc"),
6394
6735
  content: ruleContent,
6395
6736
  type: "text"
6396
6737
  });
@@ -6399,7 +6740,7 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
6399
6740
  }
6400
6741
  case "windsurf": {
6401
6742
  files.push({
6402
- path: join5(home, ".codeium", "windsurf", "mcp_config.json"),
6743
+ path: join7(home, ".codeium", "windsurf", "mcp_config.json"),
6403
6744
  content: JSON.stringify({
6404
6745
  mcpServers: {
6405
6746
  harmony: {
@@ -6425,17 +6766,17 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
6425
6766
  `;
6426
6767
  if (installMode === "global") {
6427
6768
  files.push({
6428
- path: join5(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
6769
+ path: join7(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
6429
6770
  content: ruleContent,
6430
6771
  type: "text"
6431
6772
  });
6432
6773
  symlinks.push({
6433
- target: join5(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
6434
- link: join5(home, ".codeium", "windsurf", "rules", "harmony.md")
6774
+ target: join7(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
6775
+ link: join7(home, ".codeium", "windsurf", "rules", "harmony.md")
6435
6776
  });
6436
6777
  } else {
6437
6778
  files.push({
6438
- path: join5(cwd, ".windsurf", "rules", "harmony.md"),
6779
+ path: join7(cwd, ".windsurf", "rules", "harmony.md"),
6439
6780
  content: ruleContent,
6440
6781
  type: "text"
6441
6782
  });
@@ -6447,7 +6788,7 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
6447
6788
  }
6448
6789
  async function runSetup(options = {}) {
6449
6790
  const cwd = process.cwd();
6450
- const home = homedir4();
6791
+ const home = homedir6();
6451
6792
  console.clear();
6452
6793
  console.log(messages.header());
6453
6794
  const existingConfig = loadConfig();
@@ -6855,7 +7196,7 @@ async function runSetup(options = {}) {
6855
7196
  for (const symlink of allSymlinks) {
6856
7197
  try {
6857
7198
  const linkDir = dirname3(symlink.link);
6858
- if (!existsSync7(linkDir)) {
7199
+ if (!existsSync8(linkDir)) {
6859
7200
  mkdirSync5(linkDir, { recursive: true });
6860
7201
  }
6861
7202
  let linkExists = false;
@@ -6884,7 +7225,7 @@ async function runSetup(options = {}) {
6884
7225
  } else {
6885
7226
  try {
6886
7227
  await writeMcpConfigFallback(home);
6887
- console.log(` ${colors.success("✓")} ${colors.dim(formatPath(join5(home, ".claude", "settings.json"), home))} ${colors.dim("(updated)")}`);
7228
+ console.log(` ${colors.success("✓")} ${colors.dim(formatPath(join7(home, ".claude", "settings.json"), home))} ${colors.dim("(updated)")}`);
6888
7229
  } catch {
6889
7230
  p3.log.warning("Could not register MCP server. Run manually: claude mcp add --transport stdio harmony -- npx -y @gethmy/mcp@latest serve");
6890
7231
  }
@@ -6951,9 +7292,9 @@ program.command("serve").description("Start the MCP server (stdio transport)").a
6951
7292
  console.error("Run: npx @gethmy/mcp setup");
6952
7293
  process.exit(1);
6953
7294
  }
6954
- await refreshSkills();
7295
+ const { updated } = await refreshSkills();
6955
7296
  const server = new HarmonyMCPServer;
6956
- await server.run();
7297
+ await server.run({ skillsUpdated: updated });
6957
7298
  });
6958
7299
  program.command("status").description("Show configuration status").action(() => {
6959
7300
  const globalConfig = loadConfig();