feat(flywheel): ingest_git_history agent 按钮——全量导入双投影
消费者验证暴露的死角:「全量导入」只有 UI 投影,agent 够不到, 存量 trailer 回填只能靠人点按钮。按双投影原则补 MCP 投影: - 不传 project_id 遍历所有 win_path 项目跑 ingest_full_git_history, 传则单项目;返回逐项目 imported/skipped 汇总,单项目失败不中断 - 能力本体(ingest_full_git_history)不动,UI 与 MCP 调同一函数 - tools_list_result 断言驻军,92 测试全绿 Rules-Applied: R12,R13
This commit is contained in:
parent
a5cb30734d
commit
a77cb4ec96
@ -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
|
- 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)"
|
- 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)
|
【复盘】阶段三执行质量预测层 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。用于日常发现漏网项目并决定是否接入炼境。",
|
"description": "扫描已登记项目的父目录,找出存在于文件系统但尚未在炼境注册的项目目录。返回每个未注册目录的路径、是否有 .git、技术栈特征文件(package.json / Cargo.toml / go.mod)、是否有 AGENTS.md。用于日常发现漏网项目并决定是否接入炼境。",
|
||||||
"inputSchema": { "type": "object", "properties": {}, "required": [] }
|
"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",
|
"name": "predict_task_risk",
|
||||||
"description": "任务风险预测(飞轮阶段三):按 scope 和/或文件清单查询该项目历史返工率,返回 risk_level(low/medium/high)、样本量与置信度、热点文件排行。agent 开工前调用——任务卡涉及高返工 scope/文件时提高警惕(多写测试、小步提交)。样本 <5 或 commit 历史不规范时 confidence=low,结论仅供参考。",
|
"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(),
|
"list_all_projects" => list_all_projects(),
|
||||||
"scan_unregistered_projects" => scan_unregistered_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" => {
|
"predict_task_risk" => {
|
||||||
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 scope = args.get("scope").and_then(|v| v.as_str());
|
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(&"create_pull_request"), "缺少 create_pull_request");
|
||||||
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");
|
||||||
// 每个工具都有 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("?");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user