feat(flywheel): 阶段三执行质量预测层(trailer 死链修复 + 风险画像) #2

Merged
lanrtop merged 14 commits from feat/flywheel-intelligence into master 2026-07-04 14:45:28 +08:00
Showing only changes of commit f03e4178fc - Show all commits

View File

@ -246,6 +246,17 @@ pub fn tools_list_result() -> Value {
"description": "扫描已登记项目的父目录,找出存在于文件系统但尚未在炼境注册的项目目录。返回每个未注册目录的路径、是否有 .git、技术栈特征文件package.json / Cargo.toml / go.mod、是否有 AGENTS.md。用于日常发现漏网项目并决定是否接入炼境。", "description": "扫描已登记项目的父目录,找出存在于文件系统但尚未在炼境注册的项目目录。返回每个未注册目录的路径、是否有 .git、技术栈特征文件package.json / Cargo.toml / go.mod、是否有 AGENTS.md。用于日常发现漏网项目并决定是否接入炼境。",
"inputSchema": { "type": "object", "properties": {}, "required": [] } "inputSchema": { "type": "object", "properties": {}, "required": [] }
}, },
{
"name": "unregister_project",
"description": "从炼境删除一条项目登记(等价 UI 删除项目按钮的 agent 投影):清理产品组 MCP 配置、关联表、workspace 与孤儿 profile。绝不删除本地文件该能力不对 agent 开放。适用scan_unregistered_projects 的反向操作——清理目录已消失或已无 .git 的死登记。删除不可逆,调用前先向用户列出目标清单确认。",
"inputSchema": {
"type": "object",
"properties": {
"project_id": { "type": "string", "description": "要删除登记的项目 workspace ID" }
},
"required": ["project_id"]
}
},
{ {
"name": "ingest_git_history", "name": "ingest_git_history",
"description": "全量导入 git 历史到飞轮(等价 UI「全量导入」按钮的 agent 投影):解析最近 500 条 commit 的 conventional 指标、Rules-Applied trailer 与触碰文件UPSERT 幂等(存量行自动回填 trailer。不传 project_id 时对所有已登记 Windows 路径项目执行。适用trailer 存量回填、新项目接入后初始化飞轮数据。", "description": "全量导入 git 历史到飞轮(等价 UI「全量导入」按钮的 agent 投影):解析最近 500 条 commit 的 conventional 指标、Rules-Applied trailer 与触碰文件UPSERT 幂等(存量行自动回填 trailer。不传 project_id 时对所有已登记 Windows 路径项目执行。适用trailer 存量回填、新项目接入后初始化飞轮数据。",
@ -486,6 +497,16 @@ pub async fn tools_call(group_id: &str, params: Option<&Value>) -> Value {
} }
"list_all_projects" => list_all_projects(), "list_all_projects" => list_all_projects(),
"scan_unregistered_projects" => scan_unregistered_projects(), "scan_unregistered_projects" => scan_unregistered_projects(),
"unregister_project" => {
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
if pid.is_empty() {
Err("project_id 不能为空".to_string())
} else {
// delete_local 硬编码 falseMCP 侧永不删除本地文件
crate::commands::projects::delete_project(pid.to_string(), Some(false))
.map(|_| format!("已删除登记:{}(本地文件未动)", pid))
}
}
"ingest_git_history" => { "ingest_git_history" => {
let pid = args.get("project_id").and_then(|v| v.as_str()); let pid = args.get("project_id").and_then(|v| v.as_str());
let conn = db::pool().get().map_err(|e| e.to_string())?; let conn = db::pool().get().map_err(|e| e.to_string())?;
@ -1127,6 +1148,7 @@ mod tests {
assert!(names.contains(&"poll_now"), "缺少 poll_now"); assert!(names.contains(&"poll_now"), "缺少 poll_now");
assert!(names.contains(&"predict_task_risk"), "缺少 predict_task_risk"); assert!(names.contains(&"predict_task_risk"), "缺少 predict_task_risk");
assert!(names.contains(&"ingest_git_history"), "缺少 ingest_git_history"); assert!(names.contains(&"ingest_git_history"), "缺少 ingest_git_history");
assert!(names.contains(&"unregister_project"), "缺少 unregister_project");
// 每个工具都有 description 和 inputSchema // 每个工具都有 description 和 inputSchema
for tool in tools { for tool in tools {
let name = tool["name"].as_str().unwrap_or("?"); let name = tool["name"].as_str().unwrap_or("?");