@composer-app/mcp 0.0.1-beta.5 → 0.0.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.
@@ -0,0 +1,177 @@
1
+ ---
2
+ name: suggesting
3
+ description: Use when you're about to call `composer_add_suggestion` — proposing a text replacement in a Composer doc. Covers span scoping (don't replace more than asked), cross-span responses, ripple coverage (so accepting leaves the doc correct), the auto-suggest-on-confirm pattern, and anchor mechanics (`textToFind` / `occurrence` / whitespace / formatting).
4
+ ---
5
+
6
+ # Composer suggestions
7
+
8
+ A suggestion proposes a text replacement: `textToFind` is the literal
9
+ span that gets cut out when the user accepts; `replacementText` is what
10
+ gets inserted in its place. Pending suggestions render as Replace/With
11
+ cards in the doc's sidebar. There's no "just edit" — every text change
12
+ goes through the user's accept click.
13
+
14
+ This skill is the full guide for `composer_add_suggestion`. Load it any
15
+ time you're about to call the tool, even on familiar territory — the
16
+ failure modes are precise.
17
+
18
+ ## Match the span to the request
19
+
20
+ Before calling `composer_add_suggestion`, decide what span the user is
21
+ actually asking you to change. Three patterns:
22
+
23
+ ### 1. Request scoped to their selection (the common case)
24
+
25
+ "Rewrite this", "make this clearer", "fix the grammar" — no new span
26
+ mentioned. Pass `fromThreadId: <threadId>`. The suggestion inherits the
27
+ source thread's exact anchor — the span the user selected,
28
+ character-for-character.
29
+
30
+ ```
31
+ composer_add_suggestion({
32
+ roomId, fromThreadId: event.threadId, replacementText: "…"
33
+ })
34
+ ```
35
+
36
+ ### 2. Request targets a different span
37
+
38
+ "Rewrite this whole paragraph", "replace the entire list", "change the
39
+ heading". Supply `anchor` (`headingId` + `textToFind`) for the span the
40
+ user actually named. Do **not** pass `fromThreadId` — you're no longer
41
+ inheriting the thread's span.
42
+
43
+ ### 3. Proactive suggestion with no source thread
44
+
45
+ Supply `anchor`. Keep `textToFind` tight — do not widen beyond what
46
+ you're actually replacing.
47
+
48
+ **Picking a broader `textToFind` than the user asked for is the main
49
+ failure mode.** When in doubt, default to path 1.
50
+
51
+ ## Cross-span: respond anywhere in the doc
52
+
53
+ A thread is anchored to one span, but your response isn't confined to
54
+ it. When the user's question (or your own judgment) points elsewhere:
55
+
56
+ - **Suggest on a different span** — call `composer_add_suggestion` with
57
+ an explicit `anchor`. You can post multiple suggestions in one turn.
58
+ E.g. user says *"the flour amount is off and so is the bake time"* →
59
+ two suggestions, each anchored to its own span.
60
+ - **Open a new thread elsewhere** — `composer_add_comment` with its own
61
+ anchor. Useful for cross-references ("see also the conclusion") or
62
+ raising something the user didn't ask about but should see.
63
+ - **Still reply on the original thread too** if the user's question
64
+ deserves a direct answer — but only when the reply says something the
65
+ suggestion/new-comment doesn't already convey. Don't post "see my
66
+ suggestion"; the card IS the answer.
67
+
68
+ Order of operations for a multi-span response: post the
69
+ suggestion(s) / new comment(s) first, then (optionally) a reply on the
70
+ originating thread pointing out the bigger picture.
71
+
72
+ ## Suggest completely — accepting must leave the doc correct
73
+
74
+ The user clicks Accept and is done. They should never have to hunt down
75
+ downstream edits you forgot.
76
+
77
+ **Load enough context before you suggest.** The mention event gives you
78
+ `sectionMarkdown` for the containing section — usually enough for
79
+ wording changes. For anything that might appear elsewhere in the doc
80
+ (numbers, names, product/feature references, versions, dates,
81
+ terminology, heading text), call `composer_get_full_doc` first. One
82
+ extra read is much cheaper than shipping a broken doc.
83
+
84
+ **Scan for ripples before posting.** Common ones:
85
+
86
+ - **Counts and enumerations.** "The three examples below" / "three
87
+ things to remember" — adding or removing an item ripples to the count
88
+ and ordinal words ("first", "finally").
89
+ - **Cross-references.** "As in section 2", "see the conclusion", "per
90
+ step 3 above". If your edit moves or renames the target, update the
91
+ reference too.
92
+ - **Restated facts.** Recipes mention an ingredient twice; release notes
93
+ cite a version in both intro and body; specs quote a number in a
94
+ heading and a paragraph. One fact, multiple spans — cover all of them.
95
+ - **Subject/verb and pronoun agreement.** "X and Y are" → trim to just X
96
+ → "X is".
97
+ - **Neighboring flow.** Rewriting sentence 2 can break sentence 3 ("This
98
+ is why..."). Fix the continuation.
99
+ - **Heading changes.** If you change heading text, prose that says "see
100
+ the Intro section" may need updating.
101
+
102
+ **Post every ripple as its own suggestion, in the same turn.** The tool
103
+ accepts one anchor per call — call it multiple times. Each suggestion
104
+ stays tight to its own span (this is NOT oversuggesting — it's covering
105
+ the actual surface of the change).
106
+
107
+ If a ripple is too structural for a clean suggestion (reorder a list,
108
+ split a paragraph), post the ones you can AND a short reply flagging
109
+ what's still open. The user shouldn't be surprised.
110
+
111
+ When in doubt about the scope of a ripple, fetch the full doc. Don't
112
+ guess.
113
+
114
+ ## Auto-suggest when the user confirms a concrete proposal
115
+
116
+ Two turns, not three. When a user flags something qualitative (*"this
117
+ is too much flour"*, *"this sentence is clunky"*, *"this number feels
118
+ off"*):
119
+
120
+ 1. **Turn 1 — propose.** Reply on the thread with one specific
121
+ alternative phrased as a check: *"Does 200g seem right?"*, *"How
122
+ about 'gently fold' instead of 'stir'?"*, *"Would 45 minutes read
123
+ better than 90?"*. Pick a real number or phrase — not *"would you
124
+ like me to suggest a different amount?"* (that's a question about
125
+ your behavior, not a proposal).
126
+ 2. **Turn 2 — commit on confirmation.** When the user replies with any
127
+ variant of yes ("yes", "sure", "go for it", "perfect", a thumbs-up
128
+ emoji), call `composer_add_suggestion` with `fromThreadId:
129
+ event.threadId` and the concrete replacement. Do **not** also post a
130
+ comment reply — the suggestion card IS your reply.
131
+
132
+ If the user says no / picks a different value / redirects, follow their
133
+ lead — don't post the original proposal anyway.
134
+
135
+ If you can't name a concrete alternative (the thread is too abstract to
136
+ guess a number), ask a clarifying question instead. Don't propose
137
+ something generic just to fill the slot — *"Would you like me to
138
+ shorten this?"* is worthless without a target length.
139
+
140
+ ## Anchor mechanics
141
+
142
+ Write tools take:
143
+
144
+ ```
145
+ { headingId: "intro-0", textToFind: "the exact words to anchor on", occurrence?: 1 }
146
+ ```
147
+
148
+ `textToFind` is literally cut out on accept. Pick the right span:
149
+
150
+ - **Anchor the whole unit you're changing.** Replacing a sentence →
151
+ include the terminal punctuation (`.`, `?`, `!`). Replacing a bullet
152
+ item → anchor the item's text (not the `- ` marker; that's block
153
+ structure). Replacing a paragraph → anchor the whole paragraph.
154
+ - **Include any trailing punctuation you're changing.** Converting a
155
+ statement to a question? End the anchor at the `.`, end the
156
+ replacement with `?`. Don't anchor "the statement" alone and replace
157
+ with "the question?" — you'll end up with `the question?.`.
158
+ - **Match `replacementText`'s shape to the anchor's shape.** Inline
159
+ replacement inside a paragraph → replacement is inline (no leading
160
+ `- `, `#`, or blank line). Replacing a full list → replacement is a
161
+ full markdown list.
162
+ - **Formatting is part of your replacement, not the anchor.** If the
163
+ original had `**bold**` or a link, the anchor's formatting is gone on
164
+ accept — your replacement must include the markdown for any
165
+ formatting you want preserved.
166
+ - **Anchor at token boundaries, not mid-word.** `textToFind: "istrat"`
167
+ to hit the middle of "administration" is fragile. Use whole words or
168
+ sentence boundaries. Use `occurrence` when the same phrase appears
169
+ multiple times.
170
+ - **Whitespace.** Default: no leading or trailing whitespace in the
171
+ anchor, end `replacementText` at the same boundary. If you include a
172
+ trailing space in the anchor, include one in the replacement; otherwise
173
+ words smash together.
174
+
175
+ If you get `text_not_found`, the error message includes the current
176
+ section text. Re-plan against the fresh text and retry. Never retry
177
+ with stale content.
@@ -1,8 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "mcp__composer-mcp__composer_create_room",
5
- "mcp__composer-mcp__composer_join_room"
6
- ]
7
- }
8
- }
package/skill/SKILL.md DELETED
@@ -1,417 +0,0 @@
1
- ---
2
- name: composer
3
- description: Use when the user pastes a Composer share prompt, asks to "send this to Composer", wants to join a Composer doc, or says /composer. Composer is a realtime collaborative markdown editor; your MCP server (composer-mcp) lets you act inside docs as the user's agent.
4
- ---
5
-
6
- # Composer
7
-
8
- You have access to a `composer-mcp` MCP server. Use it when the user asks
9
- to create, join, monitor, or act in a Composer doc.
10
-
11
- ## Four modes
12
-
13
- ### 1. Create
14
- Triggers: "send this markdown to Composer", "make a Composer doc with this".
15
- Action: call `composer_create_room({ ... })`.
16
-
17
- **First run only — ask the user what to call you.** If you have no saved
18
- name on this machine, the MCP returns an error instructing you to stop
19
- and ask. Offer one suggested default they can accept with a tap:
20
-
21
- - If you know the user's first name, suggest `"<FirstName>'s Agent"`
22
- (e.g. `"Josh's Agent"`).
23
- - Otherwise suggest something playful that isn't a model family — `Monty`,
24
- `Gerty`, `Rosie`, `Otto`, `Pip`. Do **not** suggest Claude, Gemini,
25
- Sonnet, Opus, Haiku, GPT, or any other model name.
26
-
27
- Phrase it like: *"I'll go by Monty in Composer docs — sound good, or pick
28
- your own?"* Retry the tool call with their answer as `actingAs`. It
29
- persists to `~/.composer/user.json` and is reused forever.
30
-
31
- **On success** (first run or any subsequent run), the return gives you
32
- two ordered steps — the field names encode the order:
33
-
34
- 1. `step1_sayToUser` — output this FIRST. It always starts with the
35
- `browserUrl` because the user needs the link to open the doc; it
36
- also carries the `@<your-name>` tagging hint. Relay it; you can
37
- paraphrase lightly but do not drop the URL or the mention syntax.
38
- 2. `step2_callTool` — a structured `{ tool, args, why }` directive for
39
- the `composer_next_event` loop. **Do not run it inline.** Hand the
40
- loop to a background subagent (see "Monitor — runs in a subagent"
41
- below). End your turn once the subagent is spawned.
42
-
43
- Skipping step 1 leaves the user without the URL — they have no way into
44
- the doc. Skipping the subagent spawn leaves the room attached but
45
- silent; saying "I'm monitoring" without spawning the loop is a lie,
46
- every mention gets missed.
47
-
48
- **Seeding — prefer a file path when one exists.** Pick exactly one:
49
-
50
- - `seedMarkdownPath: "<absolute path>"` — **preferred** whenever the markdown
51
- already lives in a file on disk (a plan, a journal entry, any `.md` the
52
- user pointed at). The MCP reads the file itself, so you don't stream the
53
- whole document through the model. This is faster and avoids burning
54
- tokens re-emitting content that already exists.
55
- - `seedMarkdown: "<inline string>"` — only when the content was generated
56
- in this turn and isn't on disk yet.
57
-
58
- The seed file is read **once** at creation. Composer never writes back to
59
- it — edits made in the room stay in the room. Do not modify the source
60
- file while the user is working in Composer unless they explicitly ask you
61
- to sync changes back.
62
-
63
- ### 2. Join
64
- Triggers: a share prompt with a Composer URL, "/composer join <url>".
65
- Action: extract the URL from the prompt and call `composer_join_room({ url })`.
66
- Same first-run rule as Create. On success, the return carries the same
67
- ordered pair: output `step1_sayToUser` first (confirms the URL the user
68
- just joined), then spawn the monitor subagent — same flow as Create
69
- (see "Monitor — runs in a subagent" below).
70
-
71
- ### 3. Monitor — runs in a subagent
72
-
73
- Triggers: "watch this doc", or automatically after create/join.
74
-
75
- The monitor loop is delegated to a background subagent. Polling
76
- `composer_next_event` from the main thread fills the conversation
77
- context with idle-tick chatter and mention-handling that belongs in
78
- the doc, not the terminal. Spawn the subagent, end your turn, let the
79
- doc be the conversation.
80
-
81
- **How to spawn (Claude Code).** Use the `Agent` tool with:
82
-
83
- - `subagent_type: "general-purpose"`
84
- - `run_in_background: true`
85
- - `description: "Composer monitor: <roomId>"` — short, identifies the room
86
- - `prompt`: the template below, with `{roomId}` and `{actingAs}` filled in
87
-
88
- Prompt template:
89
-
90
- > Invoke the composer skill, then run the monitor loop for room
91
- > `{roomId}` as `{actingAs}`. Your only job is the in-doc conversation:
92
- > call `composer_next_event({ roomId: "{roomId}" })` and follow each
93
- > return's `requiredNextToolCall` directive verbatim, looping until the
94
- > goodbye branch fires.
95
- >
96
- > All write tools (`composer_add_comment`, `composer_add_suggestion`,
97
- > `composer_reply_comment`, `composer_reply_suggestion`,
98
- > `composer_resolve_thread`) are yours to use as the skill describes.
99
- > The doc IS your conversation — do not narrate to the parent between
100
- > ticks.
101
- >
102
- > Exit and return when ANY of these happen:
103
- > 1. `composer_next_event` returns `kind: "timeout"` with
104
- > `recentActivity: false` — say `userMessage` EXACTLY in the doc
105
- > (it's the goodbye line), then exit.
106
- > 2. A request inside the doc clearly needs the parent terminal
107
- > (a code change, a shell command, an external action the parent
108
- > would do). Post a short reply: "I'll get on this in the terminal,"
109
- > then exit with a one-sentence summary of the ask.
110
- > 3. An unrecoverable error (auth lost, room destroyed). Exit with
111
- > the error.
112
-
113
- **Main thread after spawning.** Turn ends. Do **not** also call
114
- `composer_next_event` — two listeners on the same room means duplicated
115
- replies. If the user asks "what's happening in Composer?", check the
116
- subagent's status (`TaskList` / `TaskOutput`) rather than re-entering
117
- the loop yourself.
118
-
119
- **Inside the loop (what the subagent does).** Default timeout is 30
120
- seconds. Every return carries a structured directive — follow it
121
- without waiting for user input.
122
-
123
- On `mention`: handle the event (reply / suggestion / resolve as needed),
124
- output any in-doc action, then execute `requiredNextToolCall` — which
125
- is another `composer_next_event` call. Do not pause for the user to
126
- acknowledge. The doc is the conversation.
127
-
128
- On `timeout`: check `recentActivity`.
129
- - `recentActivity: true` → the return includes `requiredNextToolCall`.
130
- Execute it — the user is still working, just not tagging you.
131
- - `recentActivity: false` → the return includes `userMessage` and
132
- `instruction` but NO `requiredNextToolCall`. Say `userMessage`
133
- EXACTLY ("I've left the document…") and exit (per exit rule 1
134
- above). Do not paraphrase — users recognize the line across sessions.
135
-
136
- On `mention`, the event contains everything you need to act in one turn:
137
-
138
- ```
139
- {
140
- kind: "mention",
141
- threadId: "...",
142
- threadKind: "comment" | "suggestion",
143
- threadText: "...", // the exact message that triggered you
144
- replyId?: "...", // present when it's a reply on an existing thread
145
- reason: "direct_mention" | "active_thread",
146
- anchoredText?: "...", // the doc text the thread is anchored to
147
- headingId?: "...", // the section's headingId (use with write tools)
148
- headingText?: "...",
149
- sectionMarkdown?: "..." // full containing section as markdown
150
- }
151
- ```
152
-
153
- Use `headingId` + `anchoredText` directly when calling `composer_add_suggestion`
154
- or `composer_add_comment` — no extra `composer_get_section` call is needed in
155
- the common case. Reach for `sectionMarkdown` to understand surrounding context
156
- before replying or suggesting.
157
-
158
- **The event only carries the triggering message.** If the thread already has
159
- replies (from the user, or from another agent), call `composer_get_thread({
160
- roomId, threadId })` before replying. The return has every reply with author
161
- and timestamp — essential when the user tagged you mid-conversation and you
162
- need to catch up on what's already been said.
163
-
164
- **`reason` is your main filter:**
165
-
166
- - `"direct_mention"` — sidecar or text explicitly tagged you. Always
167
- reply (unless the content is purely a thank-you that doesn't need an
168
- answer — never emit empty acknowledgements).
169
- - `"active_thread"` — a plain reply on a thread you're already in. Reply
170
- if the content invites one; skip if it's plainly addressed to another
171
- person, is a thank-you, or is otherwise a conversational dead-end.
172
- - `"solo_room"` — you're alone with one human who didn't tag anyone.
173
- **Default to a helpful reply** — they almost certainly want your
174
- input. Skip only when the text reads like:
175
- - a **note-to-self** ("TODO: fix this later", "remember to check
176
- the date"),
177
- - a bare **acknowledgement** ("k", "got it", "done"),
178
- - a stage direction / aside ("ugh", "hmm"),
179
- - or anything that visibly isn't pointed at you (quoted text,
180
- drafts they're jotting down).
181
- When in doubt, reply — the user can always ignore you.
182
-
183
- ### 4. Act
184
- Triggers: direct requests in the terminal like "add a summary to section 2".
185
- Action: the main thread is also attached to the room — call the write
186
- tools from here and report back concisely. Don't hand terminal directives
187
- to the monitor subagent; the subagent handles in-doc mentions, the main
188
- thread handles in-terminal asks. They share the same MCP, so writes from
189
- either show up in the doc.
190
-
191
- ## Tools
192
-
193
- Read tools:
194
- - `composer_get_full_doc` — entire doc as markdown.
195
- - `composer_get_section` — one section by `headingId`.
196
- - `composer_get_thread` — full state of a thread (all replies, anchor,
197
- containing section). Call this when `composer_next_event` surfaces a
198
- mention on a thread that already has history — the event gives you
199
- only the triggering message.
200
-
201
- Write tools:
202
- - `composer_add_comment` — NEW comment on any span in the doc. Use when
203
- raising something outside the current thread's anchor.
204
- - `composer_add_suggestion` — propose a text replacement (lands as
205
- pending). Can target any span — `fromThreadId` inherits the source
206
- thread's anchor; `anchor` specifies a span elsewhere. Call it multiple
207
- times in a turn to suggest in several spots.
208
- - `composer_reply_comment` / `composer_reply_suggestion` — reply on an
209
- existing thread.
210
- - `composer_resolve_thread` — mark resolved.
211
-
212
- There is no "just edit" tool in v1. All text changes go through suggestions
213
- that a human accepts manually.
214
-
215
- ### Keep comment text terse
216
-
217
- Comment threads render in a narrow sidebar (think Figma's comment box), not
218
- a chat window. Long replies get unwieldy fast. Rules:
219
-
220
- - Answer in 1–3 sentences. Prefer one.
221
- - Reply directly to the question asked — no preamble ("Great question!"),
222
- no restating the ask, no trailing summary of what you just did.
223
- - **If you post a suggestion in response to the thread, the suggestion IS
224
- your reply. Do not also post a comment reply.** The suggestion renders
225
- as a Replace/With card in the sidebar already; a pointer comment ("see
226
- the suggestion") just duplicates what the user can already see.
227
- Silent suggestion-only responses are correct and expected.
228
- - If the answer genuinely needs structure (a list of 4+ items, code, a
229
- table) and a suggestion isn't the right shape, post it as a suggestion
230
- in the doc body instead of as a comment reply.
231
- - Never dump your reasoning or tool-call chatter into a comment.
232
-
233
- Terse beats thorough. If the user wants more, they'll ask.
234
-
235
- ### Do not oversuggest — match the span to the request
236
-
237
- Before calling `composer_add_suggestion`, read the user's message and
238
- decide what span they're actually asking you to change:
239
-
240
- 1. **Request scoped to their selection** (the common case — "rewrite this",
241
- "make this clearer", "fix the grammar", no new span mentioned). Pass
242
- `fromThreadId: <threadId>`. The suggestion inherits the source thread's
243
- exact anchor — the span the user selected, character-for-character.
244
-
245
- ```
246
- composer_add_suggestion({
247
- roomId, fromThreadId: event.threadId, replacementText: "…"
248
- })
249
- ```
250
-
251
- 2. **Request targets a different span** ("rewrite this whole paragraph",
252
- "replace the entire list", "change the heading"). Supply `anchor`
253
- (`headingId` + `textToFind`) for the span the user actually named.
254
- Do not pass `fromThreadId` in this case — you're no longer inheriting
255
- the thread's span.
256
-
257
- 3. **Proactive suggestion with no source thread**. Supply `anchor` and
258
- keep `textToFind` tight — do not widen beyond what you're actually
259
- replacing.
260
-
261
- Picking a broader `textToFind` than the user asked for (the whole sentence
262
- when they highlighted a phrase, the whole paragraph when they asked about
263
- one clause) is the main failure mode. When in doubt, default to path 1.
264
-
265
- ### Cross-span: reply and suggest anywhere in the doc
266
-
267
- A comment/reply thread is anchored to *one* span, but your response is
268
- not confined to that span. When the user's question (or your own
269
- judgment) points elsewhere:
270
-
271
- - **Suggest a change to different text.** Call `composer_add_suggestion`
272
- with `anchor: { headingId, textToFind }` pointing at the target. You
273
- can post multiple suggestions in one turn — e.g., the user says "the
274
- flour amount is off and so is the bake time" → two suggestions, each
275
- anchored to its own span.
276
- - **Open a new thread elsewhere.** Call `composer_add_comment` with
277
- its own anchor. Useful for cross-references ("see also the
278
- conclusion") or raising something the user didn't ask about but
279
- should see.
280
- - **Still reply on the original thread too** if the user's question
281
- deserves a direct answer — but only when the reply says something
282
- the suggestion/new-comment doesn't already convey. Don't post
283
- "see my suggestion"; the card IS the answer.
284
-
285
- Order of operations for a multi-span response: post the suggestion(s)
286
- / new comment(s) first, then (optionally) a reply on the originating
287
- thread pointing out the bigger picture. That way the originating
288
- thread's reply can reference what you just did.
289
-
290
- ### Suggest completely — accepting must leave the doc correct
291
-
292
- Goal: the user clicks Accept and is done. They should never have to
293
- hunt down downstream edits you forgot.
294
-
295
- **Load enough context before you suggest.** The event gives you
296
- `sectionMarkdown` for the containing section — usually enough for
297
- wording changes. For anything that might appear elsewhere in the doc
298
- (numbers, names, product/feature references, versions, dates,
299
- terminology, heading text), call `composer_get_full_doc` first.
300
- One extra read is much cheaper than shipping a broken doc.
301
-
302
- **Scan for ripples before posting.** Common ones:
303
-
304
- - **Counts and enumerations.** "The three examples below" / "three
305
- things to remember" — if you add or remove an item, update the
306
- count and any ordinal words ("first", "finally").
307
- - **Cross-references.** "As in section 2", "see the conclusion",
308
- "per step 3 above". If your edit moves or renames the target,
309
- update the reference too.
310
- - **Restated facts.** Recipes reference an ingredient twice; release
311
- notes cite a version in both intro and body; specs quote a number
312
- in a heading and a paragraph. One fact, multiple spans — cover
313
- all of them.
314
- - **Subject/verb and pronoun agreement.** "X and Y are" → trim to
315
- just X → "X is". Changing from plural to singular ripples.
316
- - **Neighboring flow.** Rewriting sentence 2 can break sentence 3
317
- ("This is why..."). Fix the continuation.
318
- - **Heading changes.** If you change heading text, any prose that
319
- says "see the Intro section" may need updating.
320
-
321
- **Post every ripple as its own suggestion, in the same turn.** Don't
322
- leave the user to hunt for companion edits. The tool accepts one
323
- anchor per call — call it multiple times. Each suggestion stays
324
- tight to its own span (this is NOT oversuggesting — it's covering
325
- the actual surface of the change).
326
-
327
- If a ripple is too structural for a clean suggestion (reorder a list,
328
- split a paragraph), post the ones you can AND a short reply flagging
329
- what's still open. The user shouldn't be surprised.
330
-
331
- **When in doubt about the scope of a ripple, fetch the full doc.**
332
- Don't guess.
333
-
334
- ### Auto-suggest when the user confirms a concrete proposal
335
-
336
- When a user flags something qualitative ("this is too much flour", "this
337
- sentence is clunky", "this number feels off"), lead with a **concrete
338
- counter-proposal framed as a question** — then, if they confirm, post
339
- the suggestion immediately without waiting for a second "yes, go ahead".
340
-
341
- Two turns, not three:
342
-
343
- 1. **Turn 1 (propose).** Reply on the thread with one specific
344
- alternative phrased as a check: "Does 200g seem right?", "How about
345
- 'gently fold' instead of 'stir'?", "Would 45 minutes read better than
346
- 90?". Pick a real number / phrase — not "would you like me to
347
- suggest a different amount?" (that's a question about your behavior,
348
- not a proposal).
349
- 2. **Turn 2 (commit on confirmation).** When the user replies with any
350
- variant of yes ("yes", "sure", "go for it", "perfect", a thumbs-up
351
- emoji), call `composer_add_suggestion` with `fromThreadId: event.threadId`
352
- and the concrete replacement. Do NOT also post a comment reply — the
353
- suggestion card IS your reply (see "Keep comment text terse" above).
354
-
355
- If the user says no / picks a different value / redirects, follow their
356
- lead — do not post the original proposal anyway.
357
-
358
- If you can't name a concrete alternative (e.g. the thread is too
359
- abstract to guess a number), ask a clarifying question instead. Don't
360
- propose something generic just to fill the slot — "Would you like me
361
- to shorten this?" is worthless without a target length.
362
-
363
- ## Anchors
364
-
365
- Write tools take:
366
-
367
- ```
368
- { headingId: "intro-0", textToFind: "the exact words to anchor on", occurrence?: 1 }
369
- ```
370
-
371
- ### Pick the right span — anchor = what gets deleted
372
-
373
- Your `textToFind` is literally cut out when the user accepts; your
374
- `replacementText` is inserted in its place. So:
375
-
376
- - **Anchor the whole unit you're changing.** Replacing a sentence →
377
- include the terminal punctuation (`.`, `?`, `!`). Replacing a bullet
378
- item → anchor the item's text (not the `- ` marker; that's block
379
- structure). Replacing a paragraph → anchor the whole paragraph.
380
- - **Include any trailing punctuation you're changing.** Converting a
381
- statement to a question? End the anchor at the `.` and end the
382
- replacement with `?`. Don't anchor "the statement" alone and
383
- replace with "the question?" — you'll end up with `the question?.`.
384
- - **Match your `replacementText`'s shape to the anchor's shape.** Inline
385
- replacement inside a paragraph → replacement is inline (no leading
386
- `- `, `#`, or blank line). Replacing a full list → replacement is a
387
- full markdown list. Single-paragraph markdown is unwrapped to inline
388
- on accept; multi-block markdown is inserted as blocks.
389
- - **Formatting is part of your replacement, not the anchor.** If the
390
- original had `**bold**` or a link, the anchor's formatting is gone
391
- on accept — your replacement must include the markdown syntax for
392
- any formatting you want preserved.
393
- - **Anchor at token boundaries, not mid-word.** `textToFind: "istrat"`
394
- to hit the middle of "administration" is fragile. Use whole words
395
- or sentence boundaries. Use `occurrence` when the same phrase
396
- appears multiple times.
397
- - **Mind the whitespace.** By default, do not include leading or
398
- trailing whitespace in the anchor, and end `replacementText` at the
399
- same boundary. If you include a trailing space in the anchor,
400
- include one in the replacement too; otherwise words smash together.
401
-
402
- If you get `text_not_found`, the error message includes the current
403
- section text. Re-plan against the fresh text and retry. Never retry
404
- with stale content.
405
-
406
- ## Discoverability
407
-
408
- On first Composer-related message in a session, tell the user:
409
- "You can say 'send this markdown to Composer' and I'll create a seeded doc
410
- with a link to open."
411
-
412
- ## Failure handling
413
-
414
- - Setup not done → run `npx @composer-app/mcp@latest setup`, restart your CLI.
415
- - Anchor text not found → retry against the fresh section text returned in
416
- the error.
417
- - Doc edited mid-turn → anchors re-resolve natively; just retry.