diff --git a/src/mcp/server.integration.test.ts b/src/mcp/server.integration.test.ts index e66b52d3..b144afb2 100644 --- a/src/mcp/server.integration.test.ts +++ b/src/mcp/server.integration.test.ts @@ -66,7 +66,6 @@ describe("MCP server integration", () => { "grove_check_stop", "grove_checkout", "grove_claim", - "grove_contribute", "grove_create_plan", "grove_create_session", "grove_discuss", @@ -85,11 +84,12 @@ describe("MCP server integration", () => { "grove_release", "grove_report_usage", "grove_reproduce", - "grove_review", "grove_search", "grove_send_message", "grove_set_goal", "grove_set_outcome", + "grove_submit_review", + "grove_submit_work", "grove_thread", "grove_threads", "grove_tree", @@ -97,9 +97,9 @@ describe("MCP server integration", () => { ]); }); - test("grove_contribute round-trip", async () => { + test("grove_submit_work round-trip", async () => { const result = await client.callTool({ - name: "grove_contribute", + name: "grove_submit_work", arguments: { kind: "work", mode: "evaluation", @@ -162,16 +162,17 @@ describe("MCP server integration", () => { expect(releaseData.status).toBe("completed"); }); - test("grove_review round-trip", async () => { + test("grove_submit_review round-trip", async () => { // Create target const target = makeContribution({ summary: "Reviewable work" }); await deps.contributionStore.put(target); const result = await client.callTool({ - name: "grove_review", + name: "grove_submit_review", arguments: { targetCid: target.cid, summary: "LGTM", + scores: { quality: { value: 0.9, direction: "maximize" } }, agent: { agentId: "reviewer" }, }, }); diff --git a/src/mcp/server.test.ts b/src/mcp/server.test.ts index f756ae6a..261f45ab 100644 --- a/src/mcp/server.test.ts +++ b/src/mcp/server.test.ts @@ -48,10 +48,10 @@ describe("createMcpServer preset scoping", () => { // --- Contribution tool names (always registered) ------------------------- const contributionTools = [ - "grove_contribute", + "grove_submit_work", + "grove_submit_review", "grove_discuss", "grove_reproduce", - "grove_review", ]; // --- Full tool list (matches integration test expectation) --------------- @@ -66,7 +66,6 @@ describe("createMcpServer preset scoping", () => { "grove_check_stop", "grove_checkout", "grove_claim", - "grove_contribute", "grove_create_plan", "grove_create_session", "grove_discuss", @@ -85,11 +84,12 @@ describe("createMcpServer preset scoping", () => { "grove_release", "grove_report_usage", "grove_reproduce", - "grove_review", "grove_search", "grove_send_message", "grove_set_goal", "grove_set_outcome", + "grove_submit_review", + "grove_submit_work", "grove_thread", "grove_threads", "grove_tree", @@ -281,7 +281,7 @@ describe("createMcpServer preset scoping", () => { expect(names).not.toContain("grove_check_stop"); // Included groups - expect(names).toContain("grove_contribute"); + expect(names).toContain("grove_submit_work"); expect(names).toContain("grove_frontier"); expect(names).toContain("grove_checkout"); expect(names).toContain("grove_send_message"); diff --git a/src/mcp/tools/bounties.ts b/src/mcp/tools/bounties.ts index 19aad743..6a99c932 100644 --- a/src/mcp/tools/bounties.ts +++ b/src/mcp/tools/bounties.ts @@ -146,16 +146,21 @@ export function registerBountyTools(server: McpServer, deps: McpDeps): void { }, ); - // --- grove_bounty_claim -------------------------------------------------- + // --- grove_bounty_claim --- DEPRECATED ------------------------------------ + // DEPRECATED: Use grove_claim instead. Agents should call grove_claim with the + // bounty ID as targetRef. grove_bounty_claim is kept for backwards compatibility. server.registerTool( "grove_bounty_claim", { description: - "Claim an open bounty. Creates a claim via the existing claim system " + - "and transitions the bounty to 'claimed' status.", + "[DEPRECATED — Use grove_claim instead] " + + "Claim an open bounty by bounty ID. " + + "grove_bounty_claim calls claim internally. Agents should use grove_claim directly.", inputSchema: claimBountySchema, }, async (args) => { + // Forward to grove_claim behavior via claimBountyOperation + // (kept for backwards compatibility; agents should use grove_claim) const result = await claimBountyOperation( { bountyId: args.bountyId, diff --git a/src/mcp/tools/contributions.test.ts b/src/mcp/tools/contributions.test.ts index 8b8e23ba..42461079 100644 --- a/src/mcp/tools/contributions.test.ts +++ b/src/mcp/tools/contributions.test.ts @@ -32,7 +32,7 @@ async function callTool( }; } -describe("grove_contribute", () => { +describe("grove_submit_work", () => { let testDeps: TestMcpDeps; let deps: McpDeps; let server: McpServer; @@ -49,12 +49,8 @@ describe("grove_contribute", () => { }); test("creates a work contribution", async () => { - const result = await callTool(server, "grove_contribute", { - kind: "work", - mode: "evaluation", + const result = await callTool(server, "grove_submit_work", { summary: "Test work", - tags: ["test"], - relations: [], artifacts: {}, agent: { agentId: "agent-1" }, }); @@ -67,12 +63,8 @@ describe("grove_contribute", () => { }); test("validates artifact hashes exist in CAS", async () => { - const result = await callTool(server, "grove_contribute", { - kind: "work", - mode: "evaluation", + const result = await callTool(server, "grove_submit_work", { summary: "Bad artifacts", - tags: [], - relations: [], artifacts: { "file.txt": "blake3:0000000000000000000000000000000000000000000000000000000000000000", }, @@ -87,12 +79,8 @@ describe("grove_contribute", () => { test("creates contribution with valid artifact hash", async () => { const hash = await storeTestContent(deps.cas, "hello world"); - const result = await callTool(server, "grove_contribute", { - kind: "work", - mode: "evaluation", + const result = await callTool(server, "grove_submit_work", { summary: "With artifact", - tags: [], - relations: [], artifacts: { "file.txt": hash }, agent: { agentId: "agent-1" }, }); @@ -103,18 +91,15 @@ describe("grove_contribute", () => { }); test("validates relation target CIDs exist", async () => { - const result = await callTool(server, "grove_contribute", { - kind: "work", - mode: "evaluation", + const result = await callTool(server, "grove_submit_work", { summary: "Bad relation", - tags: [], + artifacts: {}, relations: [ { targetCid: "blake3:0000000000000000000000000000000000000000000000000000000000000000", relationType: "derives_from", }, ], - artifacts: {}, agent: { agentId: "agent-1" }, }); @@ -128,13 +113,10 @@ describe("grove_contribute", () => { const parent = makeContribution({ summary: "Parent" }); await deps.contributionStore.put(parent); - const result = await callTool(server, "grove_contribute", { - kind: "work", - mode: "evaluation", + const result = await callTool(server, "grove_submit_work", { summary: "Child", - tags: [], - relations: [{ targetCid: parent.cid, relationType: "derives_from" }], artifacts: {}, + relations: [{ targetCid: parent.cid, relationType: "derives_from" }], agent: { agentId: "agent-1" }, }); @@ -144,7 +126,7 @@ describe("grove_contribute", () => { }); }); -describe("grove_review", () => { +describe("grove_submit_review", () => { let testDeps: TestMcpDeps; let deps: McpDeps; let server: McpServer; @@ -164,11 +146,11 @@ describe("grove_review", () => { const target = makeContribution({ summary: "Target work" }); await deps.contributionStore.put(target); - const result = await callTool(server, "grove_review", { + const result = await callTool(server, "grove_submit_review", { targetCid: target.cid, summary: "Looks good", + scores: { quality: { value: 0.8, direction: "maximize" } }, agent: { agentId: "reviewer-1" }, - tags: [], }); expect(result.isError).toBeUndefined(); @@ -178,11 +160,11 @@ describe("grove_review", () => { }); test("returns not-found for non-existent target", async () => { - const result = await callTool(server, "grove_review", { + const result = await callTool(server, "grove_submit_review", { targetCid: "blake3:0000000000000000000000000000000000000000000000000000000000000000", summary: "Review of nothing", + scores: { quality: { value: 0.5, direction: "maximize" } }, agent: { agentId: "reviewer-1" }, - tags: [], }); expect(result.isError).toBe(true); @@ -193,7 +175,7 @@ describe("grove_review", () => { const target = makeContribution({ summary: "Target" }); await deps.contributionStore.put(target); - const result = await callTool(server, "grove_review", { + const result = await callTool(server, "grove_submit_review", { targetCid: target.cid, summary: "Detailed review", scores: { quality: { value: 0.8, direction: "maximize" } }, diff --git a/src/mcp/tools/contributions.ts b/src/mcp/tools/contributions.ts index 7b4c58a9..c6491fb2 100644 --- a/src/mcp/tools/contributions.ts +++ b/src/mcp/tools/contributions.ts @@ -1,10 +1,14 @@ /** * MCP tools for contribution operations. * - * grove_contribute — Submit a contribution with artifacts - * grove_review — Submit a review of a contribution (sugar over contribute) - * grove_reproduce — Submit a reproduction of a contribution (sugar over contribute) - * grove_discuss — Post a discussion or reply (sugar over contribute) + * grove_submit_work — Submit a work contribution (replaces grove_contribute kind=work) + * grove_submit_review — Submit a review of a contribution (replaces grove_review + kind=review) + * grove_discuss — Post a discussion or reply (replaces grove_contribute kind=discussion + grove_send_message) + * grove_reproduce — Submit a reproduction of a contribution (replaces grove_contribute kind=reproduction) + * grove_done — Signal that this agent has finished its work (replaces grove_contribute done=true) + * + * grove_contribute has been REMOVED to force agents to use structured per-kind tools. + * Agents can no longer bypass required fields by calling grove_contribute with minimal args. * * All business logic is delegated to the shared operations layer. */ @@ -24,37 +28,24 @@ import type { McpDeps } from "../deps.js"; import { toMcpResult, toOperationDeps } from "../operation-adapter.js"; import { agentSchema, relationSchema, scoreSchema } from "../schemas.js"; -const contributeInputSchema = z.object({ - kind: z - .enum([ - "work", - "review", - "discussion", - "adoption", - "reproduction", - "plan", - "ask_user", - "response", - ]) - .describe("Contribution kind"), - mode: z - .enum(["evaluation", "exploration"]) - .default("evaluation") - .describe("Whether this contribution has measurable scores (evaluation) or is exploratory"), - summary: z.string().describe("Short summary of the contribution"), - description: z.string().optional().describe("Longer description"), +// --------------------------------------------------------------------------- +//grove_submit_work — replaces grove_contribute(kind=work) +// --------------------------------------------------------------------------- +const submitWorkInputSchema = z.object({ + summary: z.string().describe("Short summary of the work completed"), + description: z.string().optional().describe("Longer description of the work"), artifacts: z .record(z.string(), z.string()) - .optional() - .default({}) .describe( - "Map of artifact name to content hash (blake3:). Artifacts must already exist in CAS.", + "File artifacts produced by this work. Map of path to content hash (blake3:). " + + 'Example: {"src/index.ts": "blake3:abc123...", "README.md": "blake3:def456..."}. ' + + "Without artifacts, reviewers cannot see your files and will reject the work.", ), relations: z .array(relationSchema) .optional() .default([]) - .describe("Typed edges to other contributions"), + .describe("Typed edges to other contributions (e.g., derives_from a previous work)"), scores: z.record(z.string(), scoreSchema).optional().describe("Named numeric scores"), tags: z.array(z.string()).optional().default([]).describe("Free-form labels for filtering"), context: z @@ -64,20 +55,50 @@ const contributeInputSchema = z.object({ agent: agentSchema, }); -const reviewInputSchema = z.object({ +// --------------------------------------------------------------------------- +//grove_submit_review — replaces grove_review + grove_contribute(kind=review) +// --------------------------------------------------------------------------- +const submitReviewInputSchema = z.object({ targetCid: z.string().describe("CID of the contribution being reviewed"), - summary: z.string().describe("Review summary"), - description: z.string().optional().describe("Detailed review"), - scores: z.record(z.string(), scoreSchema).optional().describe("Review scores"), + summary: z.string().describe("Review summary — what did you find?"), + description: z.string().optional().describe("Detailed review with specific feedback"), + scores: z + .record(z.string(), scoreSchema) + .describe( + "Review scores. At least one score is required. " + + 'Example: {"correctness": {"value": 0.9, "direction": "maximize"}, "clarity": {"value": 0.8, "direction": "maximize"}}', + ), tags: z.array(z.string()).optional().default([]).describe("Tags"), context: z.record(z.string(), z.unknown()).optional().describe("Context metadata"), - agent: agentSchema, metadata: z .record(z.string(), z.unknown()) .optional() .describe("Relation metadata (e.g., {score: 0.8})"), + agent: agentSchema, }); +// --------------------------------------------------------------------------- +//grove_discuss — replaces grove_contribute(kind=discussion) + grove_send_message +// --------------------------------------------------------------------------- +const discussInputSchema = z.object({ + targetCid: z + .string() + .optional() + .describe("CID of the contribution to reply to. Omit for root discussions (topic anchors)."), + summary: z.string().describe("Discussion message or reply text"), + description: z.string().optional().describe("Longer description"), + tags: z.array(z.string()).optional().default([]).describe("Tags for channel semantics"), + recipients: z + .array(z.string()) + .optional() + .describe("Recipient handles for mentions (e.g., @claude-eng). Use @all for broadcast."), + context: z.record(z.string(), z.unknown()).optional().describe("Context metadata"), + agent: agentSchema, +}); + +// --------------------------------------------------------------------------- +//grove_reproduce — replaces grove_contribute(kind=reproduction) +// --------------------------------------------------------------------------- const reproduceInputSchema = z.object({ targetCid: z.string().describe("CID of the contribution being reproduced"), summary: z.string().describe("Reproduction summary"), @@ -97,18 +118,6 @@ const reproduceInputSchema = z.object({ agent: agentSchema, }); -const discussInputSchema = z.object({ - targetCid: z - .string() - .optional() - .describe("CID of the contribution to reply to. Omit for root discussions (topic anchors)."), - summary: z.string().describe("Discussion message"), - description: z.string().optional().describe("Longer description"), - tags: z.array(z.string()).optional().default([]).describe("Tags for channel semantics"), - context: z.record(z.string(), z.unknown()).optional().describe("Context metadata"), - agent: agentSchema, -}); - // --------------------------------------------------------------------------- // Tool registration // --------------------------------------------------------------------------- @@ -133,29 +142,22 @@ function withDefaultRole(agent: AgentOverrides | undefined): AgentOverrides { export function registerContributionTools(server: McpServer, deps: McpDeps): void { const opDeps = toOperationDeps(deps); - // --- grove_contribute --------------------------------------------------- + // --- grove_submit_work --------------------------------------------------- server.registerTool( - "grove_contribute", + "grove_submit_work", { description: - "Submit a contribution to the grove. Contributions are immutable units of work " + - "(code, reviews, discussions, adoptions, reproductions) that form a DAG. " + - "Artifacts must be pre-stored in CAS; pass their content hashes in the artifacts map.", - inputSchema: contributeInputSchema, + "Submit a work contribution. This is the ONLY way to submit completed work. " + + "You MUST include artifacts (file hashes) so reviewers can inspect your code. " + + "Without artifacts, reviewers cannot see your work and will not approve it. " + + "Use this when you have finished implementing something.", + inputSchema: submitWorkInputSchema, }, async (args) => { const result = await contributeOperation( { - kind: args.kind as - | "work" - | "review" - | "discussion" - | "adoption" - | "reproduction" - | "plan" - | "ask_user" - | "response", - ...(args.mode !== undefined ? { mode: args.mode as "evaluation" | "exploration" } : {}), + kind: "work", + mode: "evaluation", summary: args.summary, ...(args.description !== undefined ? { description: args.description } : {}), artifacts: args.artifacts, @@ -175,14 +177,16 @@ export function registerContributionTools(server: McpServer, deps: McpDeps): voi }, ); - // --- grove_review ------------------------------------------------------- + // --- grove_submit_review ------------------------------------------------- server.registerTool( - "grove_review", + "grove_submit_review", { description: - "Submit a review of an existing contribution. This is a convenience tool that creates " + - "a contribution with kind=review and a 'reviews' relation to the target.", - inputSchema: reviewInputSchema, + "Submit a review of an existing contribution. This is the ONLY way to submit a review. " + + "You MUST include at least one score and the target CID. " + + "Scores are required so the frontier can rank contributions by quality. " + + "Use this when you have reviewed someone's work and want to give feedback.", + inputSchema: submitReviewInputSchema, }, async (args) => { const result = await reviewOperation( @@ -190,9 +194,7 @@ export function registerContributionTools(server: McpServer, deps: McpDeps): voi targetCid: args.targetCid, summary: args.summary, ...(args.description !== undefined ? { description: args.description } : {}), - ...(args.scores !== undefined - ? { scores: args.scores as Readonly> } - : {}), + scores: args.scores as Readonly>, tags: args.tags, ...(args.context !== undefined ? { context: args.context as Readonly> } @@ -208,29 +210,26 @@ export function registerContributionTools(server: McpServer, deps: McpDeps): voi }, ); - // --- grove_reproduce ---------------------------------------------------- + // --- grove_discuss ------------------------------------------------------ server.registerTool( - "grove_reproduce", + "grove_discuss", { description: - "Submit a reproduction attempt of an existing contribution. Reports whether the " + - "result was confirmed, challenged, or partial. Adds a 'reproduces' relation to the target.", - inputSchema: reproduceInputSchema, + "Post a discussion or reply to an existing contribution. Creates a contribution with " + + "kind=discussion and mode=exploration. If targetCid is provided, adds a 'responds_to' " + + "relation (reply). If omitted, creates a root discussion (topic anchor). " + + "Use 'recipients' for @mentions (e.g., @all for broadcast). " + + "This also replaces grove_send_message — use this for messaging other agents.", + inputSchema: discussInputSchema, }, async (args) => { - const result = await reproduceOperation( + const result = await discussOperation( { - targetCid: args.targetCid, summary: args.summary, + ...(args.targetCid !== undefined ? { targetCid: args.targetCid } : {}), ...(args.description !== undefined ? { description: args.description } : {}), - ...(args.result !== undefined - ? { result: args.result as "confirmed" | "challenged" | "partial" } - : {}), - ...(args.scores !== undefined - ? { scores: args.scores as Readonly> } - : {}), - artifacts: args.artifacts, tags: args.tags, + ...(args.recipients !== undefined ? { recipients: args.recipients } : {}), ...(args.context !== undefined ? { context: args.context as Readonly> } : {}), @@ -242,22 +241,29 @@ export function registerContributionTools(server: McpServer, deps: McpDeps): voi }, ); - // --- grove_discuss ------------------------------------------------------ + // --- grove_reproduce ---------------------------------------------------- server.registerTool( - "grove_discuss", + "grove_reproduce", { description: - "Post a discussion or reply to an existing contribution. Creates a contribution with " + - "kind=discussion and mode=exploration. If targetCid is provided, adds a 'responds_to' " + - "relation (reply). If omitted, creates a root discussion (topic anchor).", - inputSchema: discussInputSchema, + "Submit a reproduction attempt of an existing contribution. Reports whether the " + + "result was confirmed, challenged, or partial. Adds a 'reproduces' relation to the target. " + + "Use this when you need to verify that someone's work can be reproduced.", + inputSchema: reproduceInputSchema, }, async (args) => { - const result = await discussOperation( + const result = await reproduceOperation( { + targetCid: args.targetCid, summary: args.summary, - ...(args.targetCid !== undefined ? { targetCid: args.targetCid } : {}), ...(args.description !== undefined ? { description: args.description } : {}), + ...(args.result !== undefined + ? { result: args.result as "confirmed" | "challenged" | "partial" } + : {}), + ...(args.scores !== undefined + ? { scores: args.scores as Readonly> } + : {}), + artifacts: args.artifacts, tags: args.tags, ...(args.context !== undefined ? { context: args.context as Readonly> } diff --git a/src/mcp/tools/tools.test.ts b/src/mcp/tools/tools.test.ts index f8749ef7..0880d33c 100644 --- a/src/mcp/tools/tools.test.ts +++ b/src/mcp/tools/tools.test.ts @@ -1,5 +1,5 @@ /** - * Unified MCP tool tests covering grove_contribute, grove_frontier, + * Unified MCP tool tests covering grove_submit_work, grove_frontier, * grove_claim, grove_search, and grove_checkout. * * Each tool is tested for valid inputs (success), invalid inputs (error), @@ -51,7 +51,7 @@ async function callTool( // grove_contribute // --------------------------------------------------------------------------- -describe("grove_contribute (tools.test)", () => { +describe("grove_submit_work (tools.test)", () => { let testDeps: TestMcpDeps; let deps: McpDeps; let server: McpServer; @@ -68,12 +68,9 @@ describe("grove_contribute (tools.test)", () => { }); test("succeeds with minimal required fields", async () => { - const result = await callTool(server, "grove_contribute", { - kind: "work", + const result = await callTool(server, "grove_submit_work", { summary: "Minimal contribution", - relations: [], artifacts: {}, - tags: [], }); expect(result.isError).toBeUndefined(); @@ -86,9 +83,7 @@ describe("grove_contribute (tools.test)", () => { test("succeeds with all optional fields populated", async () => { const hash = await storeTestContent(deps.cas, "artifact content"); - const result = await callTool(server, "grove_contribute", { - kind: "work", - mode: "evaluation", + const result = await callTool(server, "grove_submit_work", { summary: "Full contribution", description: "A detailed description", artifacts: { "main.py": hash }, @@ -106,14 +101,11 @@ describe("grove_contribute (tools.test)", () => { }); test("returns error for non-existent artifact hash", async () => { - const result = await callTool(server, "grove_contribute", { - kind: "work", + const result = await callTool(server, "grove_submit_work", { summary: "Bad artifact ref", - relations: [], artifacts: { "missing.txt": "blake3:0000000000000000000000000000000000000000000000000000000000000000", }, - tags: [], agent: { agentId: "agent-1" }, }); @@ -123,17 +115,15 @@ describe("grove_contribute (tools.test)", () => { }); test("returns error when relation targets non-existent contribution", async () => { - const result = await callTool(server, "grove_contribute", { - kind: "work", + const result = await callTool(server, "grove_submit_work", { summary: "Dangling relation", + artifacts: {}, relations: [ { targetCid: "blake3:0000000000000000000000000000000000000000000000000000000000000000", relationType: "derives_from", }, ], - artifacts: {}, - tags: [], agent: { agentId: "agent-1" }, }); @@ -141,14 +131,10 @@ describe("grove_contribute (tools.test)", () => { expect(result.text).toContain(McpErrorCode.NotFound); }); - test("exploration mode contribution succeeds without scores", async () => { - const result = await callTool(server, "grove_contribute", { - kind: "work", - mode: "exploration", - summary: "Exploratory work without scores", - relations: [], + test("work contribution succeeds without optional fields", async () => { + const result = await callTool(server, "grove_submit_work", { + summary: "Simple work without optional fields", artifacts: {}, - tags: ["experiment"], agent: { agentId: "explorer-1" }, }); diff --git a/src/tui/spawn-manager.ts b/src/tui/spawn-manager.ts index 5414160a..36baade1 100644 --- a/src/tui/spawn-manager.ts +++ b/src/tui/spawn-manager.ts @@ -524,7 +524,7 @@ ${rolePrompt ? `## Instructions\n${rolePrompt}\n` : ""} ## Identity -You are the **${roleId}** agent. Always pass \`agent: { role: "${roleId}" }\` in grove_contribute and grove_done calls. This is set once here — do not worry about it after this. +You are the **${roleId}** agent. Always pass \`agent: { role: "${roleId}" }\` in grove_submit_work and grove_done calls. This is set once here — do not worry about it after this. ## Communication @@ -532,8 +532,10 @@ You will receive push notifications from the system when other agents produce wo ## MCP Tools (use sparingly) -- \`grove_contribute\` — record your work (always include agent: { role: "${roleId}" }) -- \`grove_done\` — signal session complete (only after approval from other agents) +- \`grove_submit_work\` — submit completed work with summary + artifacts (ALWAYS include artifacts, or reviewers will reject your work) +- \`grove_submit_review\` — submit a code review with targetCid + scores (ALWAYS include scores, or frontier won't rank your review) +- \`grove_discuss\` — post a discussion or reply (use tags for @mentions) +- \`grove_done\` — signal session complete (only after all work is done and approved) Do NOT call grove_log, grove_search, grove_frontier, grove_checkout, grove_goal, or grove_read_inbox. You receive everything you need via push notifications. @@ -566,9 +568,9 @@ Follow the Instructions section above exactly. You are in a git worktree with fu "- grove_frontier — discover best contributions to build on", "- grove_claim / grove_release — coordinate work to avoid duplication", "- grove_checkout — materialize artifacts into your workspace", - "- grove_contribute — submit your work", - "- grove_review — submit a code review", - "- grove_send_message / grove_read_inbox — agent-to-agent messaging", + "- grove_submit_work — submit completed work (requires summary + artifacts)", + "- grove_submit_review — submit a review (requires targetCid + summary + scores)", + "- grove_discuss — post a discussion or reply (replaces grove_send_message)", "- grove_create_plan / grove_update_plan — maintain project plans", "- grove_check_stop — check if stop conditions are met", "",