agentic-comments 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +272 -2
- package/SKILL.md +35 -0
- package/dist/chunk-C3P5TAC3.js +770 -0
- package/dist/chunk-C43HLFQE.js +262 -0
- package/dist/chunk-RWMBGIAS.js +7 -0
- package/dist/chunk-UE5ULEXU.js +86 -0
- package/dist/chunk-ZCM2UDLN.js +172 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +205 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +223 -0
- package/dist/local.d.ts +112 -0
- package/dist/local.js +23 -0
- package/dist/mcp-BOJM5NE6.js +11 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +21 -0
- package/dist/suggestions-U8tXRLZ5.d.ts +334 -0
- package/llms.txt +15 -0
- package/package.json +76 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pooria Arab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,273 @@
|
|
|
1
|
-
#
|
|
1
|
+
# agentic-comments
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://opensource.org/license/mit)
|
|
4
|
+
|
|
5
|
+
[Source](https://github.com/pooriaarab/agentic-comments) · [Support](https://github.com/pooriaarab/agentic-comments/issues)
|
|
6
|
+
|
|
7
|
+
Comment threads and suggestions anchored to any content, from people and agents.
|
|
8
|
+
|
|
9
|
+
> Status: see [npm](https://www.npmjs.com/package/agentic-comments) for the latest released version. The
|
|
10
|
+
> `main` branch can have changes that npm does not have yet.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
Requires Node.js 20 or later. `yjs` is a peer dependency. Your app and this package must use the same
|
|
15
|
+
copy of `yjs`, because the package checks the Yjs types of your document. Install both:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install agentic-comments yjs
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
To build from source, see Develop.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import * as Y from "yjs";
|
|
27
|
+
import { Comments, stamp, textAnchor } from "agentic-comments";
|
|
28
|
+
|
|
29
|
+
const doc = new Y.Doc();
|
|
30
|
+
doc.getText("body").insert(0, "Hello brave world");
|
|
31
|
+
|
|
32
|
+
const comments = new Comments(doc);
|
|
33
|
+
const ada = stamp({ kind: "human", name: "ada" });
|
|
34
|
+
const scribe = stamp({ kind: "agent_of_human", name: "scribe" });
|
|
35
|
+
|
|
36
|
+
const thread = comments.add({ anchor: textAnchor(doc.getText("body"), 6, 11), body: "Word choice? @scribe" }, ada);
|
|
37
|
+
comments.reply(thread.id, { body: "I suggest a shorter word." }, scribe);
|
|
38
|
+
comments.resolve(thread.id, ada);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The `Comments` object lives on a Yjs document. Send the document updates with your
|
|
42
|
+
normal sync, and every replica sees the same threads.
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- Threads anchored to a text range, a cell key, a slide element, or a canvas element.
|
|
47
|
+
You can add your own anchor type.
|
|
48
|
+
- A text anchor uses Yjs relative positions, so it follows its text through edits.
|
|
49
|
+
- Messages with mentions. Resolve and reopen a thread.
|
|
50
|
+
- Suggestions: a change your app proposes. A person, or an agent with the accept scope, accepts it.
|
|
51
|
+
An agent never accepts or rejects its own. Two replicas that accept at once apply it once.
|
|
52
|
+
- The host stamps every author. An operation never reads an author from its input.
|
|
53
|
+
- Every message keeps the author kind, so agent comments stay marked as machine-made.
|
|
54
|
+
- Replies and status merge across replicas. Two replicas that resolve at once agree.
|
|
55
|
+
- A malformed thread from a bad replica is skipped. A bad message is left out, and `skipped` on the
|
|
56
|
+
thread counts those. `problems()` names them.
|
|
57
|
+
|
|
58
|
+
## When the content changes
|
|
59
|
+
|
|
60
|
+
Every thread and suggestion has an `anchorState`:
|
|
61
|
+
|
|
62
|
+
| State | Meaning |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `attached` | The content is there, as it was. |
|
|
65
|
+
| `edited` | A text range is there, with other text. `currentText` shows it. |
|
|
66
|
+
| `orphaned` | The content is gone. The thread stays readable and takes replies. |
|
|
67
|
+
| `unknown` | Nothing can tell. A cell, slide, canvas, or custom anchor has no host answer. |
|
|
68
|
+
|
|
69
|
+
- A text anchor follows its text through edits by any replica. Deleting the whole range orphans it.
|
|
70
|
+
- For other anchors, pass `exists(anchor)` to `new Comments`. Return `true` or `false`. Any other answer,
|
|
71
|
+
or a throw, means `unknown`.
|
|
72
|
+
- `list({ anchorState: "orphaned" })` finds threads to review.
|
|
73
|
+
- `relocate(threadId, actor)` moves a text anchor to its quote when the quote occurs exactly once in the
|
|
74
|
+
same text. It never guesses. It throws `RELOCATE_FAILED` for no match or many.
|
|
75
|
+
- Accept refuses a suggestion whose anchor is orphaned or edited. The suggestion stays pending, and a
|
|
76
|
+
person can reject it.
|
|
77
|
+
- `currentText` is document text. It can hold any character, so a UI should treat it as untrusted.
|
|
78
|
+
|
|
79
|
+
## Suggestions
|
|
80
|
+
|
|
81
|
+
Your app owns the change. This package stores it as JSON, tracks the decision, and calls your `apply`.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { ACCEPT_SCOPE, Suggestions } from "agentic-comments";
|
|
85
|
+
|
|
86
|
+
const suggestions = new Suggestions(comments, {
|
|
87
|
+
apply(change, { anchor, by, ydoc }) {
|
|
88
|
+
// Edit ydoc here. Throw before you edit when the change cannot apply.
|
|
89
|
+
return { /* a receipt, JSON that lets revert undo this edit */ };
|
|
90
|
+
},
|
|
91
|
+
revert(receipt, { ydoc }) {
|
|
92
|
+
// Undo a duplicate edit. Every replica runs this at once, so use an edit that merges to one result.
|
|
93
|
+
// Throw before you change anything.
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const s = suggestions.add({ anchor, change: { replacement: "bold" }, summary: "Word choice" }, scribe);
|
|
98
|
+
suggestions.accept(s.id, ada);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- A signed-in person (`human`) accepts or rejects. Every other kind, including `user_token`, needs the
|
|
102
|
+
`comments:accept` scope, and never decides a suggestion it wrote. Names match exactly, in Unicode NFC form (a name in another form is refused), and two
|
|
103
|
+
principals that are not signed-in people with the same name count as one, whatever their kinds.
|
|
104
|
+
- Accept checks that a text anchor still matches the document, then runs the edit and writes the
|
|
105
|
+
record in one step. If `apply` throws before it edits, the suggestion stays pending. If it edits and
|
|
106
|
+
then fails, the decision is recorded without a receipt, and `problems()` reports it. A host that cannot undo a double apply must say `idempotent: true`.
|
|
107
|
+
- Every replica of a document must use the same host mode: the same `revert`, or the same `idempotent`
|
|
108
|
+
flag. A mix can leave a duplicate edit in the document.
|
|
109
|
+
- `problems()` entries about an edit that failed after it landed, or a duplicate with no receipt, live in
|
|
110
|
+
memory on the replica that saw them. Other replicas and a reload do not show them.
|
|
111
|
+
- A decision by an agent with the accept scope is final for everyone, including people.
|
|
112
|
+
- An accept on a replica that has not yet seen a reject wins, even when a person made the reject long before.
|
|
113
|
+
The window is any time the replicas were apart. An agent with the scope can override a person this way.
|
|
114
|
+
A host that wants the person to win can accept only while its replica is in sync.
|
|
115
|
+
- Two replicas that accept before they meet each apply the edit. After the merge, the application of
|
|
116
|
+
the lowest client id stays and `revert` undoes each other one. An accept and a reject at once end as
|
|
117
|
+
accepted.
|
|
118
|
+
- A receipt is stored data. A peer that can write comment data can choose one, and can rewrite the
|
|
119
|
+
`anchor` to match. Check the receipt in `revert` before you use it. The sync layer's write control is
|
|
120
|
+
the real protection. A record with no receipt (an `apply` that edited and
|
|
121
|
+
then failed) always stays, and the other application is undone. If two records have none, the extra
|
|
122
|
+
edit cannot be undone. `problems()` reports it. With `idempotent: true`, a duplicate is never undone.
|
|
123
|
+
- `apply` and `revert` must be synchronous, and must edit only the Yjs document. `accept` cannot run
|
|
124
|
+
inside another Yjs transaction, so not inside an observer or an `apply`.
|
|
125
|
+
- A peer that can write the document can write a decision record. Records with a bad author, time, or
|
|
126
|
+
receipt, or from a decider who wrote the suggestion as an agent, are not counted. A well-formed forged
|
|
127
|
+
record is, and it can win settle, so every replica undoes the real edit. The sync layer must decide who
|
|
128
|
+
may write.
|
|
129
|
+
|
|
130
|
+
## CLI and MCP server
|
|
131
|
+
|
|
132
|
+
Both run on a local store: one file that holds a Yjs document. The text is in the `text` root, and
|
|
133
|
+
non-text values are in the `values` map. A file starts with a magic mark and a hash, so a damaged or zero-filled file is refused and not read as empty. A write
|
|
134
|
+
takes a lock file, saves with a rename, and runs again if the file changed meanwhile, so parallel processes
|
|
135
|
+
on one machine lose nothing. A full disk or a file size limit fails the write and leaves the old file. A lock left by a dead process, or by this process under another mark, is cleared. So is an empty lock older
|
|
136
|
+
than two seconds and any lock older than a minute. The store is for one
|
|
137
|
+
machine. It cannot check a lock held in another container or on another machine. Two containers that
|
|
138
|
+
share a volume can have the same process id, and each then clears the other's lock, so they can lose writes. A save that
|
|
139
|
+
would pass 50 MB is refused.
|
|
140
|
+
|
|
141
|
+
If the version you install has no `agentic-comments` command, build from source (see [Develop](#develop)).
|
|
142
|
+
In the cloned repo, run `npm run build` and use `node dist/cli.js` where these examples say
|
|
143
|
+
`npx agentic-comments`.
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
export AGENTIC_COMMENTS_FILE=./comments.ydoc
|
|
147
|
+
export AGENTIC_COMMENTS_AS=human:ada # kind:name. The operator sets this.
|
|
148
|
+
npx agentic-comments init --text "Hello brave world"
|
|
149
|
+
npx agentic-comments comment --text-quote brave --body "Word choice? @scribe"
|
|
150
|
+
npx agentic-comments list --status open
|
|
151
|
+
AGENTIC_COMMENTS_AS=agent_of_human:scribe npx agentic-comments suggest --text-quote brave --replace bold
|
|
152
|
+
npx agentic-comments accept <id>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- Commands: `init`, `list`, `show`, `comment`, `reply`, `resolve`, `reopen`, `relocate`, `suggestions`,
|
|
156
|
+
`suggest`, `accept`, `reject`, `mcp`. Run `agentic-comments --help` for flags.
|
|
157
|
+
- Every result is JSON. An error is JSON on stderr with a `code`, and the exit code is 1. A usage error
|
|
158
|
+
exits 2.
|
|
159
|
+
- The principal comes from `--as` or `AGENTIC_COMMENTS_AS`. Scopes come only from
|
|
160
|
+
`AGENTIC_COMMENTS_SCOPES`, a comma list such as `comments:accept`. No flag takes a scope or an author,
|
|
161
|
+
and a flag that does not apply to a command is refused. `AGENTIC_COMMENTS_LOCK_TIMEOUT_MS` sets the wait
|
|
162
|
+
for a lock, in whole milliseconds.
|
|
163
|
+
- `npx agentic-comments mcp` (or the `agentic-comments-mcp` binary, which reads the same environment) starts
|
|
164
|
+
the MCP server on stdio. The operator fixes the principal and the
|
|
165
|
+
scopes when the server starts. No tool takes an author or a scope. A call with any key a tool does not list is refused, so an agent
|
|
166
|
+
cannot pick its own kind or rights. Tools: `list_threads`, `get_thread`, `add_comment`, `reply_to_thread`, `resolve_thread`,
|
|
167
|
+
`reopen_thread`, `relocate_thread`, `list_suggestions`, `suggest_change`, `accept_suggestion`,
|
|
168
|
+
`reject_suggestion`.
|
|
169
|
+
- A text change is `{"replace": "new text"}`. A change for a cell, slide, or canvas anchor is
|
|
170
|
+
`{"value": <JSON>}`.
|
|
171
|
+
- From code: `import { FileStore, CommentsService, operatorActor } from "agentic-comments/local"`.
|
|
172
|
+
|
|
173
|
+
## Notifications
|
|
174
|
+
|
|
175
|
+
`Notifications` turns changes into events that your app can send to a notifier. It watches the data, so it
|
|
176
|
+
sees changes from other replicas too. It replays nothing from before you created it. A first load into an
|
|
177
|
+
empty document looks like new changes, so create the stream with `muted: true` and call `unmute()` after
|
|
178
|
+
that load.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { Notifications } from "agentic-comments";
|
|
182
|
+
|
|
183
|
+
const notifications = new Notifications(comments, suggestions, { muted: true, onError: (error, source) => report(error, source) });
|
|
184
|
+
// ...load the document or connect the provider, then:
|
|
185
|
+
notifications.unmute();
|
|
186
|
+
const stop = notifications.subscribe(async (event) => {
|
|
187
|
+
if (event.type === "mention") await notify(event.mention, event.threadId); // fetch the text with the ids
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
- Events: `thread.created`, `message.added`, `mention` (one per handle), `thread.resolved`,
|
|
192
|
+
`thread.reopened`, `suggestion.created`, `suggestion.accepted`, `suggestion.rejected`.
|
|
193
|
+
- An event holds ids, the actor, `machineMade`, the anchor type, `source` (`local` or `remote`), and a time.
|
|
194
|
+
It never holds the text of a comment, a suggestion, or a change. Fetch that with the ids.
|
|
195
|
+
- The same change has the same `id` on every replica. So a notifier that must
|
|
196
|
+
send once keeps the ids it has sent. Within one replica, an id is delivered at most once, and a listener
|
|
197
|
+
that throws is not retried. A replica remembers its last 10,000 ids (`seenLimit`) and drops a
|
|
198
|
+
repeat within that window. Two accepts at once give one id, and the actor can differ between replicas. A `suggestion.rejected` event
|
|
199
|
+
can be overturned: if another replica accepted at the same time, the merge ends as accepted, and a
|
|
200
|
+
`suggestion.accepted` event follows.
|
|
201
|
+
An id for a status change includes the clock time, so a clock coarser than the changes can merge two.
|
|
202
|
+
- Use `source` to decide where to send. If every replica runs a notifier, filter on `local`, or each
|
|
203
|
+
replica sends its own copy of every remote change.
|
|
204
|
+
- Events are delivered after the Yjs transaction that caused them has finished, so a listener can write to
|
|
205
|
+
the document. A listener that throws, or returns a rejected promise, cannot break a write or another
|
|
206
|
+
listener. The
|
|
207
|
+
error goes to `onError`, or to `errors()`.
|
|
208
|
+
- Write a thread in one Yjs transaction, as `add` does. A thread whose map arrives empty and gets its fields
|
|
209
|
+
and messages in one later transaction is treated as created then. A thread built in more steps than that
|
|
210
|
+
can make no event.
|
|
211
|
+
- A peer that deletes a thread or a suggestion and adds it back under the same id makes a `created` event
|
|
212
|
+
again once the id has left the `seenLimit` window.
|
|
213
|
+
- A change that arrives as a whole thread, with its replies and status inside, makes an event for each
|
|
214
|
+
message and for a resolved status. A reopen inside such a thread makes none. A message past the thread's read limit makes no event.
|
|
215
|
+
|
|
216
|
+
## Author kinds
|
|
217
|
+
|
|
218
|
+
| Kind | Meaning | Label |
|
|
219
|
+
| --- | --- | --- |
|
|
220
|
+
| `human` | A person signed in. | person |
|
|
221
|
+
| `user_token` | A person, through a token an agent holds. | person via agent |
|
|
222
|
+
| `agent_of_human` | An agent that acts for a person. | agent |
|
|
223
|
+
| `agent_of_agent` | An agent that acts for another agent. | agent |
|
|
224
|
+
| `agent` | An agent with its own identity. | agent |
|
|
225
|
+
|
|
226
|
+
## Limits
|
|
227
|
+
|
|
228
|
+
- No HTTP API yet. The file store is for one machine. It is not a sync server.
|
|
229
|
+
- `add` checks that a text anchor points into this document and matches its quote.
|
|
230
|
+
- The library trusts the host to call `stamp()` only for a principal it authenticated.
|
|
231
|
+
- A reply to a resolved thread is refused on the replica that sees the resolve. A reply and a
|
|
232
|
+
resolve made at once on two replicas both stay. The merged thread is resolved and holds the reply.
|
|
233
|
+
- Author and kind are only as trustworthy as every peer that can write the document. A peer can write
|
|
234
|
+
any author straight into the Yjs data. The sync layer must decide who may write.
|
|
235
|
+
- `relocate` and `exists` never touch a thread whose anchor is attached. `exists` runs for every thread
|
|
236
|
+
on every read, so keep it fast.
|
|
237
|
+
- A local reply stops at 1,000 messages. Replicas that reply at once can pass that. A read shows the first
|
|
238
|
+
2,000 valid messages. `skipped` and `problems()` report the rest.
|
|
239
|
+
- If several processes clear one stale lock at the same moment, two writes can very rarely overlap. The
|
|
240
|
+
save check catches almost every such case. A symlink as the store path is followed. A filesystem with no
|
|
241
|
+
hard links makes `init` use a check and a rename, which can race.
|
|
242
|
+
- The CLI trusts its caller: a process that can set `AGENTIC_COMMENTS_AS` and `AGENTIC_COMMENTS_SCOPES` can
|
|
243
|
+
act as anyone. Give an agent the MCP server, where the operator fixes both. Do not give it a shell with
|
|
244
|
+
the store file and the CLI.
|
|
245
|
+
- Events come from the data and not from the calls, so a status change that two replicas make at once gives
|
|
246
|
+
each replica its own event, and a thread that arrives whole gives events for its current state only.
|
|
247
|
+
- Every replica of a document must register the same custom anchor kinds.
|
|
248
|
+
- A repeated message id hides every copy. A peer that appends a copy of a thread's only message makes
|
|
249
|
+
the thread unreadable.
|
|
250
|
+
- The quoted text of a text anchor is not checked when a thread is read. A peer can store any text there.
|
|
251
|
+
`textAnchor` refuses a quote whose JSON form passes 4,096 characters. Quotes with many newlines or
|
|
252
|
+
quotation marks hit that before 2,000 characters.
|
|
253
|
+
- The first visible message decides when a thread started, so a peer that inserts an older message at
|
|
254
|
+
the front moves the thread in `list()`.
|
|
255
|
+
- A read scans every stored entry. A peer that writes millions of junk entries slows every read.
|
|
256
|
+
- A body mentions anyone named as `@handle`. Put `@` text in backticks to avoid that. Any two backticks in a body pair up, so a loose backtick
|
|
257
|
+
can hide a real mention that sits between two of them. A scoped
|
|
258
|
+
package name such as `@scope/pkg` is not a mention.
|
|
259
|
+
- Mentions are stored in lowercase, with no leading `@`. A handle holds up to 64 letters, digits, marks,
|
|
260
|
+
and `. _ -`. A longer handle in a body is refused.
|
|
261
|
+
|
|
262
|
+
## Develop
|
|
263
|
+
|
|
264
|
+
```sh
|
|
265
|
+
git clone https://github.com/pooriaarab/agentic-comments
|
|
266
|
+
cd agentic-comments
|
|
267
|
+
npm ci
|
|
268
|
+
npm run ci:local
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## License
|
|
272
|
+
|
|
273
|
+
MIT.
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentic-comments
|
|
3
|
+
description: Comment threads and suggestions anchored to any content, from people and agents. Keywords: comments,threads,mentions,suggestions,annotations,collaboration,agents,yjs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# agentic-comments
|
|
7
|
+
|
|
8
|
+
Comment threads and suggestions anchored to any content, from people and agents.
|
|
9
|
+
|
|
10
|
+
## Use the SDK
|
|
11
|
+
|
|
12
|
+
The host gives you an `Actor` that names you. Pass it to each call.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { Comments } from "agentic-comments";
|
|
16
|
+
|
|
17
|
+
const comments = new Comments(ydoc);
|
|
18
|
+
const thread = comments.add({ anchor: { type: "cell", key: "Sheet1!B4" }, body: "Check this total." }, actor);
|
|
19
|
+
comments.reply(thread.id, { body: "Fixed." }, actor);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Check `thread.anchorState`. An `orphaned` thread has lost its content. Read it, reply, or call
|
|
23
|
+
`comments.relocate(id, actor)`. Do not treat it as bad data.
|
|
24
|
+
|
|
25
|
+
Suggest a change with `suggestions.add({ anchor, change, summary }, actor)`. Accept needs a person or
|
|
26
|
+
the `comments:accept` scope. You never accept or reject your own suggestion.
|
|
27
|
+
|
|
28
|
+
Over MCP, call `list_threads`, `add_comment`, `reply_to_thread`, `resolve_thread`, `suggest_change`,
|
|
29
|
+
`accept_suggestion`, and `reject_suggestion`. You never pass an author or a scope.
|
|
30
|
+
|
|
31
|
+
## Rules
|
|
32
|
+
|
|
33
|
+
- The host stamps the author. Never call `stamp()` yourself, and never pass an author in an input.
|
|
34
|
+
- Reopen a resolved thread before you reply.
|
|
35
|
+
- Report refusals and errors as failures.
|