feat(flywheel): 阶段三执行质量预测层(trailer 死链修复 + 风险画像) #2
@ -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 0;FlywheelPanel 新增跨项目返工热区展示(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~E(2026-07-04)
|
||||
|
||||
@ -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_level(low/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("?");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user