feat(onboarding): 接入包数据可信度提示(commit 规范化比例)

get_commit_stats 暴露项目 commit 规范化统计(total/conventional/rework);治理面板常驻展示规范化比例 + 返工数,比例 <60% 提示种 lefthook。接入包 A/B/C1/C2/D 全部完成。
This commit is contained in:
lanrtop 2026-06-19 13:52:09 +09:00
parent 150535ffcf
commit 58d2c79c74
5 changed files with 56 additions and 3 deletions

View File

@ -93,8 +93,8 @@ git 是「过去时 + 量化」(做了什么),蓝图是「将来时 + 语
- files: src-tauri/src/commands/buff.rs, src-tauri/src/commands/blueprint.rs
- acceptance: check_project 用 `last_commit..HEAD` 取 commit messagegit log复用现有 WSL 路径处理)→ 调 C1 解析写 commit_metrics首次接入last_commit 空)限解析最近 N 条append_usage_snapshot 的 rework_count 改查 commit_metricsis_rework=1 AND is_ci_auto=0取代 parse_notes_metrics
### 🔵 D 数据可信度提示
- status: planned
### D 数据可信度提示
- status: done
- complexity: S
- depends: onboarding-pack卡 C1、卡 B
- files: src-tauri/src/commands/onboarding.rs, src-tauri/src/commands/commit_metrics.rs

View File

@ -4,6 +4,7 @@
//! 取代枯竭的「复盘笔记解析」路径。纯解析逻辑(`parse_conventional`)与 DB 无关,可独立单测。
use rusqlite::Connection;
use serde::{Deserialize, Serialize};
/// 一条 commit 的解析结果。
#[derive(Debug, Clone, PartialEq, Eq)]
@ -114,7 +115,6 @@ pub fn rework_count(conn: &Connection, project_id: &str) -> Result<u32, String>
}
/// commit 规范化统计:(总数, conventional 数)。供数据可信度提示(卡 D使用。
#[allow(dead_code)]
pub fn conventional_stats(conn: &Connection, project_id: &str) -> Result<(u32, u32), String> {
let total: i64 = conn
.query_row(
@ -133,6 +133,27 @@ pub fn conventional_stats(conn: &Connection, project_id: &str) -> Result<(u32, u
Ok((total as u32, conv as u32))
}
/// 项目 commit 执行质量统计(供前端数据可信度展示)。
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CommitStats {
pub total: u32,
pub conventional: u32,
pub rework: u32,
}
/// 查询某项目的 commit 执行质量统计(总数 / conventional 数 / 有效返工数)。
#[tauri::command]
pub fn get_commit_stats(project_id: String) -> Result<CommitStats, String> {
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
let (total, conventional) = conventional_stats(&conn, &project_id)?;
let rework = rework_count(&conn, &project_id)?;
Ok(CommitStats {
total,
conventional,
rework,
})
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -267,6 +267,7 @@ pub fn run() {
list_buffed_projects,
// onboarding pack
commands::onboarding::apply_onboarding_pack,
commands::commit_metrics::get_commit_stats,
// mentor
get_mentor_context,
get_project_notes,

View File

@ -23,6 +23,7 @@ import {
generateMuhePrompt,
archiveProjectDocs,
applyOnboardingPack,
getCommitStats,
removeBlueprintBuff,
getBuffStatus,
getUserConventionsStatus,
@ -39,6 +40,7 @@ import {
type StalledProject,
type BuffStatus,
type TemplateResult,
type CommitStats,
type UserConventionsStatus,
} from "../../lib/commands";
@ -1061,6 +1063,13 @@ function GovernancePanel({
staleTime: 5000,
});
const { data: commitStats } = useQuery<CommitStats | null>({
queryKey: ["commit-stats-governance", projectId],
queryFn: () => (projectId ? getCommitStats(projectId) : Promise.resolve(null)),
enabled: !!projectId,
staleTime: 5000,
});
const [syncing, setSyncing] = useState(false);
const [syncResult, setSyncResult] = useState<SyncResult | null>(null);
const [syncError, setSyncError] = useState<string | null>(null);
@ -1521,6 +1530,19 @@ function GovernancePanel({
</button>
</div>
)}
{buffStatus?.status === "active" && commitStats && commitStats.total > 0 && (
<div className="pt-1.5 border-t border-gray-100 space-y-0.5">
<p className="text-[10px] text-gray-500">
commit {commitStats.conventional}/{commitStats.total}
{Math.round((commitStats.conventional / commitStats.total) * 100)}% · {commitStats.rework}
</p>
{commitStats.conventional / commitStats.total < 0.6 && (
<p className="text-[10px] text-amber-600 leading-relaxed">
lefthook
</p>
)}
</div>
)}
</div>
{/* 项目信息 */}

View File

@ -1615,6 +1615,15 @@ export interface OnboardingReport {
export const applyOnboardingPack = (projectPath: string, projectId: string) =>
invoke<OnboardingReport>("apply_onboarding_pack", { projectPath, projectId });
export interface CommitStats {
total: number;
conventional: number;
rework: number;
}
export const getCommitStats = (projectId: string) =>
invoke<CommitStats>("get_commit_stats", { projectId });
// ── Beast Global Status (read-only) ───────────────────────────────────────────
export interface BeastGlobalStatus {