@gethmy/mcp 3.8.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ }
package/src/tui/setup.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { spawnSync } from "node:child_process";
1
2
  import { createHash } from "node:crypto";
2
3
  import {
3
4
  existsSync,
@@ -23,7 +24,11 @@ import {
23
24
  } from "../config.js";
24
25
  import { loginWithBrowser, type OAuthTokens } from "../oauth-login.js";
25
26
  import { onboardNewUser } from "../onboard.js";
26
- import { buildSkillFile, HARMONY_WORKFLOW_PROMPT } from "../skills.js";
27
+ import { buildSkillFile } from "../skills.js";
28
+ import {
29
+ HARMONY_AGENTS_SECTION,
30
+ renderWorkflowPrompt,
31
+ } from "./agent-instructions.js";
27
32
  import { type AgentId, detectAgents } from "./agents.js";
28
33
  import { confirmOrDefault, shouldAssumeYes } from "./confirm.js";
29
34
  import { runDocsStep } from "./docs.js";
@@ -61,11 +66,17 @@ export interface SetupOptions {
61
66
  * keep, especially under autonomous agent runs. New tools default to prompting
62
67
  * until someone classifies them here (safe failure mode). `--allow-all-tools`
63
68
  * overrides this with a blanket grant.
69
+ *
70
+ * Every name here must exist in the advertised `TOOLS` object in `server.ts`.
71
+ * `harmony_get_card_by_short_id` sat in this list for four minor versions after
72
+ * 2.14.0 dropped it, writing a permission rule for a tool that can never be
73
+ * offered — and the same dead name was being installed into AGENTS.md, where an
74
+ * agent read it and had to correct us. `setup-agent-files.test.ts` now fails on
75
+ * a name that is not advertised, in this list and in every generated file (#1124).
64
76
  */
65
- const SAFE_HARMONY_TOOLS = [
77
+ export const SAFE_HARMONY_TOOLS = [
66
78
  // Reads
67
79
  "harmony_get_card",
68
- "harmony_get_card_by_short_id",
69
80
  "harmony_search_cards",
70
81
  "harmony_get_board",
71
82
  "harmony_get_context",
@@ -77,12 +88,19 @@ const SAFE_HARMONY_TOOLS = [
77
88
  "harmony_get_comments",
78
89
  "harmony_get_plan",
79
90
  "harmony_list_plans",
91
+ "harmony_get_playbook",
92
+ "harmony_list_playbook",
80
93
  "harmony_get_agent_session",
94
+ // The checkpoint steering poll. Absent until #1124, so every checkpoint of
95
+ // every run raised a prompt for a read the workflow itself prescribes.
96
+ "harmony_get_pending_messages",
81
97
  "harmony_get_workspace_members",
82
98
  "harmony_list_agents",
83
99
  "harmony_resolve_links",
100
+ "harmony_suggest_relations",
84
101
  "harmony_recall",
85
102
  "harmony_memory_search",
103
+ "harmony_vault_index",
86
104
  "harmony_generate_prompt",
87
105
  // Routine writes
88
106
  "harmony_create_card",
@@ -91,6 +109,7 @@ const SAFE_HARMONY_TOOLS = [
91
109
  "harmony_assign_card",
92
110
  "harmony_create_subtask",
93
111
  "harmony_toggle_subtask",
112
+ "harmony_update_subtask",
94
113
  "harmony_add_label_to_card",
95
114
  "harmony_remove_label_from_card",
96
115
  "harmony_create_label",
@@ -98,6 +117,10 @@ const SAFE_HARMONY_TOOLS = [
98
117
  "harmony_update_comment",
99
118
  "harmony_add_link_to_card",
100
119
  "harmony_remove_link_from_card",
120
+ // The durable carrier for a PR link — the completion path calls it on every
121
+ // card that changed code, and it mirrors the card-link pair above.
122
+ "harmony_add_external_link",
123
+ "harmony_remove_external_link",
101
124
  "harmony_start_agent_session",
102
125
  "harmony_update_agent_progress",
103
126
  "harmony_end_agent_session",
@@ -112,6 +135,9 @@ const SAFE_HARMONY_TOOLS = [
112
135
  "harmony_remember",
113
136
  "harmony_relate",
114
137
  "harmony_update_memory",
138
+ // A ranking counter on a recalled memory. A write, not a read — it never
139
+ // deletes, hides or supersedes anything.
140
+ "harmony_recall_feedback",
115
141
  "harmony_process_command",
116
142
  "harmony_sync",
117
143
  ];
@@ -401,7 +427,8 @@ export async function resolveProjectSlug(
401
427
  export interface FileToWrite {
402
428
  path: string;
403
429
  content: string;
404
- type: "text" | "json" | "toml";
430
+ /** `markdown` merges a Harmony-owned section into a file the project owns. */
431
+ type: "text" | "json" | "toml" | "markdown";
405
432
  tomlSection?: string;
406
433
  mode?: number;
407
434
  }
@@ -512,64 +539,15 @@ async function getAgentFiles(
512
539
  }
513
540
 
514
541
  case "codex": {
515
- // AGENTS.md in project root (always local - project context)
516
- const agentsContent = `# Harmony Integration
517
-
518
- This project uses Harmony for task management. When working on tasks:
519
-
520
- ## Agent identity — always identify as yourself
521
-
522
- Every \`harmony_start_agent_session\` call passes \`agentIdentifier\` + \`agentName\`. **Use your own
523
- identity, never a hardcoded one from this file.** AGENTS.md is a cross-runtime convention file, so
524
- more than one kind of agent will read it; the board shows agents as teammates, and a session
525
- attributed to the wrong runtime misattributes the work in front of the whole team.
526
-
527
- - \`agentIdentifier\` — a stable kebab-case id for the runtime you actually are
528
- - \`agentName\` — its human-readable name
529
-
530
- Known values: \`claude-code\` / "Claude Code", \`codex\` / "OpenAI Codex", \`cursor\` / "Cursor",
531
- \`claude-desktop\` / "Claude Desktop". If you are a runtime not listed here, use your own name rather
532
- than borrowing the closest entry.
533
-
534
- ## Starting Work on a Card
535
-
536
- When given a card reference (e.g., #42 or a card name), follow this workflow:
537
-
538
- 1. Use \`harmony_get_card\` or \`harmony_search_cards\` to find the card
539
- 2. Move the card to "In Progress" using \`harmony_move_card\`
540
- 3. Add the "agent" label using \`harmony_add_label_to_card\`
541
- 4. Start a session with \`harmony_start_agent_session\`, passing **your own** \`agentIdentifier\` +
542
- \`agentName\` (see "Agent identity" above)
543
- 5. Show the card details to the user
544
- 6. Use \`harmony_generate_prompt\` to get guidance, then implement the solution
545
- 7. Update progress periodically with \`harmony_update_agent_progress\`
546
- 8. When done, call \`harmony_end_agent_session\` and move to "Review"
547
-
548
- ## Auto-Detect Card for Implementation Tasks
549
-
550
- Before implementing a plan or feature, check if it maps to an existing Harmony card:
551
-
552
- 1. Use \`harmony_search_cards\` with keywords from the task description
553
- 2. If a match is found, call \`harmony_start_agent_session\` with **your own** \`agentIdentifier\` +
554
- \`agentName\` (see "Agent identity" above), plus \`moveToColumn: "In Progress"\`, \`addLabels: ["agent"]\`
555
- 3. Update progress with \`harmony_update_agent_progress\` at milestones
556
- 4. When done, call \`harmony_end_agent_session\` with status: "completed", moveToColumn: "Review"
557
-
558
- Skip if: work was already started with a card reference, or no matching card exists.
559
-
560
- ## Available Harmony Tools
561
-
562
- - \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\` - Find cards
563
- - \`harmony_move_card\` - Move cards between columns
564
- - \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\` - Manage labels
565
- - \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\` - Track work
566
- - \`harmony_get_board\` - Get board state
567
- - \`harmony_generate_prompt\` - Get role-based guidance and focus areas for the card
568
- `;
542
+ // The Harmony section of AGENTS.md, written as type "markdown" so it is
543
+ // MERGED into whatever the project already has: the docs-step scaffold
544
+ // written earlier in this same run, or a file the user maintains by hand.
545
+ // A plain text write destroyed both setup forces writes on a fresh
546
+ // install, and this push lands after the scaffold's (#1124).
569
547
  files.push({
570
548
  path: join(cwd, "AGENTS.md"),
571
- content: agentsContent,
572
- type: "text",
549
+ content: HARMONY_AGENTS_SECTION,
550
+ type: "markdown",
573
551
  });
574
552
 
575
553
  // Codex prompt file
@@ -582,7 +560,7 @@ arguments:
582
560
  required: true
583
561
  ---
584
562
 
585
- ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "{{card}}").replace("Your agent identifier", "codex").replace("Your agent name", "OpenAI Codex")}
563
+ ${renderWorkflowPrompt({ cardArgument: "{{card}}", agentIdentifier: "codex", agentName: "OpenAI Codex" })}
586
564
  `;
587
565
 
588
566
  if (installMode === "global") {
@@ -652,7 +630,7 @@ alwaysApply: false
652
630
 
653
631
  When the user asks you to work on a Harmony card (references like #42, card names, or UUIDs):
654
632
 
655
- ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Your agent identifier", "cursor").replace("Your agent name", "Cursor AI")}
633
+ ${renderWorkflowPrompt({ cardArgument: "the card reference", agentIdentifier: "cursor", agentName: "Cursor" })}
656
634
  `;
657
635
 
658
636
  if (installMode === "global") {
@@ -708,7 +686,7 @@ description: Activate when user asks to work on a Harmony card (references like
708
686
 
709
687
  When working on a Harmony card:
710
688
 
711
- ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Your agent identifier", "windsurf").replace("Your agent name", "Windsurf AI")}
689
+ ${renderWorkflowPrompt({ cardArgument: "the card reference", agentIdentifier: "windsurf", agentName: "Windsurf" })}
712
690
  `;
713
691
 
714
692
  if (installMode === "global") {
@@ -1576,6 +1554,11 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
1576
1554
  ` ${colors.success("\u2713")} ${colors.dim(formatPath(writtenLocalConfigPath, home))} ${colors.dim("(created)")}`,
1577
1555
  );
1578
1556
 
1557
+ // The scan runs AFTER the global mirror below, deliberately: it spawns a
1558
+ // child with inherited stdio for as long as this repo's test suite takes,
1559
+ // and a Ctrl-C there must not cost the operator the global default that
1560
+ // every server started outside this directory reads (#893).
1561
+ //
1579
1562
  // Mirror the choice into the GLOBAL default, explicitly (#893). The local
1580
1563
  // file above already pins this directory; this second write is what gives a
1581
1564
  // server started elsewhere — Claude Desktop, another repo — a context at
@@ -1592,6 +1575,8 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
1592
1575
  { global: true },
1593
1576
  );
1594
1577
  }
1578
+
1579
+ await offerCommandScan(dirname(writtenLocalConfigPath), assumeYes);
1595
1580
  }
1596
1581
 
1597
1582
  // Step 10: Show completion message
@@ -1686,3 +1671,99 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
1686
1671
  }
1687
1672
  console.log("");
1688
1673
  }
1674
+
1675
+ /**
1676
+ * The command that proposes a repo's `commands` block by running it.
1677
+ *
1678
+ * `@latest` is this repo's own convention for an `npx` spec (`@gethmy/mcp@latest`
1679
+ * in every documented invocation) and it is load-bearing here: `scan-commands`
1680
+ * arrives in a release later than several already published, and a warm npx
1681
+ * cache of an older `@gethmy/agent` would otherwise hand the operator the
1682
+ * "may predate it" fallback forever with no way to see why.
1683
+ */
1684
+ const SCAN_ARGV = ["--yes", "@gethmy/agent@latest", "scan-commands"] as const;
1685
+
1686
+ function scanTip(): void {
1687
+ console.log(
1688
+ ` ${colors.dim("Tip: run")} ${colors.highlight(`npx ${SCAN_ARGV.slice(1).join(" ")}`)} ${colors.dim("to have the daemon prove which build, test and dev commands this repo has, and write them into the pin.")}`,
1689
+ );
1690
+ }
1691
+
1692
+ /**
1693
+ * Offer to scan this repo's commands, and run the scanner when asked (#1116).
1694
+ *
1695
+ * Card 1116's first acceptance criterion is that the scanner RUNS when a
1696
+ * project is set up — this is that moment, and a printed tip is not a run.
1697
+ * What this package must not do is scan by itself: the scan executes the
1698
+ * repo's own build, test and dev commands, and `@gethmy/mcp` neither depends
1699
+ * on `@gethmy/harness` (which owns both the execution and the schema) nor
1700
+ * spawns a toolchain anywhere else. So it does what the tip asked a person to
1701
+ * do — spawn the agent CLI — which keeps the dependency direction intact
1702
+ * (agent → harness → shared, mcp beside them) and re-types no schema.
1703
+ *
1704
+ * Three properties, each deliberate.
1705
+ *
1706
+ * **Asked, never assumed.** The scan starts this repo's build, test and dev
1707
+ * commands, which is minutes of work and arbitrary code from the checkout. A
1708
+ * person is present at setup and is the right one to say yes.
1709
+ *
1710
+ * **Non-interactive setups are not scanned at all** — `--yes`, a pipe, a
1711
+ * coding agent — even though `--yes` means yes to everything else. Those two
1712
+ * are not the same question: the other confirmations write files this command
1713
+ * already owns, and this one hands control to another package's process for
1714
+ * an unbounded time. They get the tip, i.e. exactly the behaviour that
1715
+ * shipped before this.
1716
+ *
1717
+ * **A failure is not an error.** The published `@gethmy/agent` gains this
1718
+ * subcommand only on its next release, and `npx` needs a network. Either way
1719
+ * the fallback is the tip, so an older agent degrades to the previous
1720
+ * behaviour rather than turning setup red over an optional convenience.
1721
+ *
1722
+ * It deliberately does NOT pass `--write`: the proposal is printed for a
1723
+ * person to read, which is card 1116's fourth criterion (never applied
1724
+ * silently), and `scan-commands --write` is the separate, explicit act.
1725
+ */
1726
+ export async function offerCommandScan(
1727
+ repoDir: string,
1728
+ assumeYes: boolean,
1729
+ ): Promise<void> {
1730
+ if (assumeYes) {
1731
+ scanTip();
1732
+ return;
1733
+ }
1734
+
1735
+ const scan = await confirmOrDefault(assumeYes, {
1736
+ message:
1737
+ "Scan this repo's commands now? It runs your build, test and dev scripts once to prove which ones exist. Nothing is written.",
1738
+ initialValue: true,
1739
+ });
1740
+ // Ctrl-C ends setup, as it does at every other prompt in this file. A
1741
+ // cancel folded into "no" would carry on through the remaining steps as if
1742
+ // the person had answered, which is the one reading they did not give.
1743
+ if (p.isCancel(scan)) {
1744
+ p.cancel("Setup cancelled.");
1745
+ process.exit(0);
1746
+ }
1747
+ if (!scan) {
1748
+ scanTip();
1749
+ return;
1750
+ }
1751
+
1752
+ console.log(
1753
+ ` ${colors.dim("Running")} ${colors.highlight(`npx ${SCAN_ARGV.slice(1).join(" ")}`)}${colors.dim(" …")}`,
1754
+ );
1755
+ const result = spawnSync("npx", [...SCAN_ARGV], {
1756
+ cwd: repoDir,
1757
+ stdio: "inherit",
1758
+ });
1759
+ if (result.error || result.status !== 0) {
1760
+ console.log(
1761
+ ` ${colors.dim("The scan did not run — your installed @gethmy/agent may predate it.")}`,
1762
+ );
1763
+ scanTip();
1764
+ return;
1765
+ }
1766
+ console.log(
1767
+ ` ${colors.dim("Re-run it with")} ${colors.highlight("--write")} ${colors.dim("to merge that block into the pin.")}`,
1768
+ );
1769
+ }