feat(mcp): 治理三件套的 agent 投影——能力双投影首次实践

get_blueprint_status / generate_blueprint_prompt / sync_blueprint_rules
注册为 MCP 工具,与 UI 治理按钮共享同一后端函数(统一验证路径空间)。
工具描述声明对应 UI 按钮与使用约束。agent 从此可走正规流程完成
"拿提示词 → 子项目执行 → 验证蓝图"闭环,不再绕道。

Rules-Applied: R01, R10
This commit is contained in:
lanrtop 2026-07-03 11:29:28 +09:00
parent 7b0f8e60bb
commit 8c8423e0a4
2 changed files with 84 additions and 1 deletions

View File

@ -40,7 +40,15 @@
- files: src-tauri/src/db.rs, src-tauri/src/mcp_server.rs - files: src-tauri/src/db.rs, src-tauri/src/mcp_server.rs
- acceptance: settings 表默认写入 mcp_port=27190启动时读取该值绑定端口SettingsPage UI 见 product-group-mcp 模块) - acceptance: settings 表默认写入 mcp_port=27190启动时读取该值绑定端口SettingsPage UI 见 product-group-mcp 模块)
### 💭 能力双投影UI 治理按钮 ↔ MCP 工具对齐R12 候选) ### ✅ P1. 治理三件套的 agent 投影get_blueprint_status / generate_blueprint_prompt / sync_blueprint_rules
- status: done
- complexity: M
- files: src-tauri/src/mcp_server.rs
- acceptance: 三个治理动作注册为 MCP 工具,与 UI 按钮共享同一后端函数(等价性=同一代码路径);工具描述声明对应的 UI 按钮与使用约束边界表达agent 可走完整验证闭环generate_blueprint_prompt 拿提示词 → 子项目执行 → get_blueprint_status/get_project_blueprint 验证
> 完成记录2026-07-03能力双投影概念的首次实践。cargo test 全绿;实测验证由 agent 通过 MCP 端点亲自按下这三颗按钮完成。
### 💭 能力双投影后续扩展R12 规则化 + 更多按钮)
- status: concept - status: concept
- notes: 2026-07-03 概念探讨成形(见 memory concept_dual_projection。现状治理动作生成蓝图提示词/同步规则/接入包/腐化扫描等)只有 UI 投影无 agent 投影agent 验证需绕道。方向:① 为治理 command 补注册 MCP 工具,实现"每个 UI 按钮有对应 agent 按钮";② 落成 R12 规则进 CONVENTIONS与 R01 同构);③ 三条件检查清单(边界单点实现/投影完整/反馈对称)纳入健康诊断。收益:统一验证路径空间——功能修复后 agent 走正规流程分钟级回归验证。 - notes: 2026-07-03 概念探讨成形(见 memory concept_dual_projection。现状治理动作生成蓝图提示词/同步规则/接入包/腐化扫描等)只有 UI 投影无 agent 投影agent 验证需绕道。方向:① 为治理 command 补注册 MCP 工具,实现"每个 UI 按钮有对应 agent 按钮";② 落成 R12 规则进 CONVENTIONS与 R01 同构);③ 三条件检查清单(边界单点实现/投影完整/反馈对称)纳入健康诊断。收益:统一验证路径空间——功能修复后 agent 走正规流程分钟级回归验证。

View File

@ -408,6 +408,53 @@ fn tools_list_result() -> Value {
"required": ["project_id", "content"] "required": ["project_id", "content"]
} }
}, },
{
"name": "get_blueprint_status",
"description": "检测项目蓝图治理状态CONVENTIONS 规则版本是否最新、蓝图内容是否可能落后于代码synced/rules_outdated/content_stale/none。等价于炼境 UI 项目卡片上的蓝图状态徽章。",
"inputSchema": {
"type": "object",
"properties": {
"project_id": {
"type": "string",
"description": "项目 workspace ID来自 list_group_projects 的返回结果)"
}
},
"required": ["project_id"]
}
},
{
"name": "generate_blueprint_prompt",
"description": "生成蓝图初始化/同步提示词(含 CONVENTIONS 规范与 manifest 严格格式约束)。等价于炼境 UI 治理面板的「初始化蓝图/更新蓝图描绘」按钮。拿到提示词后在该项目目录下执行它(或交给该项目的 AI 会话),完成后可用 get_blueprint_status / get_project_blueprint 验证结果。",
"inputSchema": {
"type": "object",
"properties": {
"project_id": {
"type": "string",
"description": "项目 workspace ID来自 list_group_projects 的返回结果)"
},
"mode": {
"type": "string",
"enum": ["init", "sync"],
"description": "init=项目尚无蓝图时从零初始化sync=已有蓝图,对照代码现状同步更新"
}
},
"required": ["project_id", "mode"]
}
},
{
"name": "sync_blueprint_rules",
"description": "将最新 CONVENTIONS 规则同步到项目(写入 .blueprint/CONVENTIONS.md + 更新 AGENTS.md/CLAUDE.md 的蓝图管理区块)。等价于炼境 UI 治理面板的「同步规则」按钮。仅当 get_blueprint_status 返回 rules_outdated 时需要调用。",
"inputSchema": {
"type": "object",
"properties": {
"project_id": {
"type": "string",
"description": "项目 workspace ID来自 list_group_projects 的返回结果)"
}
},
"required": ["project_id"]
}
},
{ {
"name": "get_project_snapshots", "name": "get_project_snapshots",
"description": "获取项目的历史健康快照(每日一条),包含 git 提交间隔、蓝图完成率、阻塞任务数、错误情况。用于分析趋势、识别高价值改进变量。", "description": "获取项目的历史健康快照(每日一条),包含 git 提交间隔、蓝图完成率、阻塞任务数、错误情况。用于分析趋势、识别高价值改进变量。",
@ -451,6 +498,34 @@ async fn tools_call(group_id: &str, params: Option<&Value>) -> Value {
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or(""); let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
get_project_blueprint(&gid, pid) get_project_blueprint(&gid, pid)
} }
"get_blueprint_status" => {
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
resolve_project_root(&gid, pid).and_then(|root| {
crate::commands::blueprint::get_blueprint_status(root.to_string_lossy().to_string())
.and_then(|s| serde_json::to_string_pretty(&s).map_err(|e| e.to_string()))
})
}
"generate_blueprint_prompt" => {
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
let mode = args.get("mode").and_then(|v| v.as_str()).unwrap_or("sync");
if mode != "init" && mode != "sync" {
Err(format!("mode 只允许 init 或 sync收到: {mode}"))
} else {
resolve_project_root(&gid, pid).and_then(|root| {
crate::commands::blueprint::generate_blueprint_prompt(
root.to_string_lossy().to_string(),
mode.to_string(),
)
})
}
}
"sync_blueprint_rules" => {
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
resolve_project_root(&gid, pid).and_then(|root| {
crate::commands::blueprint::sync_blueprint_rules(root.to_string_lossy().to_string())
.and_then(|r| serde_json::to_string_pretty(&r).map_err(|e| e.to_string()))
})
}
"get_project_file" => { "get_project_file" => {
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or(""); let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
let rel = args let rel = args