From 66dc6209a85494c350abf7d85ccdb3ece70826cc Mon Sep 17 00:00:00 2001 From: lanrtop Date: Sat, 4 Jul 2026 14:18:04 +0900 Subject: [PATCH] =?UTF-8?q?feat(flywheel):=20=E6=A2=A6=E6=A0=B8=20prompt?= =?UTF-8?q?=20=E6=B3=A8=E5=85=A5=E8=B7=A8=E9=A1=B9=E7=9B=AE=E9=AB=98?= =?UTF-8?q?=E9=A3=8E=E9=99=A9=20scope=20=E6=95=B0=E6=8D=AE=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - risk.rs 新增 high_risk_scopes:样本 ≥5 且有非 CI 返工的 scope 按返工率降序 top 10,项目名 LEFT JOIN project_profiles(缺失回退 id) - generate_muhe_prompt 新增「跨项目高风险 scope」表格段,附分析指引 (反复返工 scope = 结构性脆弱点 → 固化环节输出预防性规范) - rule_hits 段 pool() 改 try_pool():原实现测试环境必 panic, generate_muhe_prompt 因此不可测;改后两段在无 DB 时走占位文案 - prompt 构建测试断言两个数据段存在 + 聚合排序/最小样本过滤测试,92 全绿 Rules-Applied: R06,R07 --- .blueprint/modules/flywheel-intelligence.md | 5 +- src-tauri/src/commands/blueprint.rs | 62 ++++++++++++++++- src-tauri/src/commands/risk.rs | 77 +++++++++++++++++++++ 3 files changed, 139 insertions(+), 5 deletions(-) diff --git a/.blueprint/modules/flywheel-intelligence.md b/.blueprint/modules/flywheel-intelligence.md index f08bc3a..8f3f8fc 100644 --- a/.blueprint/modules/flywheel-intelligence.md +++ b/.blueprint/modules/flywheel-intelligence.md @@ -175,8 +175,9 @@ - files: src-tauri/src/commands/risk.rs, src-tauri/src/commands/mod.rs, src-tauri/src/mcp_tools.rs - acceptance: "[MCP tool] predict_task_risk(project_id, scope 或 files) → JSON 含 risk_level/rework_rate/sample_size/confidence/hot_files,无 error 字段;sample_size<5 时 confidence=low;[cargo test] 风险聚合纯函数(conn 注入 + in-memory DB)覆盖高返工 scope、小样本、脏历史(conventional 率低标不可信)三种输入;tools_list_result 含 predict_task_risk 断言" -### 📋 P3-D. 梦核 prompt 高风险 scope 数据段 -- status: todo +### ✅ P3-D. 梦核 prompt 高风险 scope 数据段 +- status: done +- done_note: 2026-07-04 完成。risk.rs 新增 high_risk_scopes(样本 ≥5 且有非 CI 返工,返工率降序,项目名从 project_profiles 解析缺失回退 id);generate_muhe_prompt 注入「跨项目高风险 scope」表格段(含分析指引:反复返工 scope=结构性脆弱点→固化环节输出预防性规范);顺手把 rule_hits 段的 pool() 改 try_pool()(原实现测试环境必 panic,generate_muhe_prompt 因此不可测);prompt 构建测试 + 聚合排序过滤测试,92 全绿 - complexity: S - depends: flywheel-intelligence(卡 P3-C) - files: src-tauri/src/commands/blueprint.rs diff --git a/src-tauri/src/commands/blueprint.rs b/src-tauri/src/commands/blueprint.rs index 64a98c3..0ca50cf 100644 --- a/src-tauri/src/commands/blueprint.rs +++ b/src-tauri/src/commands/blueprint.rs @@ -1964,9 +1964,9 @@ pub fn generate_muhe_prompt(stats: FlywheelStats) -> String { let notes_total = stats.project_notes.len(); // ── 规则命中 × 返工交叉(Rules-Applied trailer,飞轮 R1)───────────────── - let rule_hits_summary = crate::db::pool() - .get() - .ok() + // try_pool:测试环境 pool 未初始化时走占位文案,不 panic + let rule_hits_summary = crate::db::try_pool() + .and_then(|p| p.get().ok()) .and_then(|conn| crate::commands::commit_metrics::rule_hit_stats(&conn).ok()) .filter(|v| !v.is_empty()) .map(|v| { @@ -1980,6 +1980,22 @@ pub fn generate_muhe_prompt(stats: FlywheelStats) -> String { }) .unwrap_or_else(|| "(暂无数据——commit message 附带 Rules-Applied trailer 后自动积累)".to_string()); + // ── 跨项目高风险 scope(飞轮阶段三预测层,样本 ≥5 且有返工)───────────── + let high_risk_summary = crate::db::try_pool() + .and_then(|p| p.get().ok()) + .and_then(|conn| crate::commands::risk::high_risk_scopes(&conn, 5, 10).ok()) + .filter(|v| !v.is_empty()) + .map(|v| { + let header = "| 项目 | scope | commit 数 | 返工数 | 返工率 |"; + let sep = "|------|-------|-----------|--------|--------|"; + let rows: Vec = v.iter().map(|s| { + format!("| {} | {} | {} | {} | {:.0}% |", + s.project_name, s.scope, s.commits, s.rework, s.rework_rate * 100.0) + }).collect(); + format!("{header}\n{sep}\n{}", rows.join("\n")) + }) + .unwrap_or_else(|| "(暂无数据——需 scope 样本 ≥5 且存在非 CI 返工)".to_string()); + format!(r#"你是炼境(Dev Manager)的蓝图架构师,负责对飞轮运转数据进行记忆巩固分析。 ## 飞轮运转数据(来自炼境跨项目聚合) @@ -2027,6 +2043,13 @@ pub fn generate_muhe_prompt(stats: FlywheelStats) -> String { {rule_hits_summary} +### 跨项目高风险 scope(飞轮阶段三预测层) + +> 历史返工率最高的模块热区。反复返工的 scope = 结构性脆弱点, +> 是「固化」环节输出预防性规范、或提示该模块需要重构/补测试的信号。 + +{high_risk_summary} + --- ## 当前 CONVENTIONS.md(v{conventions_version})规则摘要 @@ -2731,5 +2754,38 @@ mod tests { let _ = std::fs::remove_dir_all(&dir); } + + // ── generate_muhe_prompt(P3-D 梦核数据段)─────────────────────────────── + + #[test] + fn muhe_prompt_contains_high_risk_scope_section() { + let stats = FlywheelStats { + cohorts: vec![], + top_blocked_reasons: vec![], + stalled_projects: vec![], + ai_doc_stale_projects: vec![], + iteration_jumps: vec![], + stalled_modules: vec![], + total_projects_scanned: 0, + projects_with_data: 0, + project_notes: vec![], + execution_quality: ExecutionQuality { + complexity_dist: Default::default(), + total_rework_count: 0, + avg_rounds_m: None, + }, + }; + let prompt = generate_muhe_prompt(stats); + assert!( + prompt.contains("### 跨项目高风险 scope"), + "梦核 prompt 应含高风险 scope 数据段" + ); + assert!( + prompt.contains("### 规则命中 × 返工交叉"), + "规则置信度数据段应保留" + ); + // 测试环境 pool 未初始化 → 两段都应走占位文案而非 panic + assert!(prompt.contains("暂无数据")); + } } diff --git a/src-tauri/src/commands/risk.rs b/src-tauri/src/commands/risk.rs index 5859970..384ffd4 100644 --- a/src-tauri/src/commands/risk.rs +++ b/src-tauri/src/commands/risk.rs @@ -111,6 +111,62 @@ fn file_risks(conn: &Connection, project_id: &str, files: &[String]) -> Result Result, String> { + let mut stmt = conn + .prepare( + "SELECT COALESCE(pp.name, cm.project_id) AS pname, cm.scope, + COUNT(*) AS c, + COALESCE(SUM(CASE WHEN cm.is_rework = 1 AND cm.is_ci_auto = 0 THEN 1 ELSE 0 END), 0) AS rw + FROM commit_metrics cm + LEFT JOIN project_workspaces pw ON pw.id = cm.project_id + LEFT JOIN project_profiles pp ON pp.id = pw.profile_id + WHERE cm.scope IS NOT NULL + GROUP BY cm.project_id, cm.scope + HAVING c >= ?1 AND rw > 0 + ORDER BY CAST(rw AS REAL) / c DESC, c DESC + LIMIT ?2", + ) + .map_err(|e| e.to_string())?; + let rows = stmt + .query_map(rusqlite::params![min_commits, limit as i64], |r| { + Ok(( + r.get::<_, String>(0)?, + r.get::<_, String>(1)?, + r.get::<_, u32>(2)?, + r.get::<_, u32>(3)?, + )) + }) + .map_err(|e| e.to_string())?; + let mut result = Vec::new(); + for row in rows { + let (project_name, scope, commits, rework) = row.map_err(|e| e.to_string())?; + result.push(HighRiskScope { + project_name, + scope, + rework_rate: rate(rework, commits), + commits, + rework, + }); + } + Ok(result) +} + /// 核心入口:按 scope 和/或文件清单预测任务风险。 pub fn predict_task_risk( conn: &Connection, @@ -280,6 +336,27 @@ mod tests { assert_eq!(r.risk_level, "high", "hot.rs 返工率 0.5 应分级 high"); } + #[test] + fn high_risk_scopes_orders_by_rate_with_min_sample() { + let conn = crate::db::conn_with_schema(); + // proj-a/hot:4 commits 2 fix(rate 0.5);proj-b/warm:5 commits 1 fix(rate 0.2) + // proj-a/tiny:2 commits 1 fix——低于 min_commits=3 应被过滤 + for (sha, subj) in [ + ("a1", "fix(hot): x"), ("a2", "fix(hot): y"), ("a3", "feat(hot): z"), ("a4", "chore(hot): w"), + ("b1", "fix(warm): x"), ("b2", "feat(warm): a"), ("b3", "feat(warm): b"), + ("b4", "feat(warm): c"), ("b5", "feat(warm): d"), + ("t1", "fix(tiny): x"), ("t2", "feat(tiny): y"), + ] { + let pid = if sha.starts_with('a') || sha.starts_with('t') { "proj-a" } else { "proj-b" }; + seed(&conn, pid, sha, subj, &[]); + } + let top = high_risk_scopes(&conn, 3, 10).unwrap(); + assert_eq!(top.len(), 2, "tiny 样本不足应被过滤"); + assert_eq!(top[0].scope, "hot", "返工率高的排前"); + assert_eq!(top[0].project_name, "proj-a", "无登记项目回退 project_id"); + assert_eq!(top[1].scope, "warm"); + } + #[test] fn requires_scope_or_files() { let conn = crate::db::conn_with_schema();