From 2fe576b75a5843aba9da713d7259d22a6cc7fd14 Mon Sep 17 00:00:00 2001 From: lanrtop Date: Wed, 15 Jul 2026 14:10:28 +0900 Subject: [PATCH] =?UTF-8?q?feat(mcp/diagnostics):=20get=5Fdiagnostics=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BD=BF=E8=80=85=E5=9B=9E=E4=BC=A0=E8=A6=86?= =?UTF-8?q?=E7=9B=96=E7=8E=87=E3=80=8C=E6=B2=89=E9=BB=98=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E3=80=8D=E4=BF=A1=E5=8F=B7=EF=BC=88B4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 近 30 天有 commit 采集但零使者回传的已登记项目浮出水面, 提示去该项目跑 /envoy——回流触发不自动化(辩证决策 2026-07-15), 用可观测性让遗忘可见,人留在过滤环里。 Rules-Applied: R06,R07 --- .blueprint/manifest.yaml | 2 +- .blueprint/modules/onboarding-pack.md | 7 +- src-tauri/src/commands/onboarding.rs | 94 +++++++++++++++++++++++++++ src-tauri/src/mcp_tools.rs | 14 ++++ 4 files changed, 113 insertions(+), 4 deletions(-) diff --git a/.blueprint/manifest.yaml b/.blueprint/manifest.yaml index efe4d20..94f9748 100644 --- a/.blueprint/manifest.yaml +++ b/.blueprint/manifest.yaml @@ -372,7 +372,7 @@ modules: area: backend status: in_progress progress: 99 - note: "体系分发主线:T1/T2/B1/B2/B3/V1/U3 ✅(U3 2026-07-15:Rules-Applied ingest 层校验+回溯清洗);剩 📋 U1(全托管升级通道+批量重分发)/ B4(遵守度+回传覆盖率,吸收被否决的 U2)" + note: "体系分发主线:T1/T2/B1/B2/B3/V1/U3/B4 ✅(2026-07-15:U3 Rules-Applied ingest 层校验+回溯清洗;B4 沉默项目回传覆盖率诊断);剩 📋 U1(全托管升级通道+批量重分发)" position: [3500, 0] updated: 2026-07-15 diff --git a/.blueprint/modules/onboarding-pack.md b/.blueprint/modules/onboarding-pack.md index e84ce96..6d2382f 100644 --- a/.blueprint/modules/onboarding-pack.md +++ b/.blueprint/modules/onboarding-pack.md @@ -134,7 +134,7 @@ - 版本化闭环要求(2026-07-03 用户澄清,体系分发的灵魂): 分发不是"发了就不管"——每类资产带版本分发 → 子项目按该版本实践产生经验(成功/失败)→ 经验自吸反哺炼境(trailer/failure_cases/复盘/返工率)→ 按版本分组对比效果 → 修订出新版 → 再分发。CONVENTIONS 是模范生(version+队列对比+梦核+同步,1.8→1.9 已跑通一整圈);B 系列每类资产按此标准建闭环。**前置依赖提升**:「接入包模板演进分发(MANAGED 块化)」concept 卡——模板无版本机制是当前最大缺口,没有它体系 v2 到不了存量项目 - notes: 2026-07-03 概念探讨收束后的分发需求(体系详见 memory concept_dual_projection 与 mcp-server 模块 concept 卡)。用户全 agent 开发形态下,"给开发者立规范"="给 agent 注入规范",可完全机器化。四条现成管道分发四类资产:① B1(S) CONVENTIONS 1.10.0 加 R12 边界单点实现 + R13 验收须真实走通验证路径 + 载体选择原则;② B2(S) 接入包 AGENTS.md 模板加 agent 按钮章节(三要素/载体谱系/侦察驻军);③ B3(M) init/sync 提示词升级——建蓝图时声明项目按钮载体、acceptance 生成为可执行验证路径格式;④ B4(S,后置) 健康诊断加体系遵守度检查。移植体验=零新增动作,接入流程不变。 -### 📋 U1. 全托管文件升级通道 + 批量重分发(择升环断点修复 ①) +### 🔵 U1. 全托管文件升级通道 + 批量重分发(择升环断点修复 ①) - status: planned - complexity: M - depends: onboarding-pack(卡 T1) @@ -150,8 +150,9 @@ - acceptance: [cargo test] trailer 解析校验纯函数——规则 ID 不存在于该项目 .blueprint/CONVENTIONS.md(如 R99)→ 从 rules_applied 剔除并可观测(日志或 invalid 计数),真实 ID 保留;项目无 CONVENTIONS.md → 不校验全保留;[MCP tool] ingest_git_history 重跑 → 「规则命中 × 返工」交叉统计不再含未知 ID(存量污染可回溯清洗) - notes: 辩证翻转载体:原方案(子项目 commit-msg hook)违反 R12 边界单点——统计消费端在炼境,校验就该在炼境 ingest 层。收益:零分发成本覆盖全部项目、重跑即回溯清洗、cargo test 可测(炼境自身 agent 按钮载体)。commit-msg hook 即时反馈版降为可选增强(若做依赖 U1 通道,不在本卡)。上限诚实:只挡 typo/幻觉 ID,「真 ID 假触发」留给宁缺毋滥纪律 -### 📋 B4. 健康诊断遵守度 + 回传覆盖率(吸收被否决的 U2,择升环断点修复 ③) -- status: planned +### ✅ B4. 健康诊断遵守度 + 回传覆盖率(吸收被否决的 U2,择升环断点修复 ③) +- status: done +- done_note: 2026-07-15 完成。onboarding.rs 新增 silent_projects(conn)(R06 注入):近 30 天有 commit_metrics 但零使者回传的已登记项目,SQL 经 blueprint_buffs UUID→win_path→project_workspaces 双路径关联(复用 get_all_commit_health 身份解析形状),未登记项目不列入(无法跑 /envoy);get_diagnostics 新增「使者回传覆盖率」段,沉默项目列名称+commit 数并提示跑 /envoy。cargo test 99 全过(新测覆盖:UUID 关联命名/普通笔记不算回传/未登记不列入/回传过期重新变沉默) - complexity: S - files: src-tauri/src/mcp_tools.rs, src-tauri/src/commands/onboarding.rs - acceptance: [MCP tool] get_diagnostics(或 verify_onboarding 检查链)新增「沉默项目」信号:项目近 30 天 commit_metrics 有数据但使者回传(list_envoy_returns 同源数据)为 0 → 列出项目名 + commit 数,提示去该项目跑 /envoy;近 30 天有回传或无 commit 活动 → 不报 diff --git a/src-tauri/src/commands/onboarding.rs b/src-tauri/src/commands/onboarding.rs index c115e4d..e4221b7 100644 --- a/src-tauri/src/commands/onboarding.rs +++ b/src-tauri/src/commands/onboarding.rs @@ -408,6 +408,43 @@ pub fn verify_onboarding( checks } +// ── B4:使者回传覆盖率(「沉默项目」信号)────────────────────────────────────── + +/// 「沉默项目」:近 30 天有 commit 采集但零使者回传的已登记项目(名称, commit 数)。 +/// +/// 回流触发不自动化(决策 2026-07-15):不在子项目装 hook 催回传, +/// 而是让遗忘可被看见——人看到信号后自行决定是否去该项目跑 /envoy。 +/// commit_metrics.project_id 可能是 blueprint_buffs 的 UUID,经 buff 路径 +/// 关联回 project_workspaces;未登记项目(无法跑 /envoy)不列入。 +pub fn silent_projects(conn: &rusqlite::Connection) -> Result, String> { + let mut stmt = conn + .prepare( + "SELECT + COALESCE(pp.name, pw.id) AS name, + COUNT(*) AS commits + FROM commit_metrics cm + LEFT JOIN blueprint_buffs bb ON bb.project_id = cm.project_id + LEFT JOIN project_workspaces pw + ON pw.id = cm.project_id OR pw.win_path = bb.project_path + LEFT JOIN project_profiles pp ON pw.profile_id = pp.id + WHERE cm.committed_at >= datetime('now','-30 days') + AND pw.id IS NOT NULL + GROUP BY pw.id + HAVING (SELECT COUNT(*) FROM project_notes pn + WHERE pn.project_id = pw.id + AND pn.content LIKE '【炼境使者回传】%' + AND pn.created_at >= datetime('now','localtime','-30 days')) = 0 + ORDER BY commits DESC", + ) + .map_err(|e| e.to_string())?; + let rows = stmt + .query_map([], |r| Ok((r.get::<_, String>(0)?, r.get::<_, i64>(1)?))) + .map_err(|e| e.to_string())? + .filter_map(|r| r.ok()) + .collect(); + Ok(rows) +} + #[cfg(test)] mod tests { use super::*; @@ -602,5 +639,62 @@ mod tests { assert!(!check_managed_version(None, expected).0, "文件缺失应 fail"); assert!(!check_managed_version(Some("手写内容,无标记"), expected).0, "无标记应 fail"); } + + // ── B4:使者回传覆盖率(沉默项目信号)──────────────────────────────────── + + #[test] + fn silent_projects_flags_active_without_returns() { + let conn = crate::db::conn_with_schema(); + // 两个已登记项目:pw-a 有 commit 无回传(沉默);pw-b 有 commit 有回传 + conn.execute_batch( + "INSERT INTO project_profiles (id, name) VALUES ('prof-a','项目A'),('prof-b','项目B'); + INSERT INTO project_workspaces (id, profile_id, win_path) + VALUES ('pw-a','prof-a','E:/a'),('pw-b','prof-b','E:/b');", + ) + .unwrap(); + // pw-a 的 commit 走 blueprint_buffs UUID 关联(生产真实形状) + conn.execute( + "INSERT INTO blueprint_buffs (project_id, project_path) VALUES ('uuid-a','E:/a')", + [], + ) + .unwrap(); + conn.execute( + "INSERT INTO commit_metrics (sha, project_id, commit_type, is_rework, is_ci_auto, committed_at, parsed_at) + VALUES ('c1','uuid-a','feat',0,0,datetime('now'),datetime('now')), + ('c2','uuid-a','fix',1,0,datetime('now'),datetime('now')), + ('c3','pw-b','feat',0,0,datetime('now'),datetime('now'))", + [], + ) + .unwrap(); + // pw-b 近 30 天有使者回传;pw-a 只有普通笔记(不算回传) + conn.execute( + "INSERT INTO project_notes (project_id, content) VALUES + ('pw-b','【炼境使者回传】来源: 项目B ...'), + ('pw-a','普通笔记,不是回传')", + [], + ) + .unwrap(); + // 未登记项目的 commit 不列入(无法跑 /envoy) + conn.execute( + "INSERT INTO commit_metrics (sha, project_id, commit_type, is_rework, is_ci_auto, committed_at, parsed_at) + VALUES ('c9','orphan','feat',0,0,datetime('now'),datetime('now'))", + [], + ) + .unwrap(); + + let silent = silent_projects(&conn).unwrap(); + assert_eq!(silent.len(), 1, "只有 pw-a 沉默:{silent:?}"); + assert_eq!(silent[0].0, "项目A", "经 buff UUID 关联解析出名称"); + assert_eq!(silent[0].1, 2, "commit 数按项目聚合"); + + // 老回传(>30 天)不算覆盖:把 pw-b 的回传改旧后应变沉默 + conn.execute( + "UPDATE project_notes SET created_at = datetime('now','localtime','-40 days') WHERE project_id = 'pw-b'", + [], + ) + .unwrap(); + let silent = silent_projects(&conn).unwrap(); + assert_eq!(silent.len(), 2, "pw-b 的回传过期后也应列为沉默"); + } } diff --git a/src-tauri/src/mcp_tools.rs b/src-tauri/src/mcp_tools.rs index c8a01bd..8f16191 100644 --- a/src-tauri/src/mcp_tools.rs +++ b/src-tauri/src/mcp_tools.rs @@ -752,6 +752,20 @@ fn get_diagnostics() -> Result { } } + out.push_str("\n### 使者回传覆盖率(近 30 天)\n\n"); + match crate::commands::onboarding::silent_projects(&conn) { + Ok(list) if list.is_empty() => { + out.push_str("_无沉默项目:有 commit 活动的已接入项目均有使者回传(或近期无活动)_ ✅\n"); + } + Ok(list) => { + out.push_str("以下项目近 30 天有 commit 活动但 **0 条使者回传**——环的回流段在这些项目上是断的,考虑去该项目会话跑一次 `/envoy`:\n\n"); + for (name, commits) in &list { + out.push_str(&format!("- **{}** — {} 条 commit,0 回传\n", name, commits)); + } + } + Err(e) => out.push_str(&format!("_查询失败: {e}_\n")), + } + out.push_str("\n### 系统健康\n\n"); let report = crate::commands::settings::collect_health_report(); out.push_str(&format!("检查时间:{}\n\n", report.checked_at));