@gethmy/mcp 3.7.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/src/skills.ts CHANGED
@@ -11,86 +11,12 @@ import { getClient } from "./api-client.js";
11
11
  import { areSkillsInstalled, isConfigured } from "./config.js";
12
12
  import { loadHmyConfig } from "./hmy-config.js";
13
13
 
14
- /**
15
- * Legacy workflow prompt used by Codex / Cursor agents. Kept inline because
16
- * those agents install via AGENTS.md and not via /v1/skills. Claude Code
17
- * agents use the DB-backed skill_resource registry (see Phase 0 of card #162).
18
- */
19
- export const HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
20
-
21
- Start work on a Harmony card. Card reference: $ARGUMENTS
22
-
23
- ## 1. Find & Fetch Card
24
-
25
- Parse the reference and fetch the card:
26
- - \`#42\` or \`42\` → \`harmony_get_card\` with \`shortId: 42\`
27
- - UUID → \`harmony_get_card\` with \`cardId\`
28
- - Name/text → \`harmony_search_cards\` with \`query\`
29
-
30
- ## 2. Get Board State
31
-
32
- Call \`harmony_get_board\` to get columns and labels. From the response:
33
- - Find the "In Progress" (or "Progress") column ID
34
- - Find the "agent" label ID
35
-
36
- ## 3. Setup Card for Work
37
-
38
- Execute these in sequence:
39
- 1. \`harmony_move_card\` → Move to "In Progress" column
40
- 2. \`harmony_add_label_to_card\` → Add "agent" label
41
- 3. \`harmony_start_agent_session\`:
42
- - \`cardId\`: Card UUID
43
- - \`agentIdentifier\`: Your agent identifier
44
- - \`agentName\`: Your agent name
45
- - \`currentTask\`: "Analyzing card requirements"
46
-
47
- ## 4. Generate Work Prompt
48
-
49
- Call \`harmony_generate_prompt\` with:
50
- - \`cardId\` or \`shortId\` (+ \`projectId\` if using shortId)
51
- - \`variant\`: Select based on task:
52
- - \`"execute"\` (default) → Clear tasks, bug fixes, well-defined work
53
- - \`"analysis"\` → Complex features, unclear requirements
54
- - \`"draft"\` → Medium complexity, want feedback first
55
-
56
- The generated prompt provides role framing, focus areas, subtasks, linked cards, and suggested outputs.
57
-
58
- ## 5. Display Card Summary
59
-
60
- Show the user: Card title, short ID, role, priority, labels, due date, description, and subtasks.
61
-
62
- ## 6. Implement Solution
63
-
64
- Work on the card following the generated prompt's guidance. Update progress at milestones:
65
- - \`harmony_update_agent_progress\` with \`progressPercent\` (0-100), \`currentTask\`, \`status\`, \`blockers\`
66
-
67
- **Progress checkpoints:** 20% (exploration), 50% (implementation), 80% (testing), 100% (done)
68
-
69
- ## 7. Complete Work
70
-
71
- When finished:
72
- 1. \`harmony_end_agent_session\` with \`status: "completed"\`, \`progressPercent: 100\`
73
- 2. \`harmony_move_card\` to "Review" column
74
- 3. Summarize accomplishments
75
-
76
- If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
77
-
78
- ## Key Tools Reference
79
-
80
- **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\`
81
-
82
- **Subtasks:** \`harmony_create_subtask\`, \`harmony_toggle_subtask\`, \`harmony_delete_subtask\`
83
-
84
- **Labels:** \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\`, \`harmony_create_label\`
85
-
86
- **Links:** \`harmony_add_link_to_card\`, \`harmony_remove_link_from_card\`, \`harmony_get_card_links\`
87
-
88
- **Board:** \`harmony_get_board\`, \`harmony_list_projects\`, \`harmony_get_context\`, \`harmony_set_project_context\`
89
-
90
- **Sessions:** \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\`, \`harmony_get_agent_session\`
91
-
92
- **AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
93
- `;
14
+ // The workflow prompt for Codex / Cursor / Windsurf — which install via their
15
+ // own rule files rather than /v1/skills used to live here. It moved to
16
+ // `tui/agent-instructions.ts` (#1124) so a test can read it alongside the
17
+ // AGENTS.md section and assert every tool it names is actually advertised.
18
+ // Import it from there; this module is not in the package `exports` map, so a
19
+ // re-export would have had no reachable consumer.
94
20
 
95
21
  /**
96
22
  * Shape of a /v1/skills/{name} response. Used by buildSkillFile + tests.
@@ -0,0 +1,335 @@
1
+ /**
2
+ * The instruction text `hmy setup` installs into a project.
3
+ *
4
+ * Two strings, one job: tell an agent how to work a Harmony card correctly.
5
+ * They live here rather than inline in `setup.ts` so a test can read them —
6
+ * `setup-agent-files.test.ts` asserts every `harmony_*` name they mention is
7
+ * advertised by the server's `TOOLS` object. Nothing checked that before
8
+ * #1124, which is how `harmony_get_card_by_short_id` stayed in the installed
9
+ * `AGENTS.md` for four minor versions after 2.14.0 removed it.
10
+ *
11
+ * Both strings are governed by one rule: say only what the tool schemas do not
12
+ * already say. The schemas are in the agent's own tool listing, with live
13
+ * descriptions; restating them here creates a second source of truth that
14
+ * drifts, and that drift is this file's entire history.
15
+ */
16
+
17
+ /**
18
+ * The plan rule, in one wording for every runtime.
19
+ *
20
+ * A plan that prescribes the code is transcribed faithfully, defects included —
21
+ * so the rule has to reach the project, not only this repository. It ships three
22
+ * ways: this constant (Codex's `AGENTS.md` section and the Cursor / Windsurf
23
+ * rule files), and the `hmy-plan` skill body for Claude Code. It lives in one
24
+ * constant because a rule that is worded differently in three places is three
25
+ * rules, and only the differences get read.
26
+ *
27
+ * No heading of its own — each caller supplies the level its host file needs.
28
+ */
29
+ export const HARMONY_PLAN_RULE = `**A plan says what must be true and why. It does not contain the code.** In it: the architecture
30
+ and technology decisions with their reasons, the data model and the API contracts, a short
31
+ signature or schema sketch wherever an interpretation gap would otherwise remain, and success
32
+ criteria a test can check. Not in it: function bodies, control flow, error handling, test code.
33
+ Detail follows risk — a throwaway script gets a rough plan, while auth, money, migrations and
34
+ anything security-relevant get their contracts and edge cases written out.
35
+
36
+ Code in a plan carries the authority of a plan and the quality of a draft that no compiler, test
37
+ or review has read, and an implementer transcribes it faithfully, defects included. So when the
38
+ plan and the code disagree, the code is the evidence: check it, record the decision as a comment,
39
+ and correct the plan as well as the code.`;
40
+
41
+ /**
42
+ * The Harmony section of a project's `AGENTS.md`. Written between the
43
+ * `<!-- harmony:start -->` / `<!-- harmony:end -->` markers by
44
+ * `mergeMarkdownSection`, so the rest of the file stays the project's.
45
+ */
46
+ export const HARMONY_AGENTS_SECTION = `## Harmony
47
+
48
+ This project uses Harmony for task management. The \`harmony_*\` MCP tools are in your tool
49
+ listing with live schemas — read them there. This section covers only what the schemas do not say.
50
+
51
+ ### Identify as yourself
52
+
53
+ Every session call takes \`agentIdentifier\` + \`agentName\`. Use your OWN, never a value copied
54
+ from this file. The board shows agents as teammates, so a session attributed to the wrong runtime
55
+ misattributes the work in front of the whole team.
56
+
57
+ Known values: \`claude-code\` / "Claude Code" · \`codex\` / "OpenAI Codex" · \`cursor\` / "Cursor" ·
58
+ \`claude-desktop\` / "Claude Desktop". If you are none of these, use your own name.
59
+
60
+ ### Starting work — one call, not three
61
+
62
+ \`harmony_start_agent_session\` moves the card and adds the labels itself. Do not call
63
+ \`harmony_move_card\` or \`harmony_add_label_to_card\` first, and do not fetch the board for a
64
+ column id or a label id — both arguments match by name.
65
+
66
+ \`\`\`
67
+ harmony_start_agent_session({
68
+ cardId,
69
+ agentIdentifier, agentName, // your own
70
+ currentTask: "Reading the auth middleware to find the affected routes",
71
+ moveToColumn: "In Progress",
72
+ addLabels: ["agent"],
73
+ steerable: true, // only if you will poll for steering — see below
74
+ })
75
+ \`\`\`
76
+
77
+ **Then read the reply, because the setup half fails quietly.** \`movedTo\` names the column it
78
+ actually moved to and \`labelsAdded\` the labels it actually added; a miss leaves them null or
79
+ empty and raises no error. The column match is a case-insensitive **substring**, so a board with
80
+ "Ready for Review" ahead of "Review" can take the wrong one. If \`movedTo\` is null or not the
81
+ column you meant, call \`harmony_move_card\` — it matches exactly first and fails loudly, listing
82
+ the columns.
83
+
84
+ Keep the returned \`session.id\`; the steering poll needs it. Then call
85
+ \`harmony_generate_prompt\` for role framing and focus areas — \`variant\` is \`execute\`
86
+ (default), \`analysis\`, or \`draft\`.
87
+
88
+ ### Progress — \`actions\` is what survives as evidence
89
+
90
+ On the card itself, \`progressPercent\` and \`currentTask\` each overwrite one field, so the live
91
+ status shows only your latest checkpoint. The timeline keeps more: a checkpoint that carries both
92
+ a \`progressPercent\` and a \`currentTask\` different from the last one leaves a row saying what you
93
+ were **about to do**, and **each entry in \`actions\` leaves a row saying what you actually did**.
94
+ Report four checkpoints with no \`actions\` and a two-hour run reads as four intentions and no
95
+ evidence.
96
+
97
+ Name what you DID since the last checkpoint: the file you edited and why, the gate you ran and
98
+ what it said, the approach you ruled out and on what evidence.
99
+
100
+ \`\`\`
101
+ harmony_update_agent_progress({
102
+ cardId, agentIdentifier, agentName,
103
+ progressPercent: 50,
104
+ currentTask: "Extracting refreshIfExpired() in auth.ts",
105
+ actions: [
106
+ { description: "Read auth.ts and middleware/session.ts — the refresh path is duplicated in both, which is the actual bug" },
107
+ { description: "Ruled out patching verifyToken(): three routes depend on its current behaviour" },
108
+ { description: "Ran bun run lint — green, exit 0" },
109
+ ],
110
+ })
111
+ \`\`\`
112
+
113
+ Three to six entries per checkpoint, one sentence each; past 512 characters an entry is silently truncated. Facts, not
114
+ intentions — one vague entry is worse than none. Checkpoints: 20% explored · 50% implementing ·
115
+ 80% verifying · 100% done. \`currentTask\` is what you are doing now — never leave it generic.
116
+
117
+ ### Steering and Stop
118
+
119
+ If you passed \`steerable: true\`, poll right after every progress update:
120
+
121
+ \`\`\`
122
+ harmony_get_pending_messages({ cardId, sessionId, sinceSeq }) // sinceSeq starts at 0
123
+ \`\`\`
124
+
125
+ Messages come back oldest first. Fold them into the next step and advance \`sinceSeq\` to the
126
+ largest \`seq\` returned, so each is handled exactly once.
127
+
128
+ Two flags come back and mean opposite things:
129
+
130
+ | flag | meaning | what to do |
131
+ |---|---|---|
132
+ | \`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. |
133
+ | \`sessionStale: true\` | your session id is no longer live — usually the inactivity sweep | **Nobody stopped you.** Carry on — with a new id. |
134
+
135
+ \`harmony_update_agent_progress\` reports the same two flags, and the recovery from a stale session
136
+ differs by which call told you:
137
+
138
+ - From the **progress** call, a replacement session has already been opened for you and inherited
139
+ the steering channel. Take the new \`session.id\` from that reply and keep going.
140
+ - From the **poll**, nothing was opened. Call \`harmony_start_agent_session\` yourself and poll
141
+ with the id it returns.
142
+
143
+ If the two flags ever disagree, the stop wins.
144
+
145
+ ### Finishing
146
+
147
+ \`\`\`
148
+ harmony_end_agent_session({ cardId, status: "completed", progressPercent: 100, moveToColumn: "Review" })
149
+ \`\`\`
150
+
151
+ One call: it moves the card, and on \`status: "completed"\` it also removes the \`agent\` label. Use
152
+ \`status: "paused"\` when you stop mid-flight — that leaves the label on, which is what you want.
153
+
154
+ Attach a PR **after** the session end has moved the card, both ways: \`harmony_add_external_link\`
155
+ (durable — it survives a later description edit) and a \`PR: <url>\` line in the description.
156
+
157
+ ### Writing a plan
158
+
159
+ ${HARMONY_PLAN_RULE}
160
+
161
+ ### Traps
162
+
163
+ - **A \`shortId\` is project-scoped, and the two ways it can miss are opposites.** With **no**
164
+ active project the number is resolved across every project you can reach, so it may come back
165
+ \`needsDisambiguation\` with \`candidates\` — ask which one is meant. With an active project the
166
+ number resolves only inside it, and a card that lives elsewhere fails with an error naming the
167
+ projects it is really in — switch with \`harmony_set_project_context\` or pass \`projectId\`.
168
+ Either way, check \`resolvedProject\` matches the card you meant before starting work.
169
+ - **Fetch many cards in one call:** \`harmony_get_card({ shortIds: [400, 401, 402] })\`, max 100.
170
+ - **Moving a card to a terminal column ends your session** — a column that marks cards done, or
171
+ one named \`done\`, \`completed\` or \`review\`. The response says \`sessionEnded\`.
172
+ - **Read \`harmony_get_comments\` before you act.** Steering messages do not include comments, and
173
+ a later comment outranks an earlier one it contradicts.
174
+ - **Report findings and decisions as comments, not description edits.** \`harmony_add_comment\`
175
+ takes a \`commentType\`: \`question\` and \`blocker\` signal that you need a human; \`decision\`,
176
+ \`finding\`, \`summary\`, \`progress\` and \`message\` are the rest.`;
177
+
178
+ /**
179
+ * The per-runtime workflow prompt installed as the Codex prompt file and the
180
+ * Cursor / Windsurf rule files. Rendered through `renderWorkflowPrompt`.
181
+ */
182
+ export const HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
183
+
184
+ Work a Harmony card. Card reference: $ARGUMENTS
185
+
186
+ The \`harmony_*\` MCP tools are in your tool listing with live schemas — read them there. This
187
+ prompt covers only what the schemas do not say.
188
+
189
+ ## 1. Fetch the card
190
+
191
+ - \`#42\` or \`42\` → \`harmony_get_card({ shortId: 42 })\`
192
+ - UUID → \`harmony_get_card({ cardId })\`
193
+ - A name or phrase → \`harmony_search_cards({ query })\`
194
+ - Several at once → \`harmony_get_card({ shortIds: [40, 41, 42] })\`, max 100
195
+
196
+ A \`shortId\` is project-scoped, and the two ways it can miss are opposites. With **no** active
197
+ project the number is resolved across every project you can reach, so it may come back
198
+ \`needsDisambiguation\` with \`candidates\` — ask which one is meant. With an active project the
199
+ number resolves only inside it, and a card that lives elsewhere fails with an error naming the
200
+ projects it is really in; switch with \`harmony_set_project_context\` or pass \`projectId\`.
201
+ Either way, check \`resolvedProject\` before you start.
202
+
203
+ Read \`harmony_get_comments\` too: a later comment outranks an earlier one it contradicts.
204
+
205
+ ## 2. Start the session — one call, not three
206
+
207
+ \`harmony_start_agent_session\` moves the card and adds the labels itself. Do not call
208
+ \`harmony_move_card\` or \`harmony_add_label_to_card\` first, and do not fetch the board for a
209
+ column id or a label id — both arguments match by name.
210
+
211
+ \`\`\`
212
+ harmony_start_agent_session({
213
+ cardId,
214
+ agentIdentifier: "$AGENT_IDENTIFIER",
215
+ agentName: "$AGENT_NAME",
216
+ currentTask: "Reading the auth middleware to find the affected routes",
217
+ moveToColumn: "In Progress",
218
+ addLabels: ["agent"],
219
+ steerable: true,
220
+ })
221
+ \`\`\`
222
+
223
+ \`currentTask\` says what you are about to do, specifically. Never "Analyzing card requirements".
224
+ Keep the returned \`session.id\` — step 4 needs it.
225
+
226
+ **Read the reply, because the setup half fails quietly.** \`movedTo\` names the column it actually
227
+ moved to and \`labelsAdded\` the labels it actually added; a miss leaves them null or empty and
228
+ raises no error. The column match is a case-insensitive **substring**, so a board with "Ready for
229
+ Review" ahead of "Review" can take the wrong one. If \`movedTo\` is null or not the column you
230
+ meant, call \`harmony_move_card\` — it matches exactly first and fails loudly, listing the columns.
231
+
232
+ ## 3. Get the work prompt
233
+
234
+ \`harmony_generate_prompt\` with \`cardId\` (or \`shortId\` plus \`projectId\`) and a \`variant\`:
235
+ \`execute\` (default) for well-defined work, \`analysis\` for unclear requirements, \`draft\` when
236
+ you want feedback on a design first. It returns role framing, focus areas, subtasks and links.
237
+
238
+ Then show the user the card: title, short id, priority, labels, due date, description, subtasks.
239
+
240
+ ## 4. Implement, and check in at every milestone
241
+
242
+ Checkpoints: 20% explored · 50% implementing · 80% verifying · 100% done.
243
+
244
+ On the card itself, \`progressPercent\` and \`currentTask\` each overwrite one field, so the live
245
+ status shows only your latest checkpoint. The timeline keeps more: a checkpoint that carries both
246
+ a \`progressPercent\` and a \`currentTask\` different from the last one leaves a row saying what you
247
+ were **about to do**, and **each entry in \`actions\` leaves a row saying what you actually did** —
248
+ that is the evidence the team can still read afterwards.
249
+
250
+ \`\`\`
251
+ harmony_update_agent_progress({
252
+ cardId, agentIdentifier: "$AGENT_IDENTIFIER", agentName: "$AGENT_NAME",
253
+ progressPercent: 50,
254
+ currentTask: "Extracting refreshIfExpired() in auth.ts",
255
+ actions: [
256
+ { description: "Read auth.ts and middleware/session.ts — the refresh path is duplicated in both, which is the actual bug" },
257
+ { description: "Ruled out patching verifyToken(): three routes depend on its current behaviour" },
258
+ { description: "Ran bun run lint — green, exit 0" },
259
+ ],
260
+ status: "working", // or blocked / waiting / paused
261
+ blockers: [],
262
+ })
263
+ \`\`\`
264
+
265
+ Three to six entries per checkpoint, one sentence each; past 512 characters an entry is silently truncated. Say what you did,
266
+ not what you intend to do.
267
+
268
+ Right after each update, poll for steering:
269
+
270
+ \`\`\`
271
+ harmony_get_pending_messages({ cardId, sessionId, sinceSeq }) // sinceSeq starts at 0
272
+ \`\`\`
273
+
274
+ Fold any messages into the next step and advance \`sinceSeq\` to the largest \`seq\` returned. Two
275
+ flags come back and mean opposite things:
276
+
277
+ - \`stopped: true\` — a human pressed Stop. **Terminal.** Make no further edits, commits, pushes,
278
+ card moves, comments or progress writes; report what is finished and where any uncommitted work
279
+ lives.
280
+ - \`sessionStale: true\` — your session id is no longer live, usually the inactivity sweep. **Nobody stopped you.**
281
+ Carry on, with a new id: the **poll** opens nothing, so call \`harmony_start_agent_session\`
282
+ yourself; the **progress** call has already opened a replacement that inherited the steering
283
+ channel, so just take the new \`session.id\` from its reply.
284
+
285
+ If the two flags ever disagree, the stop wins.
286
+
287
+ Report findings and decisions with \`harmony_add_comment\` (\`commentType\`: \`question\` and
288
+ \`blocker\` signal that you need a human; \`decision\`, \`finding\`, \`summary\`, \`progress\`,
289
+ \`message\`), not by editing the card description.
290
+
291
+ ## 5. Finish
292
+
293
+ \`\`\`
294
+ harmony_end_agent_session({ cardId, status: "completed", progressPercent: 100, moveToColumn: "Review" })
295
+ \`\`\`
296
+
297
+ One call: it moves the card, and on \`status: "completed"\` it also removes the \`agent\` label. Use
298
+ \`status: "paused"\` when you stop mid-flight — that leaves the label on, which is what you want.
299
+
300
+ Opened a PR? Attach it **after** the session end has moved the card, both ways:
301
+ \`harmony_add_external_link\` (durable — it survives a later description edit) and a
302
+ \`PR: <url>\` line in the description.
303
+
304
+ Then summarise what changed.
305
+
306
+ ## Writing a plan
307
+
308
+ ${HARMONY_PLAN_RULE}
309
+
310
+ ## Worth knowing
311
+
312
+ - Moving a card to a terminal column ends your session — a column that marks cards done, or one
313
+ named \`done\`, \`completed\` or \`review\`. The response says \`sessionEnded\`.
314
+ - \`harmony_add_label_to_card\` and \`harmony_start_agent_session\`'s \`addLabels\` both CREATE a
315
+ label that does not exist yet. Check the spelling.
316
+ - \`harmony_add_comment\` works on any card you can see, including one you hold no session on.`;
317
+
318
+ /**
319
+ * Render the workflow prompt for one runtime.
320
+ *
321
+ * The placeholders are explicit tokens rather than prose. The previous version
322
+ * chained `.replace("Your agent identifier", …)` over the prompt text, which
323
+ * substitutes only the FIRST occurrence and no-ops silently the moment that
324
+ * exact sentence is reworded — so a prompt could ship telling Cursor to call
325
+ * itself "Your agent name" with nothing failing (#1124).
326
+ */
327
+ export function renderWorkflowPrompt(opts: {
328
+ cardArgument: string;
329
+ agentIdentifier: string;
330
+ agentName: string;
331
+ }): string {
332
+ return HARMONY_WORKFLOW_PROMPT.replaceAll("$ARGUMENTS", opts.cardArgument)
333
+ .replaceAll("$AGENT_IDENTIFIER", opts.agentIdentifier)
334
+ .replaceAll("$AGENT_NAME", opts.agentName);
335
+ }