From 343fcd20bcd329b2f4a9ec2629156a1c4537d4a0 Mon Sep 17 00:00:00 2001 From: Tim Lingo <1timlingo@gmail.com> Date: Sat, 27 Jun 2026 16:02:56 -0500 Subject: [PATCH] fix(mcp-wrapper): planWork creates a real BacklogItem; reviewBacklog lists by type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit planWork fell through create_typed_node to a generic /api/neuron/memory write — a [BacklogItem]-prefixed memory blob with title/project/priority DROPPED, never a real BacklogItem. reviewBacklog used a lexical /recall (top-50, untyped). Now: planWork -> /api/neuron/node/create {node_type:BacklogItem,...} via new create_node_typed; reviewBacklog -> list_typed('BacklogItem') (GET /api/neuron/list/BacklogItem). elc-clean. Depends on neuron PR #58 (the list/ slice fix) to round-trip; needs the wrapper binary rebuilt + :7779 restarted to take effect. Co-Authored-By: Claude Opus 4.8 (1M context) --- mcp-wrapper/src/main.el | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/mcp-wrapper/src/main.el b/mcp-wrapper/src/main.el index 2519ca1..bfb98b6 100644 --- a/mcp-wrapper/src/main.el +++ b/mcp-wrapper/src/main.el @@ -267,6 +267,27 @@ fn recall_or_list(query: String, limit: Int) -> String { return http_post_json(neuron_url() + "/recall", body) } +// Create a real typed node via /api/neuron/node/create (handle_api_node_create) so it is a proper +// BacklogItem/Artifact/etc. — listable by type via /api/neuron/list/ — instead of a generic +// memory blob. Maps title->label, content/description->content, project/priority->tags. +fn create_node_typed(args: String, node_type: String, tier: String) -> String { + let content: String = pick_content(args) + if str_eq(content, "") { + return mcp_text_result("error: content/title is required for " + node_type) + } + let title: String = json_get_string(args, "title") + let label: String = if str_eq(title, "") { node_type } else { title } + let project: String = json_get_string(args, "project") + let priority: String = json_get_string(args, "priority") + let proj_tag: String = if str_eq(project, "") { "" } else { ",\"project:" + project + "\"" } + let prio_tag: String = if str_eq(priority, "") { "" } else { ",\"priority:" + priority + "\"" } + let tags: String = "[\"" + node_type + "\"" + proj_tag + prio_tag + "]" + let body: String = "{\"node_type\":\"" + node_type + "\",\"content\":\"" + json_escape(content) + + "\",\"label\":\"" + json_escape(label) + "\",\"tier\":\"" + tier + "\",\"tags\":" + tags + "}" + let resp: String = http_post_json(neuron_url() + "/node/create", body) + return mcp_json_result(resp) +} + fn search_with_query(args: String, default_limit: Int) -> String { let query: String = json_get_string(args, "query") if str_eq(query, "") { let query = pick_content(args) } @@ -631,8 +652,12 @@ fn dispatch_tool_call(tool_name: String, args: String) -> String { } // ── Backlog + work ────────────────────────────────────────────────────── - if str_eq(tool_name, "planWork") { return create_typed_node(args, "BacklogItem", "0.65") } - if str_eq(tool_name, "reviewBacklog") { return search_with_query(args, 50) } + // planWork: create a REAL typed BacklogItem via /api/neuron/node/create (the old path fell through + // create_typed_node to a generic /memory write, dropping title/project/priority and never making a + // BacklogItem). reviewBacklog: LIST BacklogItem nodes (was a lexical /recall that never filtered by + // type). Both depend on the /api/neuron/list/ slice fix (neuron PR #58) to round-trip. + if str_eq(tool_name, "planWork") { return create_node_typed(args, "BacklogItem", "Working") } + if str_eq(tool_name, "reviewBacklog") { return list_typed("BacklogItem", 50, args) } if str_eq(tool_name, "trackWork") { return evolve_by_supersede(args, "Memory") } if str_eq(tool_name, "listWork") { return list_typed("WorkContext", 50, args) } if str_eq(tool_name, "beginWork") { return create_typed_node(args, "Memory", "0.70") }