// 飞轮健康面板(跨项目):版本队列对比 / 阻塞原因 / 停转与休眠归档 / Git 执行质量 / 梦核分析 // 2026-07-02 ui-restructure N3:从 BlueprintModal 侧板迁出为洞察页一级内容(跨项目内容不属于单项目弹窗) import { useState, useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { getProjects, getFlywheelStats, generateMuhePrompt, archiveProjectDocs, ingestFullGitHistory, getAllCommitHealth, getHighRiskScopes, type Project, type FlywheelStats, type StalledProject, type CommitHealthWithName, type HighRiskScope, } from "../../lib/commands"; function StalledProjectRow({ p, projectsMap, onArchive }: { p: StalledProject; projectsMap: Record; // name → path onArchive?: (path: string, name: string) => void; }) { const path = projectsMap[p.name]; return (

· {p.name}

最后活跃: {p.last_active}

{onArchive && path && ( )}
); } function MetricRow({ label, value, good, invert = false }: { label: string; value: string; good: boolean; invert?: boolean; }) { const isGood = invert ? !good : good; return (
{label} {value}
); } export function FlywheelPanel() { const { data: projects } = useQuery({ queryKey: ["projects-for-flywheel"], queryFn: () => getProjects(), }); 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; withPath.push({ id: p.id, name: p.name, path }); map[p.name] = path; const dirName = path.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? ""; if (dirName && !map[dirName]) map[dirName] = path; } return { projectPaths: paths, projectsMap: map, projectsWithPath: withPath }; }, [projects]); const { data: commitHealth, refetch: refetchHealth } = useQuery({ queryKey: ["commit-health-all"], queryFn: () => getAllCommitHealth(), }); const { data: riskScopes } = useQuery({ queryKey: ["high-risk-scopes"], queryFn: () => getHighRiskScopes(), }); const [ingesting, setIngesting] = useState(false); const [ingestSummary, setIngestSummary] = useState(null); const { data: stats, isLoading } = useQuery({ queryKey: ["flywheel-stats", projectPaths], queryFn: () => getFlywheelStats(projectPaths), enabled: projectPaths.length > 0, }); const [muhePrompt, setMuhePrompt] = useState(""); const [generating, setGenerating] = useState(false); const [copied, setCopied] = useState(false); const [archiveMsg, setArchiveMsg] = useState(null); 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); try { const result = await generateMuhePrompt(stats); setMuhePrompt(result); } finally { setGenerating(false); } }; const handleCopy = () => { navigator.clipboard.writeText(muhePrompt).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000); }); }; const handleArchiveConfirm = async () => { if (!pendingArchive) return; const { path, name } = pendingArchive; setPendingArchive(null); setArchiving(name); setArchiveMsg(null); try { const result = await archiveProjectDocs(path); setArchiveMsg(`✓ 已归档 ${result.archived_files.length} 个文件 → ${result.archive_dir}`); } catch (e) { setArchiveMsg(`归档失败: ${String(e)}`); } finally { setArchiving(null); } }; const fmtPct = (v: number) => `${(v * 100).toFixed(1)}%`; // 去重——90天休眠的项目不在60天停转里重复展示 const stalledOnlyProjects = (stats?.stalled_projects ?? []).filter( (p) => !(stats?.ai_doc_stale_projects ?? []).some((q) => q.name === p.name) ); return (

飞轮健康

{stats && (

{stats.projects_with_data}/{stats.total_projects_scanned} 个项目有数据

)}
{isLoading ? (

聚合中…

) : !stats || stats.projects_with_data === 0 ? (

暂无飞轮数据

打开各项目蓝图后数据将自动积累

) : ( <> {/* CONVENTIONS 版本队列对比 */} {stats.cohorts.length > 0 && (

版本队列对比

{stats.cohorts.map((c) => (
v{c.conventions_version} {c.project_count} 个项目
0.3} /> 0.5} /> 0.6} />
))}
)} {/* TOP blocked reasons */} {stats.top_blocked_reasons.length > 0 && (

TOP 阻塞原因

    {stats.top_blocked_reasons.map((r, i) => (
  • #{i + 1} {r.reason} ×{r.count}
  • ))}
)} {/* 仅展示"停转但未到90天"的项目 */} {stalledOnlyProjects.length > 0 && (

飞轮停转(60–90天无快照)

{stalledOnlyProjects.map((p, i) => ( ))}
)} {/* AI文档休眠项目(≥90天) */} {stats.ai_doc_stale_projects.length > 0 && (

AI文档休眠(90天+无快照)

建议归档 .blueprint/modules/,清空 Claude 上下文负担。

{pendingArchive ? (

确认归档「{pendingArchive.name}」?

将删除 .blueprint/modules/*.md,文件会备份到 .blueprint/archive/,但操作本身不可一键撤销。

) : ( stats.ai_doc_stale_projects.map((p, i) => ( setPendingArchive({ path, name })} /> )) )} {archiving && (

归档 {archiving} 中…

)} {archiveMsg && (

{archiveMsg}

)}
)} {/* 迭代跳变项目 */} {(stats.iteration_jumps ?? []).length > 0 && (

迭代跳变(主动重构,完成率重置)

{(stats.iteration_jumps ?? []).map((j, i) => (
{j.project} 迭代 {j.from_iteration}→{j.to_iteration} {(j.rate_before * 100).toFixed(0)}%→{(j.rate_after * 100).toFixed(0)}% {j.rate_after < j.rate_before - 0.3 && " ⚠️"}
))}
)} {/* 停滞模块 */} {(stats.stalled_modules ?? []).length > 0 && (

停滞模块(in_progress 但无可推进任务)

    {(stats.stalled_modules ?? []).map((m, i) => (
  • {m}
  • ))}
)} {/* Git 执行质量 */}

Git 执行质量

{ingestSummary && (

{ingestSummary}

)} {commitHealth && commitHealth.length > 0 ? (
{commitHealth.map((h) => { return ( ); })}
项目 commits 规范化 返工率 TOP scope
{h.project_name} {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 历史初始化

)}
{/* 返工热区(飞轮阶段三预测层) */} {riskScopes && riskScopes.length > 0 && (

返工热区 历史返工率最高的模块,开工前提高警惕

{riskScopes.map((s) => ( ))}
项目 scope commits 返工率
{s.project_name} {s.scope} {s.commits} = 0.35 ? "text-red-500" : s.rework_rate >= 0.15 ? "text-amber-500" : "text-gray-500"}`}> {Math.round(s.rework_rate * 100)}%
)} {/* 梦核分析 */}

梦核分析

生成包含以上数据的提示词,交给 Claude Opus 做固化/遗忘判断。

{!muhePrompt ? ( ) : (