From 49917784cf5da11e436567f30c3bf727c9d34908 Mon Sep 17 00:00:00 2001 From: Canrad <1517807724@qq.com> Date: Fri, 30 Oct 2026 08:40:51 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(mcp):=20descriptor/jasm=20operand=20?= =?UTF-8?q?=E5=AE=9E=E5=8F=82=E6=8E=A5=20shadow=20token=20=E6=96=B0?= =?UTF-8?q?=E6=A7=BD=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolveDescriptorArgument / resolveJasmBodyArgument 实参槽:值含 ${ 引用 形状才入槽展开(内嵌类 handle → 真名;操作数复合 → raw 文本),raw 实参 原样直通,既有契约零漂移。resolveMethodSlots 的 descriptor 实参与 edit_apply 的 member_descriptor 经描述符槽展开后参与成员核对并覆盖回 resolvedArgs;search 的 operand 过滤器经 JasmBody 槽展开后再子串匹配。 E2E:desc 内嵌 handle 的 class_query/xref_query 成功向 + unknown/malformed 错误向、edit_apply member_descriptor handle 提交与拒收、operand 过滤器 handle/raw 同一命中集。依赖 mjbase #109(Descriptor/JasmBody 槽)。 --- mjt/mcp/src/tool/mutation.cpp | 13 +- mjt/mcp/src/tool/query.cpp | 13 +- mjt/mcp/src/tool/shadow_slot.cpp | 47 +++++- mjt/mcp/src/tool/shadow_slot.hpp | 18 ++ mjt/mcp/test/mcp_shadow_token_test.cpp | 224 +++++++++++++++++++++++++ 5 files changed, 311 insertions(+), 4 deletions(-) diff --git a/mjt/mcp/src/tool/mutation.cpp b/mjt/mcp/src/tool/mutation.cpp index 4c8e1c1..f3cdba6 100644 --- a/mjt/mcp/src/tool/mutation.cpp +++ b/mjt/mcp/src/tool/mutation.cpp @@ -899,11 +899,22 @@ std::optional McpToolService::callMutationTool( const auto slots = slot::loadWorkspaceShadowTable(*current); const auto ownerInternal = mjt::internalNameOfUri(classUri); + //描述符槽:member_descriptor 带 `${` 引用形状 → 内嵌类 handle + //展开;展开值参与成员核对并覆盖回 resolvedArgs(parseMutation + //读展开值,handle 字面量不落下游)。 + std::string memberDescriptor; + if (!slot::resolveDescriptorArgument( + slots, + arguments.value("member_descriptor", std::string{}), + memberDescriptor, + slotError)) + return slotError; + resolvedArgs["member_descriptor"] = memberDescriptor; slot::SlotName memberSlot; if (!slot::resolveMemberSlot(slots, ownerInternal, memberName, - arguments.value("member_descriptor", std::string{}), + memberDescriptor, true, memberSlot, slotError)) diff --git a/mjt/mcp/src/tool/query.cpp b/mjt/mcp/src/tool/query.cpp index c5e5314..cba1274 100644 --- a/mjt/mcp/src/tool/query.cpp +++ b/mjt/mcp/src/tool/query.cpp @@ -573,7 +573,18 @@ std::optional McpToolService::callWorkspaceQueryTool( //严格切换不给混搭语义)。 const auto stringKindFilter = arguments.value("string_kind", std::string{}); const auto mnemonicFilter = arguments.value("mnemonic", std::string{}); - const auto operandFilter = arguments.value("operand", std::string{}); + auto operandFilter = arguments.value("operand", std::string{}); + //JasmBody 槽:operand 过滤器带 `${` 引用形状 → 操作数行展开 + //(类 handle / owner.name 复合 → raw 文本再子串匹配);raw 过滤器 + //原样直通(过滤契约零漂移)。 + if (!operandFilter.empty() && operandFilter.find("${") != std::string::npos) { + const auto slots = slot::loadWorkspaceShadowTable(*current); + std::string resolvedOperand; + Json operandSlotError; + if (!slot::resolveJasmBodyArgument(slots, operandFilter, resolvedOperand, operandSlotError)) + return operandSlotError; + operandFilter = std::move(resolvedOperand); + } const auto cstFilter = arguments.value("cst", std::string{}); const bool structured = !mnemonicFilter.empty() || !operandFilter.empty() || !cstFilter.empty(); diff --git a/mjt/mcp/src/tool/shadow_slot.cpp b/mjt/mcp/src/tool/shadow_slot.cpp index d2638f8..5105f34 100644 --- a/mjt/mcp/src/tool/shadow_slot.cpp +++ b/mjt/mcp/src/tool/shadow_slot.cpp @@ -145,8 +145,13 @@ bool resolveMethodSlots(const WorkspaceShadowTable& slots, { const auto ownerInput = arguments.value("owner", std::string{}); const auto nameInput = arguments.value("name", std::string{}); - const auto descInput = arguments.value("descriptor", std::string{}); - SlotName ownerSlot; + auto descInput = arguments.value("descriptor", std::string{}); + //描述符槽:desc 带 `${` 引用形状 → 内嵌类 handle 展开,展开值参与 + //成员 descHint 核对与输出;raw desc 原样直通(契约零漂移)。 + std::string resolvedDesc; + if (!resolveDescriptorArgument(slots, descInput, resolvedDesc, error)) return false; + descInput = std::move(resolvedDesc); + SlotName ownerSlot; if (!resolveClassSlot(slots, ownerInput, verifyRaw, ownerSlot, error)) return false; ownerRaw = ownerSlot.raw; SlotName nameSlot; @@ -321,4 +326,42 @@ bool resolveClassUriArgument(mjt::workspace::IWorkspace& workspace, return resourceUriOfResolved(workspace, resolved.raw, out, error); } +bool resolveDescriptorArgument(const WorkspaceShadowTable& slots, + const std::string& input, + std::string& out, + Json& error) +{ + out = input; + //非引用形状(raw 描述符)直通:文法校验与 canonical 错误面在下游, + //既有契约零漂移。 + if (input.empty() || input.find("${") == std::string::npos) return true; + mjbase::renamer::ShadowSlotQuery query; + query.position = mjbase::renamer::ShadowSlotPosition::Descriptor; + const auto result = mjbase::renamer::resolveShadowSlot(slots.table, input, query); + if (result.error != ShadowSlotError::None) { + error = errorOf(result); + return false; + } + out = result.raw; + return true; +} + +bool resolveJasmBodyArgument(const WorkspaceShadowTable& slots, + const std::string& input, + std::string& out, + Json& error) +{ + out = input; + if (input.empty() || input.find("${") == std::string::npos) return true; + mjbase::renamer::ShadowSlotQuery query; + query.position = mjbase::renamer::ShadowSlotPosition::JasmBody; + const auto result = mjbase::renamer::resolveShadowSlot(slots.table, input, query); + if (result.error != ShadowSlotError::None) { + error = errorOf(result); + return false; + } + out = result.raw; + return true; +} + } //namespace mjt::mcp::slot diff --git a/mjt/mcp/src/tool/shadow_slot.hpp b/mjt/mcp/src/tool/shadow_slot.hpp index 8e9bfcc..a302b5f 100644 --- a/mjt/mcp/src/tool/shadow_slot.hpp +++ b/mjt/mcp/src/tool/shadow_slot.hpp @@ -126,6 +126,24 @@ bool resolveClassUriArgument(mjt::workspace::IWorkspace& workspace, std::string& out, Json& error); +///@brief 描述符实参槽:值含 `${` 引用形状 → Descriptor 槽展开(内嵌类 +///handle → 真名,四分类错误直传);否则原样直通(raw 描述符零漂移,其文法 +///校验与 canonical 错误面留在下游,不因槽接线收紧)。空值直通。 +///@return false 时 `error` 已填结构化 toolError。 +bool resolveDescriptorArgument(const WorkspaceShadowTable& slots, + const std::string& input, + std::string& out, + Json& error); + +///@brief jasm 操作数实参槽(指令搜索 operand 过滤器用):值含 `${` 引用 +///形状 → JasmBody 槽展开(类 handle / owner.name 复合 / 尾随描述符); +///否则原样直通。空值直通。 +///@return false 时 `error` 已填结构化 toolError。 +bool resolveJasmBodyArgument(const WorkspaceShadowTable& slots, + const std::string& input, + std::string& out, + Json& error); + ///@brief 显式描述符失配诊断:name 在宿主类存在但 descriptor 与任一候选都 ///不符时,给结构化错误并列出该名全部候选描述符(与省略 descriptor 时的 ///歧义路径同一呈现面)。name 不存在 / owner 不可解析 / descriptor 恰好命中 diff --git a/mjt/mcp/test/mcp_shadow_token_test.cpp b/mjt/mcp/test/mcp_shadow_token_test.cpp index f0af5d1..0bcc654 100644 --- a/mjt/mcp/test/mcp_shadow_token_test.cpp +++ b/mjt/mcp/test/mcp_shadow_token_test.cpp @@ -8,15 +8,19 @@ //JSON),零反编译样本入仓。 #include #include +#include +#include #include #include #include #include "mcp_test_support.hpp" +#include "mjbase/jasm/jvm_jasm_service.hpp" #include "mjbase/mapping/shadow_alias_format.hpp" #include "mjbase/model/resource_container.hpp" #include "mjbase/model/resource_helper.hpp" +#include "mjbase/model/resource_model.hpp" #include "mjbase/renamer/shadow_alias.hpp" namespace mjt::mcp { @@ -97,6 +101,76 @@ void expectSlotError(const Json& response, } } +///装配诊断首条消息(Utf16 → UTF-8;失败定位用)。 +std::string firstDiagnostic(const mjbase::jasm::Result& assembled) +{ + if (assembled.diagnostics.empty()) return "no diagnostics"; + return assembled.diagnostics.front().message.ref().to_utf8_string().to_std_string(); +} + +///Descriptor/JasmBody 槽 E2E fixture:desc/Gift(被引用类)+ desc/Holder +///(take (Ldesc/Gift;)V 带类型参数描述符;useIt ()V 内 invokestatic 构造 +///指令操作数引用面)。全部自造 jasm 装配,零样本字节。 +///机械编号:Class_1=desc/Gift、Class_2=desc/Holder;Method_1=take、 +///Method_2=useIt(类内 (name, desc) 字典序)。 +std::filesystem::path writeDescriptorFixture() +{ + mjbase::jasm::JvmJasmService assembler; + mjbase::model::ResourceContainer container; + const auto giftSource = utf16(".super java/lang/Object\n" + ".class public super desc/Gift {\n" + "}\n"); + const auto giftAssembled = + assembler.assemble(utf16("Gift.jasm").ref(), giftSource.ref()); + if (!giftAssembled.isOk()) + throw std::runtime_error("descriptor Gift fixture did not assemble: " + + firstDiagnostic(giftAssembled)); + container.addResource( + "desc/Gift.class", + std::make_unique("desc/Gift.class", + giftAssembled.value->bytes)); + const auto holderSource = utf16( + ".super java/lang/Object\n" + ".class public super desc/Holder {\n" + " .method public static take (Ldesc/Gift;)V {\n" + " code: {\n" + " return\n" + " }\n" + " }\n" + " .method public static useIt ()V {\n" + " code: {\n" + " aconst_null\n" + " invokestatic desc/Holder.take (Ldesc/Gift;)V\n" + " return\n" + " }\n" + " }\n" + "}\n"); + const auto holderAssembled = + assembler.assemble(utf16("Holder.jasm").ref(), holderSource.ref()); + if (!holderAssembled.isOk()) + throw std::runtime_error("descriptor Holder fixture did not assemble: " + + firstDiagnostic(holderAssembled)); + container.addResource( + "desc/Holder.class", + std::make_unique("desc/Holder.class", + holderAssembled.value->bytes)); + const auto path = test::getTempOutPath("mjt_mcp_descriptor_fixture.jar"); + mjbase::model::ResourceHelper::exportToJar(container, path.string()); + return path; +} + +///writeDescriptorFixture 落盘固定路径,本组测试会在 jar 旁写 sidecar,必须 +///逐用例隔离:拷贝到唯一路径后打开副本(同 uniqueFixtureJar 的隔离规则)。 +std::filesystem::path uniqueDescriptorFixtureJar() +{ + static int counter = 0; + const auto source = writeDescriptorFixture(); + auto target = source; + target.replace_filename("mjt_mcp_descriptor_" + std::to_string(++counter) + ".jar"); + std::filesystem::copy_file(source, target, std::filesystem::copy_options::overwrite_existing); + return target; +} + } //namespace //───────────────────── handle 发放与 ClassRef 三面往返 ───────────────────── @@ -783,4 +857,154 @@ TEST(McpShadowTokenTest, ShadowToRawAcceptsHandleInput) expectSlotError(wrongWay, "shadow_to_raw", "", "reverse direction"); } +//───────────────── Descriptor/JasmBody 槽(工具面实参展开) ───────────────── + +TEST(McpShadowTokenTest, DescriptorSlotExpandsEmbeddedClassHandles) +{ + const auto jar = uniqueDescriptorFixtureJar(); + McpSessionHarness server; + const auto projectId = openWithSidecar(server, jar); + + const auto giftRef = firstMatch(server, projectId, "desc/Gift").at("ref").get(); + const auto holderRef = firstMatch(server, projectId, "desc/Holder").at("ref").get(); + const auto takeRef = + firstMatch(server, projectId, "take", holderRef, "method").at("ref").get(); + ASSERT_NE(std::string::npos, takeRef.find(".Method_")) << takeRef; + + //class_query kind=method:desc 内嵌类 handle → 展开(与表内核对一致)。 + const auto method = call(server, + "class_query", + {{"project_id", projectId}, + {"kind", "method"}, + {"owner", holderRef}, + {"name", takeRef}, + {"descriptor", "(L" + giftRef + ";)V"}}); + ASSERT_FALSE(method.at("result").at("isError")) << method.dump(); + EXPECT_EQ("take", payload(method).at("name")); + EXPECT_EQ("(Ldesc/Gift;)V", payload(method).at("descriptor")); + + //xref_query references target=method 同口径:desc handle 展开后命中 + //真实引用(useIt 内 invokestatic 的操作数描述符)。 + const auto xref = call(server, + "xref_query", + {{"project_id", projectId}, + {"kind", "references"}, + {"target", "method"}, + {"direction", "to"}, + {"owner", holderRef}, + {"name", takeRef}, + {"descriptor", "(L" + giftRef + ";)V"}}); + ASSERT_FALSE(xref.at("result").at("isError")) << xref.dump(); + EXPECT_GE(payload(xref).at("items").size(), 1U) << xref.dump(); +} + +TEST(McpShadowTokenTest, DescriptorHandleErrorsSurfaceAtToolBoundary) +{ + const auto jar = uniqueDescriptorFixtureJar(); + McpSessionHarness server; + const auto projectId = openWithSidecar(server, jar); + + const auto holderRef = firstMatch(server, projectId, "desc/Holder").at("ref").get(); + const auto takeRef = + firstMatch(server, projectId, "take", holderRef, "method").at("ref").get(); + + //desc 内类 handle token 手滑 → unknown-handle(非 raw-not-found,也不 + //落下游 canonical NOT_FOUND)。 + const auto unknown = call(server, + "class_query", + {{"project_id", projectId}, + {"kind", "method"}, + {"owner", holderRef}, + {"name", takeRef}, + {"descriptor", "(L${Class_1.deadbeef};)V"}}); + expectSlotError(unknown, "unknown-handle", "raw-not-found"); + + //desc 文法垃圾(L 段内 handle 残缺)→ malformed-handle。 + const auto malformed = call(server, + "class_query", + {{"project_id", projectId}, + {"kind", "method"}, + {"owner", holderRef}, + {"name", takeRef}, + {"descriptor", "(L${Class_1;"}}); + expectSlotError(malformed, "malformed-handle"); +} + +TEST(McpShadowTokenTest, EditApplyMemberDescriptorHandleExpands) +{ + const auto jar = uniqueDescriptorFixtureJar(); + McpSessionHarness server; + const auto projectId = openWithSidecar(server, jar); + + const auto giftRef = firstMatch(server, projectId, "desc/Gift").at("ref").get(); + const auto holderRef = firstMatch(server, projectId, "desc/Holder").at("ref").get(); + const auto takeRef = + firstMatch(server, projectId, "take", holderRef, "method").at("ref").get(); + + //member_descriptor 内嵌类 handle:槽层展开 → 成员核对一致 → 变更真实 + //提交(revision 在响应)。 + const auto committed = call(server, + "edit_apply", + {{"project_id", projectId}, + {"kind", "structured"}, + {"class_uri", holderRef}, + {"mutation_kind", "set_method_access"}, + {"member_name", takeRef}, + {"member_descriptor", "(L" + giftRef + ";)V"}, + {"access_flags", Json::array({"public", "final"})}}); + ASSERT_FALSE(committed.at("result").at("isError")) << committed.dump(); + EXPECT_TRUE(payload(committed).contains("revision")); + + //desc handle token 手滑 → unknown-handle,变更不落盘。 + const auto rejected = call(server, + "edit_apply", + {{"project_id", projectId}, + {"kind", "structured"}, + {"class_uri", holderRef}, + {"mutation_kind", "set_method_access"}, + {"member_name", takeRef}, + {"member_descriptor", "(L${Class_1.deadbeef};)V"}, + {"access_flags", Json::array({"public"})}}); + expectSlotError(rejected, "unknown-handle", "raw-not-found"); +} + +TEST(McpShadowTokenTest, OperandFilterResolvesThroughJasmBodySlot) +{ + const auto jar = uniqueDescriptorFixtureJar(); + McpSessionHarness server; + const auto projectId = openWithSidecar(server, jar); + + const auto giftRef = firstMatch(server, projectId, "desc/Gift").at("ref").get(); + const auto holderRef = firstMatch(server, projectId, "desc/Holder").at("ref").get(); + const auto takeRef = + firstMatch(server, projectId, "take", holderRef, "method").at("ref").get(); + + //类 handle 过滤器 → 展开 desc/Gift 后子串命中 invokestatic 操作数。 + const auto byClass = call(server, + "search", + {{"project_id", projectId}, + {"kind", "instructions"}, + {"operand", giftRef}}); + ASSERT_TRUE(byClass.contains("result")) << "RAWRESP " << byClass.dump(); + EXPECT_GE(payload(byClass).at("items").size(), 1U) << byClass.dump(); + + //成员复合过滤器 → rawOwner.rawName 复合命中同一枚指令。 + const auto byMember = call(server, + "search", + {{"project_id", projectId}, + {"kind", "instructions"}, + {"operand", takeRef}}); + ASSERT_FALSE(byMember.at("result").at("isError")) << byMember.dump(); + EXPECT_GE(payload(byMember).at("items").size(), 1U) << byMember.dump(); + + //raw 过滤器直通:与 handle 展开面同一命中集(过滤契约零漂移)。 + const auto byRaw = call(server, + "search", + {{"project_id", projectId}, + {"kind", "instructions"}, + {"operand", "desc/Gift"}}); + ASSERT_FALSE(byRaw.at("result").at("isError")) << byRaw.dump(); + EXPECT_EQ(payload(byClass).at("items").size(), payload(byRaw).at("items").size()); +} + } //namespace mjt::mcp From 252451767c5463a1af4387c16a11baf0c49cd56e Mon Sep 17 00:00:00 2001 From: Canrad <1517807724@qq.com> Date: Fri, 30 Oct 2026 09:20:43 +0800 Subject: [PATCH 2/3] =?UTF-8?q?chore(ci):=20mjbase=20=E5=AE=9A=E7=89=88?= =?UTF-8?q?=E5=8D=87=E8=87=B3=20shadow=20token=20=E4=B8=89=E6=A7=BD?= =?UTF-8?q?=E6=94=B6=E5=8F=A3=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Descriptor/JasmBody 槽与中段裁定并入,工具面实参展开(描述符内嵌类 handle、操作数复合)依赖该基座。 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09ab274..acf2ef9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: uses: actions/checkout@v4 with: repository: luiox/mjbase - ref: 9d78607c9acd547c7646d87971477af16727b032 + ref: 5a2d9d5287afcd8ad3b7408d751d6b3b62ef5449 path: mjbase-src - name: Install system deps @@ -82,7 +82,7 @@ jobs: uses: actions/checkout@v4 with: repository: luiox/mjbase - ref: 9d78607c9acd547c7646d87971477af16727b032 + ref: 5a2d9d5287afcd8ad3b7408d751d6b3b62ef5449 path: mjbase-src - name: Setup xmake @@ -130,7 +130,7 @@ jobs: uses: actions/checkout@v4 with: repository: luiox/mjbase - ref: 9d78607c9acd547c7646d87971477af16727b032 + ref: 5a2d9d5287afcd8ad3b7408d751d6b3b62ef5449 path: mjbase-src - name: Setup Python 3.14 (python314.dll / include / libs for mjt-py) From f705409f316b60bd67c97184f2d89999038b81e8 Mon Sep 17 00:00:00 2001 From: Canrad <1517807724@qq.com> Date: Fri, 30 Oct 2026 12:00:34 +0800 Subject: [PATCH 3/3] =?UTF-8?q?chore(ci):=20mjbase=20=E5=AE=9A=E7=89=88?= =?UTF-8?q?=E5=8D=87=E8=87=B3=E4=B8=AD=E6=AE=B5=E8=A3=81=E5=AE=9A=E5=90=88?= =?UTF-8?q?=E5=85=A5=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mjbase main 72cc5d7:中段裁定(段外花括号 fail-loud)+ JasmBody 复合 操作数矩阵钉扎 + 基线修复(libjhasm 0.3.3 接线收尾、libjbc 0.4.22 沙箱 对齐)并入,#30 CI 改钉 main tip(原 5a2d9d5 为分支 ref,SHA 级取用受 年轻分支通告滞后影响)。 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acf2ef9..c347800 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: uses: actions/checkout@v4 with: repository: luiox/mjbase - ref: 5a2d9d5287afcd8ad3b7408d751d6b3b62ef5449 + ref: 72cc5d72f79da48a6f33107103c528e9cc2d8269 path: mjbase-src - name: Install system deps @@ -82,7 +82,7 @@ jobs: uses: actions/checkout@v4 with: repository: luiox/mjbase - ref: 5a2d9d5287afcd8ad3b7408d751d6b3b62ef5449 + ref: 72cc5d72f79da48a6f33107103c528e9cc2d8269 path: mjbase-src - name: Setup xmake @@ -130,7 +130,7 @@ jobs: uses: actions/checkout@v4 with: repository: luiox/mjbase - ref: 5a2d9d5287afcd8ad3b7408d751d6b3b62ef5449 + ref: 72cc5d72f79da48a6f33107103c528e9cc2d8269 path: mjbase-src - name: Setup Python 3.14 (python314.dll / include / libs for mjt-py)