From 532e28c4219d7fca92b09c77f792d6794c34ea18 Mon Sep 17 00:00:00 2001 From: lanrtop Date: Mon, 29 Jun 2026 20:58:05 +0900 Subject: [PATCH] =?UTF-8?q?feat(flywheel-L0):=20F2=20=E9=A3=9E=E8=BD=AE?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=20Git=20=E5=81=A5=E5=BA=B7=E5=BA=A6=E5=8C=BA?= =?UTF-8?q?=E5=9D=97=20+=20=E5=85=A8=E9=87=8F=E5=AF=BC=E5=85=A5=E6=8C=89?= =?UTF-8?q?=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FlywheelPanel 新增「Git 执行质量」卡片:表格展示 commits/规范化率/返工率/TOP scope - 「全量导入」按钮对所有接入项目触发 ingest_full_git_history,完成后刷新健康度数据 - 颜色编码:规范化率 ≥80% 绿/≥50% 黄/其余红,返工率 >20% 红/>10% 黄 --- .blueprint/modules/flywheel-intelligence.md | 8 +- src/components/dashboard/BlueprintModal.tsx | 99 ++++++++++++++++++++- 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/.blueprint/modules/flywheel-intelligence.md b/.blueprint/modules/flywheel-intelligence.md index b5c6977..79f9530 100644 --- a/.blueprint/modules/flywheel-intelligence.md +++ b/.blueprint/modules/flywheel-intelligence.md @@ -110,14 +110,14 @@ --- -### 📋 F1. Git 健康度后端——全量历史导入 + 跨项目聚合 -- status: todo +### ✅ F1. Git 健康度后端——全量历史导入 + 跨项目聚合 +- status: done - complexity: M - files: src-tauri/src/commands/commit_metrics.rs, src-tauri/src/lib.rs, src/lib/commands.ts - acceptance: 新增 `ingest_full_git_history(project_id, project_path)` 命令(最多 500 条,INSERT OR IGNORE 幂等);新增 `get_project_commit_health(project_ids)` 命令返回各项目 conventional_rate/rework_rate/top_scopes;单元测试覆盖 ingest 幂等性和 health 聚合 -### 📋 F2. 飞轮面板——Git 健康度区块 + 全量导入按钮 -- status: todo +### ✅ F2. 飞轮面板——Git 健康度区块 + 全量导入按钮 +- status: done - complexity: M - depends: F1 - files: src/lib/commands.ts, src/components/dashboard/BlueprintModal.tsx diff --git a/src/components/dashboard/BlueprintModal.tsx b/src/components/dashboard/BlueprintModal.tsx index 9ac4196..b43ed66 100644 --- a/src/components/dashboard/BlueprintModal.tsx +++ b/src/components/dashboard/BlueprintModal.tsx @@ -43,6 +43,9 @@ import { type TemplateResult, type CommitStats, type UserConventionsStatus, + type ProjectCommitHealth, + ingestFullGitHistory, + getProjectCommitHealth, } from "../../lib/commands"; interface Props { @@ -1660,23 +1663,34 @@ function FlywheelPanel({ onClose }: { onClose: () => void }) { queryFn: () => getProjects(), }); - const { projectPaths, projectsMap } = useMemo(() => { + const { projectPaths, projectsMap, projectsWithPath } = useMemo(() => { const paths = (projects ?? []) .map((p) => p.win_path || p.wsl_path || "") .filter(Boolean); const map: Record = {}; + const withPath: Array<{ id: string; name: string; path: string }> = []; for (const p of (projects ?? [])) { const path = p.win_path || p.wsl_path || ""; if (!path) continue; - // 用炼境项目名做 key + withPath.push({ id: p.id, name: p.name, path }); map[p.name] = path; - // 同时用路径末尾目录名做备用 key(usage.json 里存的是目录名) const dirName = path.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? ""; if (dirName && !map[dirName]) map[dirName] = path; } - return { projectPaths: paths, projectsMap: map }; + return { projectPaths: paths, projectsMap: map, projectsWithPath: withPath }; }, [projects]); + const projectIds = useMemo(() => projectsWithPath.map((p) => p.id), [projectsWithPath]); + + const { data: commitHealth, refetch: refetchHealth } = useQuery({ + queryKey: ["commit-health", projectIds], + queryFn: () => getProjectCommitHealth(projectIds), + enabled: projectIds.length > 0, + }); + + const [ingesting, setIngesting] = useState(false); + const [ingestSummary, setIngestSummary] = useState(null); + const { data: stats, isLoading } = useQuery({ queryKey: ["flywheel-stats", projectPaths], queryFn: () => getFlywheelStats(projectPaths), @@ -1690,6 +1704,25 @@ function FlywheelPanel({ onClose }: { onClose: () => void }) { const [archiving, setArchiving] = useState(null); const [pendingArchive, setPendingArchive] = useState<{ path: string; name: string } | null>(null); + const handleIngestAll = async () => { + setIngesting(true); + setIngestSummary(null); + let totalImported = 0; + let totalSkipped = 0; + for (const { id, path } of projectsWithPath) { + try { + const r = await ingestFullGitHistory(id, path); + totalImported += r.imported; + totalSkipped += r.skipped; + } catch { + // 单个项目失败不中断其他项目 + } + } + await refetchHealth(); + setIngestSummary(`新导入 ${totalImported} 条,跳过 ${totalSkipped} 条`); + setIngesting(false); + }; + const handleGenerateMuhe = async () => { if (!stats) return; setGenerating(true); @@ -1894,6 +1927,64 @@ function FlywheelPanel({ onClose }: { onClose: () => void }) { )} + {/* Git 执行质量 */} +
+
+

Git 执行质量

+ +
+ {ingestSummary && ( +

{ingestSummary}

+ )} + {commitHealth && commitHealth.length > 0 ? ( +
+ + + + + + + + + + + + {commitHealth.map((h) => { + const proj = projectsWithPath.find((p) => p.id === h.project_id); + return ( + + + + + + + + ); + })} + +
项目commits规范化返工率TOP scope
+ {proj?.name ?? h.project_id} + {h.total_commits}= 0.8 ? "text-green-600" : h.conventional_rate >= 0.5 ? "text-amber-500" : "text-red-500"}`}> + {Math.round(h.conventional_rate * 100)}% + 0.2 ? "text-red-500" : h.rework_rate > 0.1 ? "text-amber-500" : "text-gray-500"}`}> + {Math.round(h.rework_rate * 100)}% + + {h.top_scopes.slice(0, 2).map((s) => `${s.scope}(${s.commit_count})`).join(" ")} +
+
+ ) : ( +

+ 暂无数据——点"全量导入"从各项目 git 历史初始化 +

+ )} +
+ {/* 梦核分析 */}

梦核分析