@gethmy/mcp 2.1.0 → 2.1.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/README.md +5 -0
- package/dist/cli.js +1977 -1573
- package/dist/index.js +16 -0
- package/dist/lib/api-client.js +2 -0
- package/dist/lib/cli.js +2 -0
- package/dist/lib/context-assembly.js +21 -0
- package/dist/lib/skills.js +569 -0
- package/dist/lib/tui/docs.js +207 -21
- package/dist/lib/tui/setup.js +4 -308
- package/package.json +5 -4
- package/src/api-client.ts +6 -0
- package/src/cli.ts +2 -0
- package/src/context-assembly.ts +21 -0
- package/src/skills.ts +607 -0
- package/src/tui/docs.ts +241 -28
- package/src/tui/setup.ts +4 -311
package/src/api-client.ts
CHANGED
|
@@ -286,6 +286,7 @@ export class HarmonyApiClient {
|
|
|
286
286
|
columnId?: string;
|
|
287
287
|
summary?: boolean;
|
|
288
288
|
includeArchived?: boolean;
|
|
289
|
+
labelName?: string;
|
|
289
290
|
},
|
|
290
291
|
): Promise<{
|
|
291
292
|
project: unknown;
|
|
@@ -309,6 +310,7 @@ export class HarmonyApiClient {
|
|
|
309
310
|
if (options?.columnId) params.set("column_id", options.columnId);
|
|
310
311
|
if (options?.summary) params.set("summary", "true");
|
|
311
312
|
if (options?.includeArchived) params.set("include_archived", "true");
|
|
313
|
+
if (options?.labelName) params.set("label_name", options.labelName);
|
|
312
314
|
|
|
313
315
|
const query = params.toString() ? `?${params.toString()}` : "";
|
|
314
316
|
return this.request("GET", `/board/${projectId}${query}`);
|
|
@@ -497,6 +499,10 @@ export class HarmonyApiClient {
|
|
|
497
499
|
currentTask?: string;
|
|
498
500
|
blockers?: string[];
|
|
499
501
|
estimatedMinutesRemaining?: number;
|
|
502
|
+
phase?: string;
|
|
503
|
+
filesChanged?: number;
|
|
504
|
+
costCents?: number;
|
|
505
|
+
recentActions?: { action: string; ts: string }[];
|
|
500
506
|
},
|
|
501
507
|
): Promise<{ session: unknown; created: boolean }> {
|
|
502
508
|
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
package/src/cli.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
saveConfig,
|
|
15
15
|
} from "./config.js";
|
|
16
16
|
import { HarmonyMCPServer } from "./server.js";
|
|
17
|
+
import { refreshSkills } from "./skills.js";
|
|
17
18
|
import { runSetup } from "./tui/setup.js";
|
|
18
19
|
|
|
19
20
|
const require = createRequire(import.meta.url);
|
|
@@ -28,6 +29,7 @@ program
|
|
|
28
29
|
.command("serve")
|
|
29
30
|
.description("Start the MCP server (stdio transport)")
|
|
30
31
|
.action(async () => {
|
|
32
|
+
await refreshSkills();
|
|
31
33
|
const server = new HarmonyMCPServer();
|
|
32
34
|
await server.run();
|
|
33
35
|
});
|
package/src/context-assembly.ts
CHANGED
|
@@ -332,6 +332,27 @@ export async function assembleContext(
|
|
|
332
332
|
}
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
// Cross-project memory: fetch workspace-scoped entities only
|
|
336
|
+
// This ensures shared decisions/patterns are available without leaking project-private data
|
|
337
|
+
if (candidates.length < 20) {
|
|
338
|
+
try {
|
|
339
|
+
const wsResult = await client.listMemoryEntities({
|
|
340
|
+
workspace_id: workspaceId,
|
|
341
|
+
scope: "workspace",
|
|
342
|
+
limit: 20,
|
|
343
|
+
});
|
|
344
|
+
if (wsResult.entities?.length > 0) {
|
|
345
|
+
const existingIds = new Set(candidates.map((c) => c.id));
|
|
346
|
+
const additional = wsResult.entities
|
|
347
|
+
.map(mapToContextEntity)
|
|
348
|
+
.filter((e) => !existingIds.has(e.id));
|
|
349
|
+
candidates.push(...additional);
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
// Continue with what we have
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
335
356
|
if (candidates.length === 0) {
|
|
336
357
|
return {
|
|
337
358
|
context: "",
|
package/src/skills.ts
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { areSkillsInstalled } from "./config.js";
|
|
4
|
+
|
|
5
|
+
export const SKILLS_VERSION = "3";
|
|
6
|
+
|
|
7
|
+
const VERSION_MARKER_PREFIX = "<!-- skills-version:";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Legacy workflow prompt used by Codex, Cursor, Windsurf agents.
|
|
11
|
+
* Claude Code skills use the newer SKILL_DEFINITIONS content instead.
|
|
12
|
+
*/
|
|
13
|
+
export const HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
|
|
14
|
+
|
|
15
|
+
Start work on a Harmony card. Card reference: $ARGUMENTS
|
|
16
|
+
|
|
17
|
+
## 1. Find & Fetch Card
|
|
18
|
+
|
|
19
|
+
Parse the reference and fetch the card:
|
|
20
|
+
- \`#42\` or \`42\` → \`harmony_get_card_by_short_id\` with \`shortId: 42\`
|
|
21
|
+
- UUID → \`harmony_get_card\` with \`cardId\`
|
|
22
|
+
- Name/text → \`harmony_search_cards\` with \`query\`
|
|
23
|
+
|
|
24
|
+
## 2. Get Board State
|
|
25
|
+
|
|
26
|
+
Call \`harmony_get_board\` to get columns and labels. From the response:
|
|
27
|
+
- Find the "In Progress" (or "Progress") column ID
|
|
28
|
+
- Find the "agent" label ID
|
|
29
|
+
|
|
30
|
+
## 3. Setup Card for Work
|
|
31
|
+
|
|
32
|
+
Execute these in sequence:
|
|
33
|
+
1. \`harmony_move_card\` → Move to "In Progress" column
|
|
34
|
+
2. \`harmony_add_label_to_card\` → Add "agent" label
|
|
35
|
+
3. \`harmony_start_agent_session\`:
|
|
36
|
+
- \`cardId\`: Card UUID
|
|
37
|
+
- \`agentIdentifier\`: Your agent identifier
|
|
38
|
+
- \`agentName\`: Your agent name
|
|
39
|
+
- \`currentTask\`: "Analyzing card requirements"
|
|
40
|
+
|
|
41
|
+
## 4. Generate Work Prompt
|
|
42
|
+
|
|
43
|
+
Call \`harmony_generate_prompt\` with:
|
|
44
|
+
- \`cardId\` or \`shortId\` (+ \`projectId\` if using shortId)
|
|
45
|
+
- \`variant\`: Select based on task:
|
|
46
|
+
- \`"execute"\` (default) → Clear tasks, bug fixes, well-defined work
|
|
47
|
+
- \`"analysis"\` → Complex features, unclear requirements
|
|
48
|
+
- \`"draft"\` → Medium complexity, want feedback first
|
|
49
|
+
|
|
50
|
+
The generated prompt provides role framing, focus areas, subtasks, linked cards, and suggested outputs.
|
|
51
|
+
|
|
52
|
+
## 5. Display Card Summary
|
|
53
|
+
|
|
54
|
+
Show the user: Card title, short ID, role, priority, labels, due date, description, and subtasks.
|
|
55
|
+
|
|
56
|
+
## 6. Implement Solution
|
|
57
|
+
|
|
58
|
+
Work on the card following the generated prompt's guidance. Update progress at milestones:
|
|
59
|
+
- \`harmony_update_agent_progress\` with \`progressPercent\` (0-100), \`currentTask\`, \`status\`, \`blockers\`
|
|
60
|
+
|
|
61
|
+
**Progress checkpoints:** 20% (exploration), 50% (implementation), 80% (testing), 100% (done)
|
|
62
|
+
|
|
63
|
+
## 7. Complete Work
|
|
64
|
+
|
|
65
|
+
When finished:
|
|
66
|
+
1. \`harmony_end_agent_session\` with \`status: "completed"\`, \`progressPercent: 100\`
|
|
67
|
+
2. \`harmony_move_card\` to "Review" column
|
|
68
|
+
3. Summarize accomplishments
|
|
69
|
+
|
|
70
|
+
If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
|
|
71
|
+
|
|
72
|
+
## Key Tools Reference
|
|
73
|
+
|
|
74
|
+
**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\`
|
|
75
|
+
|
|
76
|
+
**Subtasks:** \`harmony_create_subtask\`, \`harmony_toggle_subtask\`, \`harmony_delete_subtask\`
|
|
77
|
+
|
|
78
|
+
**Labels:** \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\`, \`harmony_create_label\`
|
|
79
|
+
|
|
80
|
+
**Links:** \`harmony_add_link_to_card\`, \`harmony_remove_link_from_card\`, \`harmony_get_card_links\`
|
|
81
|
+
|
|
82
|
+
**Board:** \`harmony_get_board\`, \`harmony_list_projects\`, \`harmony_get_context\`, \`harmony_set_project_context\`
|
|
83
|
+
|
|
84
|
+
**Sessions:** \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\`, \`harmony_get_agent_session\`
|
|
85
|
+
|
|
86
|
+
**AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* New Claude Code skill content with intent detection.
|
|
91
|
+
*/
|
|
92
|
+
const HMY_SKILL_CONTENT = `# Harmony Card Workflow
|
|
93
|
+
|
|
94
|
+
User input: $ARGUMENTS
|
|
95
|
+
|
|
96
|
+
## 0. Detect Intent
|
|
97
|
+
|
|
98
|
+
Parse \`$ARGUMENTS\` to determine what the user wants:
|
|
99
|
+
|
|
100
|
+
| Pattern | Intent | Go to |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| \`create ...\` or \`new ...\` | **Create** a new card | Step A |
|
|
103
|
+
| \`#42\`, \`42\`, UUID, or card name (no action verb) | **Work on** an existing card | Step B |
|
|
104
|
+
| \`move #42 to Done\`, \`update #42 ...\`, \`assign #42 ...\` | **Quick action** on a card | Step C |
|
|
105
|
+
| \`show #42\`, \`view #42\`, \`status #42\` | **View** card details | Step D |
|
|
106
|
+
|
|
107
|
+
If ambiguous, ask the user what they'd like to do.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Step A: Create Card
|
|
112
|
+
|
|
113
|
+
Create a card without starting work on it.
|
|
114
|
+
|
|
115
|
+
1. Parse the title and any details from the arguments (e.g., \`create Add dark mode toggle\` → title: "Add dark mode toggle")
|
|
116
|
+
2. Call \`harmony_create_card\` with:
|
|
117
|
+
- \`title\`: extracted title
|
|
118
|
+
- \`description\`: if the user provided details beyond the title
|
|
119
|
+
- \`priority\`, \`columnId\`, \`assigneeId\`: only if explicitly specified
|
|
120
|
+
3. Show the created card: title, short ID, column, and a link if available.
|
|
121
|
+
4. **Stop here.** Do not start an agent session or begin implementation.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Step B: Work on Existing Card
|
|
126
|
+
|
|
127
|
+
Start work on a Harmony card.
|
|
128
|
+
|
|
129
|
+
### B1. Find & Fetch Card
|
|
130
|
+
|
|
131
|
+
Parse the reference and fetch the card:
|
|
132
|
+
- \`#42\` or \`42\` → \`harmony_get_card_by_short_id\` with \`shortId: 42\`
|
|
133
|
+
- UUID → \`harmony_get_card\` with \`cardId\`
|
|
134
|
+
- Name/text → \`harmony_search_cards\` with \`query\`
|
|
135
|
+
|
|
136
|
+
### B2. Start Agent Session
|
|
137
|
+
|
|
138
|
+
Call \`harmony_start_agent_session\` with:
|
|
139
|
+
- \`cardId\`: Card UUID
|
|
140
|
+
- \`agentIdentifier\`: Your agent identifier
|
|
141
|
+
- \`agentName\`: Your agent name
|
|
142
|
+
- \`currentTask\`: A specific description of the first thing you'll do (e.g., "Exploring codebase to understand auth flow"), NOT a generic phrase like "Analyzing card requirements"
|
|
143
|
+
- \`moveToColumn\`: "In Progress"
|
|
144
|
+
- \`addLabels\`: ["agent"]
|
|
145
|
+
|
|
146
|
+
This single call moves the card, adds the label, auto-assigns, and starts the session.
|
|
147
|
+
|
|
148
|
+
### B3. Generate Work Prompt
|
|
149
|
+
|
|
150
|
+
Call \`harmony_generate_prompt\` with:
|
|
151
|
+
- \`cardId\` or \`shortId\` (+ \`projectId\` if using shortId)
|
|
152
|
+
- \`variant\`: Select based on task:
|
|
153
|
+
- \`"execute"\` (default) → Clear tasks, bug fixes, well-defined work
|
|
154
|
+
- \`"analysis"\` → Complex features, unclear requirements
|
|
155
|
+
- \`"draft"\` → Medium complexity, want feedback first
|
|
156
|
+
|
|
157
|
+
The generated prompt provides role framing, focus areas, subtasks, linked cards, and suggested outputs.
|
|
158
|
+
|
|
159
|
+
### B4. Display Card Summary
|
|
160
|
+
|
|
161
|
+
Show the user: Card title, short ID, role, priority, labels, due date, description, and subtasks.
|
|
162
|
+
|
|
163
|
+
### B5. Implement Solution
|
|
164
|
+
|
|
165
|
+
Work on the card following the generated prompt's guidance.
|
|
166
|
+
|
|
167
|
+
**REQUIRED: Update progress at each milestone** by calling \`harmony_update_agent_progress\`. This is not optional — the card's live status badge depends on these updates.
|
|
168
|
+
|
|
169
|
+
| Milestone | \`progressPercent\` | Example \`currentTask\` |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| After exploring codebase & understanding requirements | 20 | "Reading auth middleware and identifying affected routes" |
|
|
172
|
+
| When starting implementation | 50 | "Refactoring token validation in auth.ts" |
|
|
173
|
+
| When moving to testing/verification | 80 | "Running build and verifying changes compile" |
|
|
174
|
+
| When done, before ending session | 100 | "All changes complete, ready for review" |
|
|
175
|
+
|
|
176
|
+
Always set \`currentTask\` to a specific description of what you're actually doing — never leave it as "Analyzing card requirements" or other generic text.
|
|
177
|
+
|
|
178
|
+
### B6. Complete Work
|
|
179
|
+
|
|
180
|
+
When finished:
|
|
181
|
+
1. \`harmony_end_agent_session\` with \`status: "completed"\`, \`progressPercent: 100\`, \`moveToColumn: "Review"\`
|
|
182
|
+
2. Summarize accomplishments
|
|
183
|
+
|
|
184
|
+
If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Step C: Quick Action
|
|
189
|
+
|
|
190
|
+
Fetch the card first (same as B1), then perform the requested action:
|
|
191
|
+
- **Move:** \`harmony_move_card\` with target column
|
|
192
|
+
- **Update:** \`harmony_update_card\` with changed fields
|
|
193
|
+
- **Assign:** \`harmony_assign_card\`
|
|
194
|
+
- **Label:** \`harmony_add_label_to_card\` / \`harmony_remove_label_from_card\`
|
|
195
|
+
- **Archive:** \`harmony_archive_card\`
|
|
196
|
+
|
|
197
|
+
Show confirmation and stop. Do not start an agent session.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Step D: View Card
|
|
202
|
+
|
|
203
|
+
Fetch the card (same as B1) and display: title, short ID, column, priority, labels, assignee, due date, description, subtasks, and links.
|
|
204
|
+
|
|
205
|
+
Do not start an agent session.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Key Tools Reference
|
|
210
|
+
|
|
211
|
+
**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\`
|
|
212
|
+
|
|
213
|
+
**Subtasks:** \`harmony_create_subtask\`, \`harmony_toggle_subtask\`, \`harmony_delete_subtask\`
|
|
214
|
+
|
|
215
|
+
**Labels:** \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\`, \`harmony_create_label\`
|
|
216
|
+
|
|
217
|
+
**Links:** \`harmony_add_link_to_card\`, \`harmony_remove_link_from_card\`, \`harmony_get_card_links\`
|
|
218
|
+
|
|
219
|
+
**Board:** \`harmony_get_board\`, \`harmony_list_projects\`, \`harmony_get_context\`, \`harmony_set_project_context\`
|
|
220
|
+
|
|
221
|
+
**Sessions:** \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\`, \`harmony_get_agent_session\`
|
|
222
|
+
|
|
223
|
+
**AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
|
|
224
|
+
`;
|
|
225
|
+
|
|
226
|
+
const HMY_PLAN_CONTENT = `# Harmony Plan Workflow
|
|
227
|
+
|
|
228
|
+
Create a new plan or work on an existing one. Argument: $ARGUMENTS
|
|
229
|
+
|
|
230
|
+
## Step 1 — Detect Intent
|
|
231
|
+
|
|
232
|
+
Parse \`$ARGUMENTS\` to determine the workflow:
|
|
233
|
+
|
|
234
|
+
- **UUID** (contains dashes, 36 chars) → call \`harmony_get_plan\` with \`planId\` directly → **Step 2A**
|
|
235
|
+
- **\`#N\`** (short ID) → call \`harmony_get_card_by_short_id\` to get the card, then \`harmony_get_plan\` with \`cardId\` → **Step 2A**
|
|
236
|
+
- **Text** (anything else) → call \`harmony_list_plans\` with \`search\` set to the text
|
|
237
|
+
- If **one match** → use it → **Step 2A**
|
|
238
|
+
- If **multiple matches** → list them with title, status, phase, and updated date. Ask the user to pick one using \`AskUserQuestion\` → **Step 2A**
|
|
239
|
+
- If **no matches** → ask user: "No existing plans found for '$ARGUMENTS'. Would you like to create a new plan on this topic?" → **Step 2B**
|
|
240
|
+
- **Empty / vague topic** → **Step 2B** (create new plan)
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Step 2A — Work on Existing Plan
|
|
245
|
+
|
|
246
|
+
### 2A.1 — Analyze & Display
|
|
247
|
+
|
|
248
|
+
Once you have the plan ID, call \`harmony_get_plan\` to fetch the full plan with tasks. Show a structured summary:
|
|
249
|
+
|
|
250
|
+
\\\`\\\`\\\`
|
|
251
|
+
## [Plan Title]
|
|
252
|
+
**Status:** draft/active/archived | **Phase:** plan/execute/verify/done
|
|
253
|
+
**Tasks:** N total (X pending, Y in_progress, Z completed)
|
|
254
|
+
**URL:** https://gethmy.com/plans/{id}
|
|
255
|
+
\\\`\\\`\\\`
|
|
256
|
+
|
|
257
|
+
If plan is in execute phase and tasks already have linked cards, note which tasks have cards and which don't.
|
|
258
|
+
|
|
259
|
+
### 2A.2 — Recall Context
|
|
260
|
+
|
|
261
|
+
Call \`harmony_memory_search\` with the plan title to find related knowledge, patterns, or decisions that may inform execution.
|
|
262
|
+
|
|
263
|
+
### 2A.3 — Present Options
|
|
264
|
+
|
|
265
|
+
Use \`AskUserQuestion\` to offer execution choices. Adapt options based on plan state:
|
|
266
|
+
|
|
267
|
+
#### Default options (plan in \`plan\` phase):
|
|
268
|
+
|
|
269
|
+
**(A) Single card** — Create one card with plan summary + link to plan. Best for simple plans.
|
|
270
|
+
**(B) Multiple cards** — Create one card per task via \`harmony_advance_plan\`. Best for complex plans with independent tasks.
|
|
271
|
+
**(C) Analyze only** — Review the plan and design an implementation approach. Create cards later.
|
|
272
|
+
**(D) Skip** — Do nothing.
|
|
273
|
+
|
|
274
|
+
#### If plan has no tasks:
|
|
275
|
+
Only offer **(A) Single card**, **(C) Analyze only**, or **(D) Skip**.
|
|
276
|
+
|
|
277
|
+
#### If plan is already in \`execute\` phase with existing cards:
|
|
278
|
+
**(A) Work on existing cards** — List cards with short IDs, suggest \`/hmy #N\` to start
|
|
279
|
+
**(B) Create cards for remaining tasks** — Only create cards for tasks without linked cards
|
|
280
|
+
**(C) Analyze progress** — Review what's done vs remaining
|
|
281
|
+
**(D) Skip**
|
|
282
|
+
|
|
283
|
+
### 2A.4 — Execute Choice
|
|
284
|
+
|
|
285
|
+
#### Option A — Single card
|
|
286
|
+
1. Call \`harmony_create_card\` with:
|
|
287
|
+
- \`title\`: Plan title
|
|
288
|
+
- \`description\`: Brief 2-3 sentence summary of the plan + \`\\n\\n[View plan](https://gethmy.com/plans/{planId})\`
|
|
289
|
+
- \`priority\`: based on plan task priorities (use highest)
|
|
290
|
+
2. Call \`harmony_update_plan\` to set \`status: "active"\`, \`workflowPhase: "execute"\`
|
|
291
|
+
|
|
292
|
+
#### Option B — Multiple cards (advance plan)
|
|
293
|
+
1. Call \`harmony_advance_plan\` with:
|
|
294
|
+
- \`planId\`: the plan ID
|
|
295
|
+
- \`phase\`: \`"execute"\`
|
|
296
|
+
- \`summary\`: Brief summary of the plan scope
|
|
297
|
+
2. Display the created cards with their short IDs
|
|
298
|
+
|
|
299
|
+
#### Option C — Analyze only
|
|
300
|
+
1. Present a structured implementation analysis:
|
|
301
|
+
- Suggested order of tasks
|
|
302
|
+
- Dependencies between tasks
|
|
303
|
+
- Key technical considerations from memory search
|
|
304
|
+
- Estimated complexity
|
|
305
|
+
2. Suggest creating cards when ready
|
|
306
|
+
|
|
307
|
+
#### Option D — Skip
|
|
308
|
+
Acknowledge and stop.
|
|
309
|
+
|
|
310
|
+
### 2A.5 — Summary
|
|
311
|
+
|
|
312
|
+
After execution, show:
|
|
313
|
+
- Created card(s) with short IDs
|
|
314
|
+
- Current plan phase
|
|
315
|
+
- Suggest next step: \`/hmy #N\` to start working on a card
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Step 2B — Create New Plan
|
|
320
|
+
|
|
321
|
+
### 2B.1 — Context Gathering
|
|
322
|
+
|
|
323
|
+
Before interviewing, gather existing context:
|
|
324
|
+
|
|
325
|
+
1. Call \`harmony_get_board\` for board state (columns, cards, labels)
|
|
326
|
+
2. Call \`harmony_recall\` with relevant tags to find existing knowledge on the topic
|
|
327
|
+
3. Call \`harmony_memory_search\` with the topic to find related patterns/decisions/lessons
|
|
328
|
+
|
|
329
|
+
Note what you learn — reference it during the interview to ask smarter questions.
|
|
330
|
+
|
|
331
|
+
### 2B.2 — Structured Interview (3-5 questions)
|
|
332
|
+
|
|
333
|
+
Interview the user with **3-5 focused questions** using AskUserQuestion. Adapt based on complexity.
|
|
334
|
+
|
|
335
|
+
#### Core Questions
|
|
336
|
+
1. **Problem & Audience**: What problem are we solving? Who benefits?
|
|
337
|
+
2. **Scope**: What's in v1, what's deferred?
|
|
338
|
+
3. **Technical Constraints**: Any technical preferences, constraints, or existing patterns to follow?
|
|
339
|
+
|
|
340
|
+
#### Adaptive Follow-ups (if needed)
|
|
341
|
+
- Key user flows or interactions?
|
|
342
|
+
- Integration points with existing systems?
|
|
343
|
+
- Performance, security, or accessibility requirements?
|
|
344
|
+
|
|
345
|
+
#### Interview Rules
|
|
346
|
+
- Reference what you found in Step 2B.1 (board state, memories) to ask informed questions
|
|
347
|
+
- Skip questions that aren't relevant — simple features need fewer questions
|
|
348
|
+
- Tell the user when you have enough information to draft
|
|
349
|
+
|
|
350
|
+
### 2B.3 — Plan Document
|
|
351
|
+
|
|
352
|
+
Create a structured markdown document:
|
|
353
|
+
|
|
354
|
+
\\\`\\\`\\\`markdown
|
|
355
|
+
# [Title]
|
|
356
|
+
|
|
357
|
+
## Problem
|
|
358
|
+
[What problem we're solving and why]
|
|
359
|
+
|
|
360
|
+
## Scope
|
|
361
|
+
|
|
362
|
+
### In Scope
|
|
363
|
+
- [Feature/capability 1]
|
|
364
|
+
- [Feature/capability 2]
|
|
365
|
+
|
|
366
|
+
### Out of Scope
|
|
367
|
+
- [Deferred items]
|
|
368
|
+
|
|
369
|
+
## Approach
|
|
370
|
+
[Technical design, architecture decisions, key patterns]
|
|
371
|
+
|
|
372
|
+
### Key Decisions
|
|
373
|
+
| Decision | Choice | Rationale |
|
|
374
|
+
|----------|--------|-----------|
|
|
375
|
+
| ... | ... | ... |
|
|
376
|
+
|
|
377
|
+
## Tasks
|
|
378
|
+
1. **[Task title]** — priority: high
|
|
379
|
+
[Brief description]
|
|
380
|
+
|
|
381
|
+
2. **[Task title]** — priority: medium
|
|
382
|
+
[Brief description]
|
|
383
|
+
|
|
384
|
+
[Continue for all tasks...]
|
|
385
|
+
|
|
386
|
+
## Risks & Mitigations
|
|
387
|
+
| Risk | Mitigation |
|
|
388
|
+
|------|------------|
|
|
389
|
+
| ... | ... |
|
|
390
|
+
|
|
391
|
+
## Success Criteria
|
|
392
|
+
- [ ] [Measurable criterion]
|
|
393
|
+
\\\`\\\`\\\`
|
|
394
|
+
|
|
395
|
+
### 2B.4 — Create Plan
|
|
396
|
+
|
|
397
|
+
Call \`harmony_create_plan\` with:
|
|
398
|
+
- \`title\`: The plan title
|
|
399
|
+
- \`content\`: Full markdown document from Step 2B.3
|
|
400
|
+
- \`source\`: \`"agent"\`
|
|
401
|
+
- \`workflowPhase\`: \`"plan"\`
|
|
402
|
+
- \`tasks\`: Array of tasks from the Tasks section, each with:
|
|
403
|
+
- \`content\`: Task title + brief description
|
|
404
|
+
- \`priority\`: \`"high"\`, \`"medium"\`, or \`"low"\`
|
|
405
|
+
- \`status\`: \`"pending"\`
|
|
406
|
+
|
|
407
|
+
### 2B.5 — User Approval
|
|
408
|
+
|
|
409
|
+
Show the user:
|
|
410
|
+
- Plan URL: \`https://gethmy.com/plans/{id}\`
|
|
411
|
+
- Number of tasks created
|
|
412
|
+
- Brief summary
|
|
413
|
+
|
|
414
|
+
Then ask: **"Ready to execute? I'll create board cards from these tasks and start the execution phase."**
|
|
415
|
+
|
|
416
|
+
Options:
|
|
417
|
+
1. **Yes, advance to Execute** — proceed to Step 2B.6
|
|
418
|
+
2. **Let me review first** — end here, they can advance later from the UI
|
|
419
|
+
3. **Make changes** — iterate on the plan
|
|
420
|
+
|
|
421
|
+
### 2B.6 — Advance to Execute
|
|
422
|
+
|
|
423
|
+
Call \`harmony_advance_plan\` with:
|
|
424
|
+
- \`planId\`: The plan ID from Step 2B.4
|
|
425
|
+
- \`phase\`: \`"execute"\`
|
|
426
|
+
- \`summary\`: A 2-3 sentence summary of the key decisions made during planning
|
|
427
|
+
|
|
428
|
+
Report the result:
|
|
429
|
+
- "Created N cards in 'To Do'. Use \`/hmy #<id>\` to start working on any card."
|
|
430
|
+
- List the created cards with their short IDs
|
|
431
|
+
|
|
432
|
+
## Key Tools Reference
|
|
433
|
+
|
|
434
|
+
**Discovery:** \`harmony_list_plans\`, \`harmony_get_plan\`, \`harmony_get_card_by_short_id\`
|
|
435
|
+
**Context:** \`harmony_get_board\`, \`harmony_recall\`, \`harmony_memory_search\`
|
|
436
|
+
**Planning:** \`harmony_create_plan\`, \`harmony_advance_plan\`, \`harmony_update_plan\`
|
|
437
|
+
**Execution:** \`harmony_create_card\`, \`harmony_update_card\`
|
|
438
|
+
`;
|
|
439
|
+
|
|
440
|
+
export interface SkillDefinition {
|
|
441
|
+
name: string;
|
|
442
|
+
description: string;
|
|
443
|
+
argumentHint: string;
|
|
444
|
+
content: string;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export const SKILL_DEFINITIONS: Record<string, SkillDefinition> = {
|
|
448
|
+
hmy: {
|
|
449
|
+
name: "hmy",
|
|
450
|
+
description:
|
|
451
|
+
"Work with Harmony cards — create, view, update, or start working on them. Use when given a card reference like #42, or commands like create, move, update.",
|
|
452
|
+
argumentHint: "<command or card-reference>",
|
|
453
|
+
content: HMY_SKILL_CONTENT,
|
|
454
|
+
},
|
|
455
|
+
"hmy-plan": {
|
|
456
|
+
name: "hmy-plan",
|
|
457
|
+
description:
|
|
458
|
+
"Create a new plan or work on an existing one. Use when asked to plan a feature, execute a plan, review a plan, or given a plan reference.",
|
|
459
|
+
argumentHint: "[plan name, ID, or topic to plan]",
|
|
460
|
+
content: HMY_PLAN_CONTENT,
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Build a complete skill file with frontmatter and version marker.
|
|
466
|
+
* Optionally substitutes agent identifier/name for agent-specific builds.
|
|
467
|
+
*/
|
|
468
|
+
export function buildSkillFile(skillId: string, agentId?: string): string {
|
|
469
|
+
const skill = SKILL_DEFINITIONS[skillId];
|
|
470
|
+
if (!skill) {
|
|
471
|
+
throw new Error(`Unknown skill: ${skillId}`);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
let content = skill.content;
|
|
475
|
+
|
|
476
|
+
// Apply agent-specific substitutions
|
|
477
|
+
if (agentId === "claude") {
|
|
478
|
+
content = content
|
|
479
|
+
.replace("Your agent identifier", "claude-code")
|
|
480
|
+
.replace("Your agent name", "Claude Code");
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const frontmatter = `---
|
|
484
|
+
name: ${skill.name}
|
|
485
|
+
description: ${skill.description}
|
|
486
|
+
argument-hint: ${skill.argumentHint}
|
|
487
|
+
---`;
|
|
488
|
+
|
|
489
|
+
return `${frontmatter}\n\n${content}\n${VERSION_MARKER_PREFIX}${SKILLS_VERSION} -->`;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Parse the skills version from a skill file's content.
|
|
494
|
+
* Returns null if no version marker is found.
|
|
495
|
+
*/
|
|
496
|
+
function parseSkillVersion(content: string): string | null {
|
|
497
|
+
const match = content.match(/<!-- skills-version:(\d+) -->/);
|
|
498
|
+
return match ? match[1] : null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Find all skill files in a given directory (global or local).
|
|
503
|
+
* Returns an array of { skillId, filePath } for each found skill.
|
|
504
|
+
*/
|
|
505
|
+
function findSkillFiles(
|
|
506
|
+
paths: string[],
|
|
507
|
+
): { skillId: string; filePath: string }[] {
|
|
508
|
+
const results: { skillId: string; filePath: string }[] = [];
|
|
509
|
+
|
|
510
|
+
for (const filePath of paths) {
|
|
511
|
+
if (!existsSync(filePath)) continue;
|
|
512
|
+
|
|
513
|
+
// Determine skill ID from path
|
|
514
|
+
for (const skillId of Object.keys(SKILL_DEFINITIONS)) {
|
|
515
|
+
if (
|
|
516
|
+
filePath.includes(`/${skillId}/`) ||
|
|
517
|
+
filePath.includes(`/${skillId}.md`)
|
|
518
|
+
) {
|
|
519
|
+
results.push({ skillId, filePath });
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return results;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Silently refresh installed skill files if they are outdated.
|
|
530
|
+
* Called at MCP server startup. Non-blocking — errors are caught and logged.
|
|
531
|
+
*/
|
|
532
|
+
export async function refreshSkills(): Promise<void> {
|
|
533
|
+
try {
|
|
534
|
+
const status = areSkillsInstalled();
|
|
535
|
+
|
|
536
|
+
if (!status.installed) {
|
|
537
|
+
// User hasn't run setup yet — don't install skills
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Find skill files from the detected paths
|
|
542
|
+
const skillFiles = findSkillFiles(status.paths);
|
|
543
|
+
|
|
544
|
+
// Also check for sibling skills in the same directory
|
|
545
|
+
// e.g., if hmy is installed at ~/.agents/skills/hmy/SKILL.md,
|
|
546
|
+
// check for hmy-plan at ~/.agents/skills/hmy-plan/SKILL.md
|
|
547
|
+
if (skillFiles.length > 0) {
|
|
548
|
+
const samplePath = skillFiles[0].filePath;
|
|
549
|
+
|
|
550
|
+
for (const skillId of Object.keys(SKILL_DEFINITIONS)) {
|
|
551
|
+
const alreadyFound = skillFiles.some((sf) => sf.skillId === skillId);
|
|
552
|
+
if (alreadyFound) continue;
|
|
553
|
+
|
|
554
|
+
// Try to find sibling skill file
|
|
555
|
+
let siblingPath: string;
|
|
556
|
+
if (samplePath.endsWith("SKILL.md")) {
|
|
557
|
+
// ~/.agents/skills/hmy/SKILL.md -> ~/.agents/skills/hmy-plan/SKILL.md
|
|
558
|
+
const parentDir = dirname(dirname(samplePath));
|
|
559
|
+
siblingPath = `${parentDir}/${skillId}/SKILL.md`;
|
|
560
|
+
} else {
|
|
561
|
+
// ~/.claude/skills/hmy.md -> ~/.claude/skills/hmy-plan.md
|
|
562
|
+
const parentDir = dirname(samplePath);
|
|
563
|
+
siblingPath = `${parentDir}/${skillId}.md`;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (existsSync(siblingPath)) {
|
|
567
|
+
skillFiles.push({ skillId, filePath: siblingPath });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (skillFiles.length === 0) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
let updated = false;
|
|
577
|
+
|
|
578
|
+
for (const { skillId, filePath } of skillFiles) {
|
|
579
|
+
try {
|
|
580
|
+
const currentContent = readFileSync(filePath, "utf-8");
|
|
581
|
+
const currentVersion = parseSkillVersion(currentContent);
|
|
582
|
+
|
|
583
|
+
// Update if no version marker or version is older
|
|
584
|
+
if (
|
|
585
|
+
currentVersion === null ||
|
|
586
|
+
Number(currentVersion) < Number(SKILLS_VERSION)
|
|
587
|
+
) {
|
|
588
|
+
const newContent = buildSkillFile(skillId, "claude");
|
|
589
|
+
const dir = dirname(filePath);
|
|
590
|
+
if (!existsSync(dir)) {
|
|
591
|
+
mkdirSync(dir, { recursive: true });
|
|
592
|
+
}
|
|
593
|
+
writeFileSync(filePath, newContent);
|
|
594
|
+
updated = true;
|
|
595
|
+
}
|
|
596
|
+
} catch {
|
|
597
|
+
// Silently skip files we can't read/write
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (updated) {
|
|
602
|
+
console.error(`Harmony: Updated skills to v${SKILLS_VERSION}`);
|
|
603
|
+
}
|
|
604
|
+
} catch {
|
|
605
|
+
// Non-blocking — if anything fails, continue silently
|
|
606
|
+
}
|
|
607
|
+
}
|