@composer-app/mcp 0.0.1-beta.7 → 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,131 @@
1
+ ---
2
+ name: create
3
+ description: Use when the user wants to start a NEW Composer doc — pasting markdown to "send to Composer", asking to create/seed a Composer room from a file or inline content, or accepting your offer to put a draft into Composer. Covers first-run agent-name prompt, seed selection (file path vs inline), the ordered `step1_sayToUser` / `step2_callTool` return, and the monitor-subagent handoff.
4
+ ---
5
+
6
+ # Composer — Create a doc
7
+
8
+ Composer is a real-time collaborative markdown editor. `composer_create_room`
9
+ seeds a new room with markdown and gives you back a shareable URL.
10
+
11
+ ## When to load this skill
12
+
13
+ - The user asks to "send this to Composer", "make a Composer doc with
14
+ this", "create a Composer room with the plan".
15
+ - They accept your offer to put a draft into Composer (the SessionStart
16
+ hook may have nudged you to make that offer; this skill tells you how
17
+ to follow through).
18
+
19
+ If they pasted an existing `usecomposer.app/r/<id>` URL, they want to
20
+ **join**, not create — load `composer:join` instead.
21
+
22
+ ## Steps
23
+
24
+ ### 1. First-run only — agree on a name
25
+
26
+ If the MCP returns an "ask the user what to call you" error, no `actingAs`
27
+ name is saved on this machine yet. Stop and ask. Suggest one default:
28
+
29
+ - If you know the user's first name → `"<FirstName>'s Agent"` (e.g.
30
+ `"Josh's Agent"`).
31
+ - Otherwise something playful that isn't a model family — `Monty`,
32
+ `Gerty`, `Rosie`, `Otto`, `Pip`. **Never** suggest Claude, Gemini,
33
+ Sonnet, Opus, Haiku, GPT, or any other model name.
34
+
35
+ Phrase: *"I'll go by Monty in Composer docs — sound good, or pick your
36
+ own?"* Retry the call with their answer as `actingAs`. The name persists
37
+ to `~/.composer/user.json` and is reused forever.
38
+
39
+ ### 2. Pick the seed source
40
+
41
+ Pick exactly one:
42
+
43
+ - **`seedMarkdownPath: "<absolute path>"`** — preferred whenever the
44
+ markdown already lives in a file on disk (a plan, a journal entry, any
45
+ `.md` the user pointed at). The MCP reads the file itself, so you don't
46
+ stream the content through the model. Faster, cheaper.
47
+ - **`seedMarkdown: "<inline string>"`** — only when the content was
48
+ generated this turn and isn't on disk yet.
49
+
50
+ The seed is read **once**. Composer never writes back to the source
51
+ file. If the user later wants the room contents back on disk, that's the
52
+ `/composer:export` slash command — explicit, one-way overwrite.
53
+
54
+ ### 3. Call `composer_create_room`
55
+
56
+ ```
57
+ composer_create_room({
58
+ seedMarkdownPath: "<absolute path>" // OR seedMarkdown: "<inline>"
59
+ })
60
+ ```
61
+
62
+ ### 4. Honor the ordered return
63
+
64
+ Success returns two ordered steps. The field names encode the order:
65
+
66
+ 1. **`step1_sayToUser`** — output this **first**. It always starts with
67
+ the `browserUrl` so the user has a way into the doc, and carries the
68
+ `@<your-name>` tagging hint they'll need to mention you. Light
69
+ paraphrasing is fine; **do not drop the URL or the mention syntax**.
70
+ 2. **`step2_callTool`** — a structured `{ tool, args, why }` directive
71
+ pointing at `composer_next_event`. **Do not run it inline.** Spawn
72
+ the monitor subagent (next step), then end your turn.
73
+
74
+ Skipping `step1_sayToUser` strands the user with no link. Skipping the
75
+ subagent spawn leaves the room attached but silent — saying "I'm
76
+ monitoring" without spawning the loop is a lie; every mention gets missed.
77
+
78
+ ### 5. Spawn the monitor subagent
79
+
80
+ Use the `Agent` tool with:
81
+
82
+ - `subagent_type: "general-purpose"`
83
+ - `run_in_background: true`
84
+ - `description: "Composer monitor: <roomId>"`
85
+ - `prompt`: tell the subagent to **invoke the `composer:monitor`
86
+ skill**, then run the loop on `{roomId}` as `{actingAs}`. Don't paste
87
+ the loop rules inline — the `composer:monitor` skill carries them
88
+ (spawn template, mention filtering, exit rules, event payload).
89
+
90
+ Once spawned, **end your turn immediately**. Do not output any text
91
+ after the spawn — no closing recap, no "monitor is running" status
92
+ line, no restating the mention syntax. The host already shows a
93
+ status indicator for the backgrounded `Agent` tool call; that's the
94
+ user's confirmation. Anything you say after `step1_sayToUser` just
95
+ duplicates it. The protocol is strict:
96
+
97
+ 1. Output `step1_sayToUser` (verbatim or lightly paraphrased).
98
+ 2. Spawn the `Agent`.
99
+ 3. End turn. No closing remark.
100
+
101
+ Also: do **not** poll `composer_next_event` from the main thread —
102
+ two listeners on the same room means duplicated replies.
103
+
104
+ ### When the monitor exits
105
+
106
+ You'll get a notification when the background subagent finishes. Its
107
+ final output line is the agent's goodbye — written in the agent's
108
+ voice (idle timeout, server kick, reconnect aborted, etc., per the
109
+ `composer:monitor` exit rules). **Relay that line verbatim to the user
110
+ in the main thread** so they see why the agent left and how to bring
111
+ it back. Don't paraphrase or wrap it in extra explanation; the
112
+ goodbye line already carries the next step (`/composer:join` etc.).
113
+
114
+ ## Terminal-side asks after creation
115
+
116
+ The main thread is also attached to the room. If the user later asks
117
+ something in the terminal like *"add a summary to section 2"*, call the
118
+ write tools from here and report back concisely. Don't hand terminal
119
+ directives to the monitor subagent — the subagent handles in-doc
120
+ mentions, the main thread handles in-terminal asks. They share the same
121
+ MCP, so writes from either show up in the doc.
122
+
123
+ For comment etiquette, load **`composer:commenting`**. For suggestion
124
+ craft, load **`composer:suggesting`**.
125
+
126
+ ## Failure handling
127
+
128
+ - **Setup not done** → `npx @composer-app/mcp@latest setup`, restart the CLI.
129
+ - **Server kicked you (4403/4410) or reconnect aborted** → the agent
130
+ left the room. Run `/composer:join <url>` to bring it back. The kick
131
+ reason is in the error message.
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: export
3
+ description: Use when the user wants to dump a Composer doc's current contents to a local markdown file — running `/composer:export <path>`, asking to "save this Composer doc to disk", or to "export the room as markdown". This is ONE-WAY — Composer is the source of truth and the local file is overwritten on every export. There is no merge, no diff, no two-way sync.
4
+ ---
5
+
6
+ # Composer — Export to a local file
7
+
8
+ `composer_export_to_file` writes the current room contents to a local
9
+ markdown file. **There is no merge, no diff, no two-way sync** —
10
+ Composer is authoritative; whatever's at the path becomes a fresh
11
+ snapshot on every export. **If a file already exists at the path, the
12
+ export will overwrite it.** Always confirm with the user first when
13
+ that's the case — see step 3 below.
14
+
15
+ ## Steps
16
+
17
+ ### 1. Resolve the room
18
+
19
+ - If exactly one Composer room is attached this session, use it.
20
+ - If multiple are attached, ask: *"Which doc — paste the URL or
21
+ roomId."* Stop. Do not guess.
22
+ - If none are attached, ask the user to `/composer:join <url>` first,
23
+ then re-run.
24
+
25
+ ### 2. Resolve the path
26
+
27
+ - The argument **must be an absolute path**. If it's relative, ask the
28
+ user for an absolute one (or resolve it against the current working
29
+ directory if unambiguous — but confirm before doing so).
30
+ - The parent directory must already exist. The MCP will not create
31
+ directories.
32
+
33
+ ### 3. Check for an existing file and ask permission before overwriting
34
+
35
+ Before calling the export tool, check whether anything is at the
36
+ target path (use whatever read-only tool your host gives you — `ls`,
37
+ `stat`, `Read`, etc.):
38
+
39
+ - **File does not exist** — proceed straight to step 4. No confirmation
40
+ needed; nothing is at risk.
41
+ - **File exists** — STOP and ask the user explicitly. Show them the
42
+ path and the consequence in one short line, e.g.:
43
+
44
+ > *`{path}` already exists. Exporting will overwrite it. Want me to
45
+ > go ahead?*
46
+
47
+ Wait for an affirmative answer (yes / sure / go ahead / 👍) before
48
+ calling the tool. If they say no or pick a different path, follow
49
+ their lead — do NOT call the tool with the original path.
50
+
51
+ Do not silently overwrite. The user typing `/composer:export <path>`
52
+ opts them into the export, not into clobbering arbitrary local files
53
+ without warning.
54
+
55
+ ### 4. Call `composer_export_to_file`
56
+
57
+ ```
58
+ composer_export_to_file({ roomId, path: "<absolute>" })
59
+ ```
60
+
61
+ Returns `{ roomId, path, bytesWritten }`.
62
+
63
+ ### 5. Confirm in one short line
64
+
65
+ > *"Exported to `{path}` ({bytes} bytes)."*
66
+
67
+ End your turn. No follow-up call needed.
@@ -0,0 +1,117 @@
1
+ ---
2
+ name: join
3
+ description: Use when the user wants to JOIN an existing Composer doc — pasting a `usecomposer.app/r/<id>` URL, running `/composer:join`, or otherwise asking you to attach to a room they already have. Covers first-run agent-name prompt, the `composer_join_room` call, the ordered `step1_sayToUser` / `step2_callTool` return, and the monitor-subagent handoff.
4
+ ---
5
+
6
+ # Composer — Join a doc
7
+
8
+ Composer is a real-time collaborative markdown editor. `composer_join_room`
9
+ attaches you to an existing room so you can read, comment, suggest, and
10
+ respond to mentions.
11
+
12
+ ## When to load this skill
13
+
14
+ - The user pastes a `usecomposer.app/r/<id>` URL.
15
+ - They run `/composer:join <url>` (the slash command delegates here).
16
+ - They ask you to "watch this Composer doc" / "join this room" with a
17
+ URL or roomId.
18
+
19
+ If they want to spin up a NEW doc from markdown they've shown you, that's
20
+ **create**, not join — load `composer:create` instead.
21
+
22
+ ## Steps
23
+
24
+ ### 1. First-run only — agree on a name
25
+
26
+ If the MCP returns an "ask the user what to call you" error, no
27
+ `actingAs` name is saved on this machine yet. Stop and ask. Suggest one
28
+ default:
29
+
30
+ - If you know the user's first name → `"<FirstName>'s Agent"` (e.g.
31
+ `"Josh's Agent"`).
32
+ - Otherwise something playful that isn't a model family — `Monty`,
33
+ `Gerty`, `Rosie`, `Otto`, `Pip`. **Never** suggest Claude, Gemini,
34
+ Sonnet, Opus, Haiku, GPT, or any other model name.
35
+
36
+ Phrase: *"I'll go by Monty in Composer docs — sound good, or pick your
37
+ own?"* Retry with their answer as `actingAs`. The name persists to
38
+ `~/.composer/user.json` and is reused forever.
39
+
40
+ ### 2. Resolve the URL
41
+
42
+ - If the user pasted a `https://usecomposer.app/r/<id>` URL, use it.
43
+ - If `/composer:join` was run with no argument, ask: *"Which Composer
44
+ doc should I join? Paste the URL."* Stop. Do not guess.
45
+ - A bare roomId (no `https://` prefix) is also acceptable.
46
+
47
+ ### 3. Call `composer_join_room`
48
+
49
+ ```
50
+ composer_join_room({ url: "<the URL or roomId>" })
51
+ ```
52
+
53
+ ### 4. Honor the ordered return
54
+
55
+ Success returns two ordered steps:
56
+
57
+ 1. **`step1_sayToUser`** — output this **first**. It confirms the URL
58
+ the user just joined and carries the `@<your-name>` tagging hint.
59
+ Light paraphrasing OK; don't drop the URL or the mention syntax.
60
+ 2. **`step2_callTool`** — a structured `{ tool, args, why }` directive
61
+ pointing at `composer_next_event`. **Do not run it inline.** Spawn
62
+ the monitor subagent (next step), then end your turn.
63
+
64
+ ### 5. Spawn the monitor subagent
65
+
66
+ Use the `Agent` tool with:
67
+
68
+ - `subagent_type: "general-purpose"`
69
+ - `run_in_background: true`
70
+ - `description: "Composer monitor: <roomId>"`
71
+ - `prompt`: tell the subagent to **invoke the `composer:monitor`
72
+ skill**, then run the loop on `{roomId}` as `{actingAs}`. Don't paste
73
+ the loop rules inline — the `composer:monitor` skill carries them
74
+ (spawn template, mention filtering, exit rules, event payload).
75
+
76
+ Once spawned, **end your turn immediately**. Do not output any text
77
+ after the spawn — no closing recap, no "monitor is running" status
78
+ line, no restating the mention syntax. The host already shows a
79
+ status indicator for the backgrounded `Agent` tool call; that's the
80
+ user's confirmation. Anything you say after `step1_sayToUser` just
81
+ duplicates it. The protocol is strict:
82
+
83
+ 1. Output `step1_sayToUser` (verbatim or lightly paraphrased).
84
+ 2. Spawn the `Agent`.
85
+ 3. End turn. No closing remark.
86
+
87
+ Also: do **not** poll `composer_next_event` from the main thread —
88
+ two listeners on the same room means duplicated replies.
89
+
90
+ ### When the monitor exits
91
+
92
+ You'll get a notification when the background subagent finishes. Its
93
+ final output line is the agent's goodbye — written in the agent's
94
+ voice (idle timeout, server kick, reconnect aborted, etc., per the
95
+ `composer:monitor` exit rules). **Relay that line verbatim to the user
96
+ in the main thread** so they see why the agent left and how to bring
97
+ it back. Don't paraphrase or wrap it in extra explanation; the
98
+ goodbye line already carries the next step (`/composer:join` etc.).
99
+
100
+ ## Terminal-side asks after joining
101
+
102
+ The main thread is also attached to the room. If the user asks something
103
+ in the terminal like *"reply to that thread saying we'll ship Friday"*,
104
+ call the write tools from here and report back concisely. Don't hand
105
+ terminal directives to the monitor subagent — the subagent handles
106
+ in-doc mentions, the main thread handles in-terminal asks. They share
107
+ the same MCP, so writes from either show up in the doc.
108
+
109
+ For comment etiquette, load **`composer:commenting`**. For suggestion
110
+ craft, load **`composer:suggesting`**.
111
+
112
+ ## Failure handling
113
+
114
+ - **Setup not done** → `npx @composer-app/mcp@latest setup`, restart the CLI.
115
+ - **Server kicked you (4403/4410) or reconnect aborted** → the agent
116
+ left the room. Run `/composer:join <url>` to bring it back. The kick
117
+ reason is in the error message.
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: monitor
3
+ description: Use when you're running the Composer monitor loop — spawned as a subagent off `composer_create_room` / `composer_join_room`, or otherwise about to call `composer_next_event`. Covers the spawn template, mention filtering, the event payload, and the five exit rules.
4
+ ---
5
+
6
+ # Composer monitor loop
7
+
8
+ Composer's monitor loop is **always-on**: every return from
9
+ `composer_next_event` carries a directive (a mention to handle, a
10
+ timeout to noop on, or a goodbye to honor). You execute the directive
11
+ and immediately call the tool again. The doc IS your conversation —
12
+ do not narrate to the parent between ticks.
13
+
14
+ This skill is for the agent **inside** the loop. The parent host that
15
+ spawns you uses `composer:create` (after `composer_create_room`) or
16
+ `composer:join` (after `composer_join_room`).
17
+
18
+ ## How the parent spawns you
19
+
20
+ The parent skill tells the host to spawn an `Agent` with:
21
+
22
+ - `subagent_type: "general-purpose"`
23
+ - `run_in_background: true`
24
+ - `description: "Composer monitor: <roomId>"`
25
+ - prompt: a short brief that includes the room's `{roomId}`, the
26
+ agent's `{actingAs}`, and the five exit rules below.
27
+
28
+ ## Exit rules
29
+
30
+ When you exit, your **final output line is the user-visible goodbye** —
31
+ the parent surfaces it back to the main thread. Voice: first-person,
32
+ casual, the same agent saying goodbye. Not a system notification, not
33
+ a stack trace. **For terminal exits #1, #3, #4, the goodbye text is
34
+ returned by the MCP as `userMessage` — print it verbatim, do not
35
+ paraphrase.** Exit #5 (unrecoverable error) has no MCP-supplied text
36
+ because the loop never returned cleanly; use the canonical line below.
37
+
38
+ > Exit and return when ANY of these happen:
39
+ >
40
+ > **1. Idle timeout.** `composer_next_event` returns `kind: "timeout"`
41
+ > with `recentActivity: false`, `userMessage`, and `instruction` (no
42
+ > `requiredNextToolCall`). The doc has been silent for the configured
43
+ > quiet window. Do **not** post anything in the doc — awareness has
44
+ > already cleared. Print `userMessage` verbatim to the terminal, then
45
+ > exit.
46
+ >
47
+ > **2. Doc-side handoff.** A request inside the doc clearly needs the
48
+ > parent terminal (a code change, a shell command, an external action
49
+ > the parent would do). Post this short reply **in the thread**, then
50
+ > exit with a one-sentence summary of the ask:
51
+ >
52
+ > > *Let's chat more about this in our connected session.*
53
+ >
54
+ > **3. Server kicked the client.** Close code `4403` (old MCP), `4410`
55
+ > (kill switch), or HTTP 403 on upgrade. `composer_next_event` returns
56
+ > `kind: "timeout"` with `userMessage` carrying the kick goodbye. Print
57
+ > `userMessage` verbatim and exit; don't post in the doc — you're
58
+ > being told to leave the room.
59
+ >
60
+ > **4. Reconnect aborted.** The client circuit breaker tripped after 15
61
+ > consecutive failed reconnects (network down, server unreachable).
62
+ > Same shape as #3 — `composer_next_event` returns
63
+ > `kind: "timeout"` with the appropriate `userMessage`. Print verbatim
64
+ > and exit.
65
+ >
66
+ > **5. Unrecoverable mid-loop error.** Auth lost, room destroyed, or
67
+ > any other thrown error not covered above. The MCP can't supply a
68
+ > `userMessage` because the call didn't return cleanly. Print the
69
+ > error AND this line, then exit:
70
+ >
71
+ > > *Something went wrong and I had to stop. Try running
72
+ > > `/composer:join` in a bit to bring me back.*
73
+
74
+ Default `composer_next_event` timeout is 30s. Don't shorten it
75
+ arbitrarily.
76
+
77
+ ## Inside the loop
78
+
79
+ Every return carries a structured directive. Follow it without waiting
80
+ for user input.
81
+
82
+ ### `kind: "mention"`
83
+
84
+ Handle the event (reply / suggestion / resolve as needed), then execute
85
+ `requiredNextToolCall` — which is another `composer_next_event` call.
86
+ Do not pause to acknowledge.
87
+
88
+ ### `kind: "timeout"` with `recentActivity: true`
89
+
90
+ The user was working in the doc recently — they just didn't tag you.
91
+ The return includes `requiredNextToolCall`. Execute it. Stay in the
92
+ loop.
93
+
94
+ ### `kind: "timeout"` without `requiredNextToolCall`
95
+
96
+ The return has `userMessage` and `instruction`. This branch covers
97
+ **three** exits (rules 1, 3, 4) — the MCP picks the right `userMessage`
98
+ based on whether the doc went silent (idle), the server kicked us
99
+ (close code 4403/4410/HTTP 403), or the reconnect breaker tripped
100
+ (15 consecutive failed retries). You don't need to distinguish the
101
+ three: print `userMessage` verbatim to the terminal and exit. Do not
102
+ post in the doc.
103
+
104
+ ## The mention event payload
105
+
106
+ ```
107
+ {
108
+ kind: "mention",
109
+ threadId: "...",
110
+ threadKind: "comment" | "suggestion",
111
+ threadText: "...", // the exact message that triggered you
112
+ replyId?: "...", // present when this is a reply on an existing thread
113
+ reason: "direct_mention" | "active_thread" | "solo_room",
114
+ anchoredText?: "...", // the doc text the thread is anchored to
115
+ headingId?: "...", // the section's headingId (use with write tools)
116
+ headingText?: "...",
117
+ sectionMarkdown?: "..." // full containing section as markdown
118
+ }
119
+ ```
120
+
121
+ Use `headingId` + `anchoredText` directly when calling
122
+ `composer_add_suggestion` or `composer_add_comment` — no extra
123
+ `composer_get_section` is needed in the common case. Reach for
124
+ `sectionMarkdown` to understand surrounding context before replying.
125
+
126
+ **The event only carries the triggering message.** If the thread already
127
+ has replies, call `composer_get_thread({ roomId, threadId })` before
128
+ replying. The return has every reply with author and timestamp —
129
+ essential when the user tagged you mid-conversation.
130
+
131
+ ## `reason` is your main filter
132
+
133
+ - **`"direct_mention"`** — sidecar or text explicitly tagged you. Always
134
+ reply (unless the content is purely a thank-you that doesn't need an
135
+ answer — never emit empty acknowledgements).
136
+ - **`"active_thread"`** — a plain reply on a thread you're already in.
137
+ Reply if the content invites one; skip if it's plainly addressed to
138
+ another person, is a thank-you, or is otherwise a conversational
139
+ dead-end.
140
+ - **`"solo_room"`** — you're alone with one human who didn't tag anyone.
141
+ **Default to a helpful reply** — they almost certainly want your
142
+ input. Skip only when the text reads like:
143
+ - a **note-to-self** ("TODO: fix this later", "remember to check the
144
+ date"),
145
+ - a bare **acknowledgement** ("k", "got it", "done"),
146
+ - a stage direction / aside ("ugh", "hmm"),
147
+ - or anything that visibly isn't pointed at you (quoted text, drafts
148
+ they're jotting down).
149
+ When in doubt, reply — the user can always ignore you.
150
+
151
+ ## Where to go for richer rules
152
+
153
+ - About to **reply on a thread** with text? Load **`composer:commenting`**
154
+ — terseness rules and the suggestion-replaces-reply pattern.
155
+ - About to **post a suggestion**? Load **`composer:suggesting`** — span
156
+ scoping, ripple coverage, anchor mechanics.
157
+
158
+ Don't try to reproduce those skills' rules from memory; load them when
159
+ the situation matches.
@@ -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
- }