Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/mcp/src/generated/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2286,7 +2286,7 @@ export const MCP_TOOL_CATALOG = {
{
toolName: 'superdoc_comment',
description:
'Manage document comment threads: create, read, update, and delete. To create a comment, first use superdoc_search to find the target text, then pass action "create" with the comment text and a target: {kind:"text", blockId:"<blockId>", range:{start:<N>, end:<N>}} using the blockId and highlightRange from the search result. For threaded replies, pass "parentId" with the parent comment ID. Action "list" returns all comments with optional pagination (limit, offset) and filtering (includeResolved:true to include resolved). Action "get" retrieves a single comment by ID. Action "update" changes status to "resolved" or marks as internal. Action "delete" removes a comment or reply by ID. Do NOT pass "ref", "id", or "parentId" when creating a new top-level comment; only "action", "text", and "target" are needed.\n\nEXAMPLES:\n 1. {"action":"create","text":"Please review this section.","target":{"kind":"text","blockId":"<blockId>","range":{"start":5,"end":25}}}\n 2. {"action":"list","limit":20,"offset":0}\n 3. {"action":"update","id":"<commentId>","status":"resolved"}\n 4. {"action":"delete","id":"<commentId>"}',
'Manage document comment threads: create, read, update, and delete. To create a comment, first use superdoc_search to find the target text, then pass action "create" with the comment text and a target built from items[0].blocks. For a single-block match use {kind:"text", blockId: items[0].blocks[0].blockId, range: items[0].blocks[0].range}. For a cross-block match use {kind:"text", segments: items[0].blocks.map(b => ({blockId: b.blockId, range: b.range}))}. Do NOT use items[0].highlightRange (snippet-relative, not block-relative) or items[0].target (a SelectionTarget, not accepted by comments.create). For threaded replies, pass "parentId" with the parent comment ID. Action "list" returns all comments with optional pagination (limit, offset) and filtering (includeResolved:true to include resolved). Action "get" retrieves a single comment by ID. Action "update" changes status to "resolved" or marks as internal. Action "delete" removes a comment or reply by ID. Do NOT pass "ref", "id", or "parentId" when creating a new top-level comment; only "action", "text", and "target" are needed.\n\nEXAMPLES:\n 1. {"action":"create","text":"Please review this section.","target":{"kind":"text","blockId":"<blockId>","range":{"start":5,"end":25}}}\n 2. {"action":"list","limit":20,"offset":0}\n 3. {"action":"update","id":"<commentId>","status":"resolved"}\n 4. {"action":"delete","id":"<commentId>"}',
inputSchema: {
type: 'object',
properties: {
Expand Down
2 changes: 1 addition & 1 deletion apps/mcp/src/generated/mcp-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ superdoc_search({select: {type: "text", pattern: "target phrase"}, require: "fir
superdoc_comment({
action: "create",
text: "Please review this section.",
target: {kind: "text", blockId: "<blocks[0].blockId>", range: {start: <highlightRange.start>, end: <highlightRange.end>}}
target: {kind: "text", blockId: "<items[0].blocks[0].blockId>", range: {start: <items[0].blocks[0].range.start>, end: <items[0].blocks[0].range.end>}}
})
\`\`\`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ export const INTENT_GROUP_META: Record<string, IntentGroupMeta> = {
toolName: 'superdoc_comment',
description:
'Manage document comment threads: create, read, update, and delete. ' +
'To create a comment, first use superdoc_search to find the target text, then pass action "create" with the comment text and a target: {kind:"text", blockId:"<blockId>", range:{start:<N>, end:<N>}} using the blockId and highlightRange from the search result. ' +
'To create a comment, first use superdoc_search to find the target text, then pass action "create" with the comment text and a target built from items[0].blocks. For a single-block match use {kind:"text", blockId: items[0].blocks[0].blockId, range: items[0].blocks[0].range}. For a cross-block match use {kind:"text", segments: items[0].blocks.map(b => ({blockId: b.blockId, range: b.range}))}. Do NOT use items[0].highlightRange (snippet-relative, not block-relative) or items[0].target (a SelectionTarget, not accepted by comments.create). ' +
'For threaded replies, pass "parentId" with the parent comment ID. ' +
'Action "list" returns all comments with optional pagination (limit, offset) and filtering (includeResolved:true to include resolved). ' +
'Action "get" retrieves a single comment by ID. Action "update" changes status to "resolved" or marks as internal. Action "delete" removes a comment or reply by ID. ' +
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/langs/browser/src/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ superdoc_search({select: {type: "text", pattern: "target phrase"}, require: "fir
superdoc_comment({
action: "create",
text: "Please review this section.",
target: {kind: "text", blockId: "<blocks[0].blockId>", range: {start: <highlightRange.start>, end: <highlightRange.end>}}
target: {kind: "text", blockId: "<items[0].blocks[0].blockId>", range: {start: <items[0].blocks[0].range.start>, end: <items[0].blocks[0].range.end>}}
})
\`\`\`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ superdoc_search({select: {type: "text", pattern: "target phrase"}, require: "fir
superdoc_comment({
action: "create",
text: "Please review this section.",
target: {kind: "text", blockId: "<blocks[0].blockId>", range: {start: <highlightRange.start>, end: <highlightRange.end>}}
target: {kind: "text", blockId: "<items[0].blocks[0].blockId>", range: {start: <items[0].blocks[0].range.start>, end: <items[0].blocks[0].range.end>}}
})
```

Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/tools/system-prompt-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ superdoc_search({select: {type: "text", pattern: "target phrase"}, require: "fir
superdoc_comment({
action: "create",
text: "Please review this section.",
target: {kind: "text", blockId: "<blocks[0].blockId>", range: {start: <highlightRange.start>, end: <highlightRange.end>}}
target: {kind: "text", blockId: "<items[0].blocks[0].blockId>", range: {start: <items[0].blocks[0].range.start>, end: <items[0].blocks[0].range.end>}}
})
```

Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/tools/system-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ superdoc_search({select: {type: "text", pattern: "target phrase"}, require: "fir
superdoc_comment({
action: "create",
text: "Please review this section.",
target: {kind: "text", blockId: "<blocks[0].blockId>", range: {start: <highlightRange.start>, end: <highlightRange.end>}}
target: {kind: "text", blockId: "<items[0].blocks[0].blockId>", range: {start: <items[0].blocks[0].range.start>, end: <items[0].blocks[0].range.end>}}
})
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -871,3 +871,54 @@ describe('queryMatchAdapter — cardinality', () => {
expect(result.total).toBe(2);
});
});

// ---------------------------------------------------------------------------
// Wire-shape regression guard
//
// Pins the public contract of items[] so callers (LLM tool prompts, agent
// guides) cannot drift back to fields that do not exist on the wire. The
// `superdoc_comment` MCP description previously told agents to use
// `items[0].context.textRanges[0]`, which is a field on `doc.find` output
// (the legacy sibling) and not on `doc.query.match` (where `superdoc_search`
// dispatches). Comments use `TextAddress | TextTarget` built from
// `items[0].blocks[*]`.
// ---------------------------------------------------------------------------

describe('queryMatchAdapter — wire-shape contract', () => {
beforeEach(() => {
vi.clearAllMocks();
mockedDeps.getRevision.mockReturnValue('rev-1');
});

it('emits items without a `context` field; range data lives on blocks[i]', () => {
const candidates = [{ nodeId: 'p1', pos: 0, end: 22, text: 'Title Body text here' }];
const editor = makeEditorWithBlocks(candidates);
setupBlockIndex(candidates.map(({ nodeId, pos, end }) => ({ nodeId, pos, end })));
setupFindResult({
matches: [{ kind: 'text', blockId: 'p1' }],
context: [{ textRanges: [{ kind: 'text', blockId: 'p1', range: { start: 5, end: 17 } }] }],
total: 1,
});
mockedDeps.captureRunsInRange.mockReturnValue(captured([capturedRun(5, 17, [])]));

const result = queryMatchAdapter(editor, {
select: { type: 'text', pattern: 'Body text he' },
});

const item = result.items[0] as any;

// The wire output of `query.match` has no `context` and no `textRanges`.
// Adding either would silently re-enable the previously-broken agent
// guidance that pointed at `items[0].context.textRanges[0]`.
expect(item.context).toBeUndefined();
expect(item.textRanges).toBeUndefined();

// The block-relative range a comment target needs lives on blocks[i].range.
expect(item.blocks[0].blockId).toBe('p1');
expect(item.blocks[0].range).toEqual({ start: 5, end: 17 });

// `target` is a SelectionTarget (kind:'selection'), which `comments.create`
// does NOT accept (it takes TextAddress | TextTarget, kind:'text').
expect(item.target.kind).toBe('selection');
});
});
Loading