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 历史初始化 +

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

梦核分析