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

Merged
lanrtop merged 14 commits from feat/flywheel-intelligence into master 2026-07-04 14:45:28 +08:00
2 changed files with 65 additions and 0 deletions
Showing only changes of commit a77cb4ec96 - Show all commits

View File

@ -191,6 +191,13 @@
- files: src/lib/commands.ts, src/components/insight/FlywheelPanel.tsx, src-tauri/src/commands/risk.rs, src-tauri/src/lib.rs
- acceptance: "[pnpm typecheck] exit 0FlywheelPanel 新增跨项目返工热区展示get_high_risk_scopes 封装同步R01"
### 🔵 P3-F. ingest_git_history agent 按钮(全量导入双投影)
- status: in_progress
- complexity: S
- files: src-tauri/src/mcp_tools.rs
- acceptance: "[MCP tool] ingest_git_history() 不传参对所有 win_path 项目执行 ingest_full_git_history返回逐项目 imported/skipped 汇总;传 project_id 只跑单项目tools_list_result 含断言;[消费者验证] 实调后 rules_applied 非空行 ≥12存量 trailer 回填生效)"
- notes: 消费者验证暴露的死角——「全量导入」只有 UI 投影agent 够不到;按双投影原则补 MCP 投影能力本体ingest_full_git_history不动
## 复盘笔记
【复盘】阶段三执行质量预测层 P3-A~E2026-07-04

View File

@ -246,6 +246,17 @@ pub fn tools_list_result() -> Value {
"description": "扫描已登记项目的父目录,找出存在于文件系统但尚未在炼境注册的项目目录。返回每个未注册目录的路径、是否有 .git、技术栈特征文件package.json / Cargo.toml / go.mod、是否有 AGENTS.md。用于日常发现漏网项目并决定是否接入炼境。",
"inputSchema": { "type": "object", "properties": {}, "required": [] }
},
{
"name": "ingest_git_history",
"description": "全量导入 git 历史到飞轮(等价 UI「全量导入」按钮的 agent 投影):解析最近 500 条 commit 的 conventional 指标、Rules-Applied trailer 与触碰文件UPSERT 幂等(存量行自动回填 trailer。不传 project_id 时对所有已登记 Windows 路径项目执行。适用trailer 存量回填、新项目接入后初始化飞轮数据。",
"inputSchema": {
"type": "object",
"properties": {
"project_id": { "type": "string", "description": "可选。项目 workspace ID省略则遍历所有已登记项目" }
},
"required": []
}
},
{
"name": "predict_task_risk",
"description": "任务风险预测(飞轮阶段三):按 scope 和/或文件清单查询该项目历史返工率,返回 risk_levellow/medium/high、样本量与置信度、热点文件排行。agent 开工前调用——任务卡涉及高返工 scope/文件时提高警惕(多写测试、小步提交)。样本 <5 或 commit 历史不规范时 confidence=low结论仅供参考。",
@ -475,6 +486,52 @@ pub async fn tools_call(group_id: &str, params: Option<&Value>) -> Value {
}
"list_all_projects" => list_all_projects(),
"scan_unregistered_projects" => scan_unregistered_projects(),
"ingest_git_history" => {
let pid = args.get("project_id").and_then(|v| v.as_str());
let conn = db::pool().get().map_err(|e| e.to_string())?;
let targets: Vec<(String, String)> = {
let mut stmt = conn
.prepare(
"SELECT id, win_path FROM project_workspaces
WHERE win_path IS NOT NULL AND (?1 IS NULL OR id = ?1)",
)
.map_err(|e| e.to_string())?;
let rows = stmt
.query_map(rusqlite::params![pid], |r| Ok((r.get(0)?, r.get(1)?)))
.map_err(|e| e.to_string())?
.filter_map(|r| r.ok())
.collect();
rows
};
drop(conn);
if targets.is_empty() {
Err(format!("未找到可导入的项目project_id={:?}", pid))
} else {
let mut lines = Vec::new();
let (mut ok, mut failed) = (0u32, 0u32);
for (id, path) in &targets {
match crate::commands::commit_metrics::ingest_full_git_history(
id.clone(),
path.clone(),
) {
Ok(r) => {
ok += 1;
lines.push(format!("- {}: 写入 {} 条,跳过 {}", id, r.imported, r.skipped));
}
Err(e) => {
failed += 1;
lines.push(format!("- {}: ❌ {}", id, e));
}
}
}
Ok(format!(
"全量导入完成:{} 个项目成功,{} 个失败\n\n{}",
ok,
failed,
lines.join("\n")
))
}
}
"predict_task_risk" => {
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
let scope = args.get("scope").and_then(|v| v.as_str());
@ -1069,6 +1126,7 @@ mod tests {
assert!(names.contains(&"create_pull_request"), "缺少 create_pull_request");
assert!(names.contains(&"poll_now"), "缺少 poll_now");
assert!(names.contains(&"predict_task_risk"), "缺少 predict_task_risk");
assert!(names.contains(&"ingest_git_history"), "缺少 ingest_git_history");
// 每个工具都有 description 和 inputSchema
for tool in tools {
let name = tool["name"].as_str().unwrap_or("?");