feat(mcp/diagnostics): get_diagnostics 新增使者回传覆盖率「沉默项目」信号(B4)
All checks were successful
Push & PR Check / check (push) Successful in 49s

近 30 天有 commit 采集但零使者回传的已登记项目浮出水面,
提示去该项目跑 /envoy——回流触发不自动化(辩证决策 2026-07-15),
用可观测性让遗忘可见,人留在过滤环里。

Rules-Applied: R06,R07
This commit is contained in:
lanrtop 2026-07-15 14:10:28 +09:00
parent aaf3343133
commit 2fe576b75a
4 changed files with 113 additions and 4 deletions

View File

@ -372,7 +372,7 @@ modules:
area: backend
status: in_progress
progress: 99
note: "体系分发主线T1/T2/B1/B2/B3/V1/U3 ✅(U3 2026-07-15Rules-Applied ingest 层校验+回溯清洗);剩 📋 U1全托管升级通道+批量重分发/ B4遵守度+回传覆盖率,吸收被否决的 U2"
note: "体系分发主线T1/T2/B1/B2/B3/V1/U3/B42026-07-15U3 Rules-Applied ingest 层校验+回溯清洗B4 沉默项目回传覆盖率诊断);剩 📋 U1全托管升级通道+批量重分发)"
position: [3500, 0]
updated: 2026-07-15

View File

@ -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 身份解析形状),未登记项目不列入(无法跑 /envoyget_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 活动 → 不报

View File

@ -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<Vec<(String, i64)>, 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 的回传过期后也应列为沉默");
}
}

View File

@ -752,6 +752,20 @@ fn get_diagnostics() -> Result<String, String> {
}
}
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!("- **{}** — {} 条 commit0 回传\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));