AgentEvalTool/docs/archive/codebase-architecture-review-health-20260824.html
sinohqb 9f5da70c36 docs: 归档 2026-08-24 全面代码库健康审查报告
八个候选中 1-7 已在 3705945 落地(Campaigns.tsx 拆分、repository/db
按域拆包、lifecycle 拆分、编排下沉、工具函数收敛、测试补全);
候选 8(Engine/Storage 解耦)与两处 _translate 副本经核对语义刻意
不同,登记为刻意不做。
2026-08-24 23:18:23 +08:00

657 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AgentEvalTool 架构审查报告 — 2026-08-24</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
.badge-strong { background: #dc2626; color: white; }
.badge-worth { background: #f59e0b; color: white; }
.badge-spec { background: #6b7280; color: white; }
.mermaid svg { max-width: 100%; }
details summary { cursor: pointer; }
details summary:hover { background: #f3f4f6; }
.card { border: 1px solid #e5e7eb; border-radius: 0.75rem; overflow: hidden; }
.card-header { padding: 1rem 1.5rem; border-bottom: 1px solid #e5e7eb; }
.card-body { padding: 1.5rem; }
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; }
.before-after { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
@media (max-width: 768px) { .before-after { grid-template-columns: 1fr; } }
.code-block { background: #1e293b; color: #e2e8f0; padding: 1rem; border-radius: 0.5rem; font-family: 'Fira Code', monospace; font-size: 0.8rem; overflow-x: auto; }
.highlight { background: #fef3c7; border-left: 3px solid #f59e0b; padding: 0.75rem 1rem; }
.green-highlight { background: #d1fae5; border-left: 3px solid #10b981; padding: 0.75rem 1rem; }
</style>
</head>
<body class="bg-gray-50 text-gray-900">
<div class="max-w-6xl mx-auto px-6 py-12">
<!-- Header -->
<div class="mb-12">
<h1 class="text-4xl font-bold text-gray-900 mb-2">AgentEvalTool 架构审查报告</h1>
<p class="text-lg text-gray-500">2026-08-24 · 全面代码库健康审查</p>
<div class="mt-6 stat-grid">
<div class="bg-white rounded-lg p-4 shadow-sm border">
<div class="text-3xl font-bold text-blue-600">15,609</div>
<div class="text-sm text-gray-500">后端代码行 (101 文件)</div>
</div>
<div class="bg-white rounded-lg p-4 shadow-sm border">
<div class="text-3xl font-bold text-purple-600">11,576</div>
<div class="text-sm text-gray-500">前端代码行 (75 文件)</div>
</div>
<div class="bg-white rounded-lg p-4 shadow-sm border">
<div class="text-3xl font-bold text-green-600">18,289</div>
<div class="text-sm text-gray-500">后端测试行 (101 文件)</div>
</div>
<div class="bg-white rounded-lg p-4 shadow-sm border">
<div class="text-3xl font-bold text-orange-600">1,347</div>
<div class="text-sm text-gray-500">前端测试行 (12 文件)</div>
</div>
</div>
</div>
<!-- Architecture Overview -->
<div class="card bg-white mb-8">
<div class="card-header bg-gray-50">
<h2 class="text-xl font-bold">架构全景</h2>
</div>
<div class="card-body">
<div class="mermaid">
graph TB
subgraph Frontend["前端 (React + TypeScript)"]
Pages["Pages<br/>11 页面组件<br/>~4,100 行"]
Components["Components<br/>~25 共享组件<br/>~3,600 行"]
API["API Layer<br/>10 域模块<br/>~720 行"]
Read["Read Module<br/>泛化资源接缝<br/>~430 行"]
Hooks["Hooks<br/>7 hooks<br/>~640 行"]
end
subgraph Backend["后端 (Python 3.11 + FastAPI)"]
Routers["Routers<br/>12 路由<br/>~2,100 行"]
Domain["Domain Layer<br/>intelligent_eval / evaluation / exploration"]
Storage["Storage Layer<br/>6 repositories<br/>~2,300 行"]
Channels["Channels<br/>3 adapters<br/>~700 行"]
end
subgraph Persistence["持久化"]
SQLite["SQLite<br/>15+ 表定义<br/>~700 行"]
end
Pages --> API
Pages --> Components
Pages --> Read
Read --> API
Hooks --> API
API --> Routers
Routers --> Domain
Domain --> Storage
Domain --> Channels
Storage --> SQLite
</div>
<div class="mt-6 highlight">
<strong>整体评价:</strong>分层清晰storage 不依赖 web/channelsdomain 层通过 repository 模式与存储交互。
后端测试覆盖率高(测试行 > 源码行)。主要问题集中在<strong>前端大组件</strong><strong>后端 god module</strong><strong>重复工具函数</strong>三个方向。
</div>
</div>
</div>
<!-- Candidates -->
<h2 class="text-2xl font-bold mb-6">改进候选点</h2>
<!-- Candidate 1: Campaigns.tsx -->
<div class="card bg-white mb-6" id="candidate-1">
<div class="card-header bg-white flex items-center justify-between">
<div>
<h3 class="text-lg font-bold">1. Campaigns.tsx — 前端 God Component</h3>
<p class="text-sm text-gray-500 mt-1">frontend/web/src/pages/Campaigns.tsx · 1,018 行</p>
</div>
<span class="badge-strong text-xs px-3 py-1 rounded-full font-medium">Strong</span>
</div>
<div class="card-body">
<div class="mb-4">
<h4 class="font-semibold text-red-700 mb-1">问题</h4>
<p>整个活动管理页面(列表、创建表单、详情视图、报告渲染、时间线、分析区、对比视图、探索式评测)全部塞在一个 1,018 行的组件里。包含 9 个 return 语句、286 个 JSX 标签。任何局部修改都需要在这个巨文件中导航AI 代理处理时容易丢失上下文。</p>
</div>
<div class="before-after mb-4">
<div>
<h5 class="text-sm font-semibold text-red-600 mb-2">Before — 单一巨组件</h5>
<div class="code-block text-xs">
Campaigns.tsx (1,018 lines)<br>
├── 列表视图 + 筛选<br>
├── 创建表单 (FormDrawer)<br>
├── 展开行 (expandedRowRender)<br>
│ ├── 时间线 (WindowTimeline)<br>
│ ├── Run 时间线 (CampaignRunTimeline)<br>
│ ├── 分析区 (AnalysisSection)<br>
│ ├── 对比区 (PeriodComparisonSection)<br>
│ └── 探索区 (ExplorationSection)<br>
├── 报告视图 (单 report 查看)<br>
├── 本地 StatusBadge, KpiItem, SectionTitle<br>
└── fmtPct(), targetName(), rateColorName()
</div>
</div>
<div>
<h5 class="text-sm font-semibold text-green-600 mb-2">After — 按视图职责拆分</h5>
<div class="code-block text-xs">
pages/Campaigns.tsx (~200 lines)<br>
├── 列表壳 (table + filter + drawer)<br>
└── 路由到子视图<br>
<br>
components/campaigns/<br>
├── CampaignForm.tsx (创建/编辑表单)<br>
├── CampaignDetail.tsx (展开行容器)<br>
├── CampaignReport.tsx (单报告视图)<br>
├── CampaignAnalysis.tsx (分析区)<br>
└── CampaignStatusBadge.tsx (状态标签)<br>
<br>
utils/campaignFormat.ts (fmtPct, targetName)
</div>
</div>
</div>
<div class="mb-4">
<h4 class="font-semibold mb-1">收益</h4>
<ul class="list-disc pl-5 space-y-1">
<li><strong>Locality</strong>:修改分析区只动 <code>CampaignAnalysis.tsx</code>,不会意外影响列表</li>
<li><strong>Leverage</strong><code>CampaignForm</code> 可被其他入口复用(如从报告页快速创建新活动)</li>
<li><strong>可测试性</strong>:当前零测试,拆分后每个子组件可独立测试</li>
</ul>
</div>
</div>
</div>
<!-- Candidate 2: storage/repository.py -->
<div class="card bg-white mb-6" id="candidate-2">
<div class="card-header bg-white flex items-center justify-between">
<div>
<h3 class="text-lg font-bold">2. storage/repository.py — 六合一 Repository 文件</h3>
<p class="text-sm text-gray-500 mt-1">backend/agenteval/storage/repository.py · 814 行</p>
</div>
<span class="badge-strong text-xs px-3 py-1 rounded-full font-medium">Strong</span>
</div>
<div class="card-body">
<div class="mb-4">
<h4 class="font-semibold text-red-700 mb-1">问题</h4>
<p>一个文件包含 <code>BaseRepository</code><code>TargetRepository</code><code>ScenarioRepository</code><code>RunRepository</code><code>CampaignRepository</code><code>ResultRepository</code> 六个类。Phase 2+3 拆分后仍残留 814 行。每次修改某个域的 repository 都要在这个大文件中定位,且所有域的 import 都指向同一个模块(<code>from agenteval.storage.repository import ...</code>),形成宽接缝。</p>
</div>
<div class="before-after mb-4">
<div>
<h5 class="text-sm font-semibold text-red-600 mb-2">Before — 六合一</h5>
<div class="mermaid">
graph LR
A[storage/repository.py<br/>814 lines] --> B[BaseRepository]
A --> C[TargetRepository]
A --> D[ScenarioRepository]
A --> E[RunRepository]
A --> F[CampaignRepository]
A --> G[ResultRepository]
</div>
</div>
<div>
<h5 class="text-sm font-semibold text-green-600 mb-2">After — 按域拆分</h5>
<div class="mermaid">
graph LR
A[storage/base_repository.py] --> B[BaseRepository]
C[storage/target_repository.py] --> D[TargetRepository]
E[storage/scenario_repository.py] --> F[ScenarioRepository]
G[storage/run_repository.py] --> H[RunRepository]
I[storage/campaign_repository.py] --> J[CampaignRepository]
K[storage/result_repository.py] --> L[ResultRepository]
</div>
</div>
</div>
<div class="highlight mb-4">
<strong>注意:</strong>已有先例——<code>async_job_repository.py</code>247 行)、<code>exploration_repository.py</code>135 行)、<code>model_config_repository.py</code>114 行)、<code>file_repository.py</code>156 行)都已独立拆分。剩余 6 个是 Phase 2+3 未触及的遗留。
</div>
<div>
<h4 class="font-semibold mb-1">收益</h4>
<ul class="list-disc pl-5 space-y-1">
<li><strong>Locality</strong>:修改 Run 查询逻辑只动 <code>run_repository.py</code></li>
<li><strong>一致性</strong>:与已有的 async_job/exploration/model_config/file repository 对齐</li>
<li><strong>减少 import 耦合</strong>:需要 RunRepository 的模块不再被迫 import 整个 814 行文件</li>
</ul>
</div>
</div>
</div>
<!-- Candidate 3: Frontend Duplicated Utilities -->
<div class="card bg-white mb-6" id="candidate-3">
<div class="card-header bg-white flex items-center justify-between">
<div>
<h3 class="text-lg font-bold">3. 前端重复工具函数 — personaLabel / fmtPct / targetName</h3>
<p class="text-sm text-gray-500 mt-1">跨 10+ 文件,至少 5 种重复模式</p>
</div>
<span class="badge-worth text-xs px-3 py-1 rounded-full font-medium">Worth exploring</span>
</div>
<div class="card-body">
<div class="mb-4">
<h4 class="font-semibold text-red-700 mb-1">问题</h4>
<p>三个工具函数在多个组件中各自实现,逻辑完全相同:</p>
</div>
<div class="overflow-x-auto mb-4">
<table class="w-full text-sm border-collapse">
<thead>
<tr class="bg-gray-50">
<th class="border p-2 text-left">函数</th>
<th class="border p-2 text-left">出现次数</th>
<th class="border p-2 text-left">位置</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border p-2"><code>personaLabel()</code></td>
<td class="border p-2">3</td>
<td class="border p-2 text-xs">ExecutionProcess.tsx:30, EvalOverview.tsx:9, EvalReport.tsx:24</td>
</tr>
<tr class="bg-gray-50">
<td class="border p-2"><code>fmtPct()</code></td>
<td class="border p-2">5</td>
<td class="border p-2 text-xs">Campaigns.tsx:118, PeriodComparisonSection.tsx:31, ExplorationSection.tsx:34, CampaignRunTimeline.tsx:96, Reports.tsx:499</td>
</tr>
<tr>
<td class="border p-2"><code>targetName()</code></td>
<td class="border p-2">2</td>
<td class="border p-2 text-xs">Campaigns.tsx:150, IntelligentEvals.tsx:105</td>
</tr>
<tr class="bg-gray-50">
<td class="border p-2"><code>_translate()</code> 错误翻译</td>
<td class="border p-2">3</td>
<td class="border p-2 text-xs">routers/intelligent_evals.py:64, routers/exploration.py:45, routers/model_configs.py:29</td>
</tr>
</tbody>
</table>
</div>
<div class="before-after">
<div>
<h5 class="text-sm font-semibold text-red-600 mb-2">Before — 各自实现</h5>
<div class="code-block text-xs">
// ExecutionProcess.tsx:30<br>
function personaLabel(persona: Record&lt;string, unknown&gt;): string {<br>
&nbsp;&nbsp;return (persona.background as string) || '未设置人设';<br>
}<br>
<br>
// EvalOverview.tsx:9<br>
function personaLabel(persona: Record&lt;string, unknown&gt;): string {<br>
&nbsp;&nbsp;return (persona.background as string) || '未设置人设';<br>
}<br>
<br>
// Campaigns.tsx:118<br>
function fmtPct(v: number | null) {<br>
&nbsp;&nbsp;return v == null ? '—' : `${(v * 100).toFixed(1)}%`;<br>
}
</div>
</div>
<div>
<h5 class="text-sm font-semibold text-green-600 mb-2">After — 收敛到 utils</h5>
<div class="code-block text-xs">
// utils/format.ts<br>
export function fmtPct(v: number | null): string {<br>
&nbsp;&nbsp;return v == null ? '—' : `${(v * 100).toFixed(1)}%`;<br>
}<br>
<br>
// utils/persona.ts<br>
export function personaLabel(<br>
&nbsp;&nbsp;persona: Record&lt;string, unknown&gt;<br>
): string {<br>
&nbsp;&nbsp;return (persona.background as string)<br>
&nbsp;&nbsp;&nbsp;&nbsp;|| '未设置人设';<br>
}<br>
<br>
// 所有消费者 import from utils
</div>
</div>
</div>
<div class="mt-4">
<h4 class="font-semibold mb-1">收益</h4>
<ul class="list-disc pl-5 space-y-1">
<li><strong>Locality</strong>:修改百分比格式(如加千位分隔符)只需改一处</li>
<li><strong>一致性</strong>:消除不同文件中格式微妙的差异</li>
<li><strong>深度</strong>:工具函数本身就是深模块的候选——小接口,行为确定</li>
</ul>
</div>
</div>
</div>
<!-- Candidate 4: runs.py router orchestration -->
<div class="card bg-white mb-6" id="candidate-4">
<div class="card-header bg-white flex items-center justify-between">
<div>
<h3 class="text-lg font-bold">4. runs.py Router — 编排逻辑泄漏到路由层</h3>
<p class="text-sm text-gray-500 mt-1">backend/agenteval/web/routers/runs.py · 211 行</p>
</div>
<span class="badge-worth text-xs px-3 py-1 rounded-full font-medium">Worth exploring</span>
</div>
<div class="card-body">
<div class="mb-4">
<h4 class="font-semibold text-red-700 mb-1">问题</h4>
<p><code>_run_evaluation()</code>35-66 行)是一个后台协程,在 router 文件内创建 <code>EvalEngine</code>、管理 session 生命周期、调用 webhook。这是 use-case 编排逻辑,应该住在 service/use-case 层。同样,<code>get_run_logs()</code>139-211 行)在 router 内做大量数据组装。</p>
</div>
<div class="before-after">
<div>
<h5 class="text-sm font-semibold text-red-600 mb-2">Before — 编排在 Router</h5>
<div class="mermaid">
graph TB
A[POST /api/runs] --> B[routers/runs.py]
B --> C[_run_evaluation()<br/>后台协程]
C --> D[EvalEngine]
C --> E[get_session]
C --> F[send_run_webhook]
B --> G[get_run_logs<br/>数据组装 70 行]
</div>
</div>
<div>
<h5 class="text-sm font-semibold text-green-600 mb-2">After — 编排下沉到 Service</h5>
<div class="mermaid">
graph TB
A[POST /api/runs] --> B[routers/runs.py<br/>~50 lines]
B --> C[services/run_execution.py<br/>RunExecutionService]
C --> D[EvalEngine]
C --> E[send_run_webhook]
B --> F[services/run_read.py<br/>RunReadService]
</div>
</div>
</div>
<div class="mt-4">
<h4 class="font-semibold mb-1">收益</h4>
<ul class="list-disc pl-5 space-y-1">
<li><strong>可测试性</strong>:编排逻辑可在无 FastAPI 上下文下单元测试</li>
<li><strong>复用</strong>CLI 入口也可调用同一 service 触发评测运行</li>
<li><strong>Seam 清晰</strong>router 只做 HTTP 翻译service 做编排</li>
</ul>
</div>
</div>
</div>
<!-- Candidate 5: intelligent_eval/lifecycle.py -->
<div class="card bg-white mb-6" id="candidate-5">
<div class="card-header bg-white flex items-center justify-between">
<div>
<h3 class="text-lg font-bold">5. intelligent_eval/lifecycle.py — 单体状态机</h3>
<p class="text-sm text-gray-500 mt-1">backend/agenteval/intelligent_eval/lifecycle.py · 866 行</p>
</div>
<span class="badge-worth text-xs px-3 py-1 rounded-full font-medium">Worth exploring</span>
</div>
<div class="card-body">
<div class="mb-4">
<h4 class="font-semibold text-red-700 mb-1">问题</h4>
<p>整个智能评估领域的状态机 + 所有领域操作集中在一个文件create、delete、list、submit_plan、approve、reject、cancel、open_session、conduct_turn、close_session、submit_report加上所有转换守卫。866 行import 了 5 个模块。这是项目中最深的领域模块,但深度以"大"而非"杠杆"衡量。</p>
</div>
<div class="highlight mb-4">
<strong>权衡:</strong>这个文件是智能评估的核心状态机。ADR-0008 明确要求加深智能评估接缝。近期已经历多轮重构watchdog 收敛、scheduler 拆分、decision-log 去重)。进一步拆分需要谨慎——<strong>状态转换的完整性</strong>是这个模块的核心价值,拆散可能反而增加跨文件追踪转换的成本。
</div>
<div class="before-after">
<div>
<h5 class="text-sm font-semibold text-red-600 mb-2">当前 — 单体 lifecycle</h5>
<div class="code-block text-xs">
lifecycle.py (866 lines)<br>
├── _TRANSITIONS 转换表<br>
├── create_eval()<br>
├── delete_eval()<br>
├── list_evals()<br>
├── submit_plan()<br>
├── approve() / reject()<br>
├── cancel()<br>
├── open_session()<br>
├── conduct_turn() ← 最复杂<br>
├── close_session()<br>
└── submit_report()
</div>
</div>
<div>
<h5 class="text-sm font-semibold text-green-600 mb-2">可选 — 按阶段职责拆分</h5>
<div class="code-block text-xs">
lifecycle.py (~200 lines)<br>
├── _TRANSITIONS 转换表<br>
├── create_eval() / delete_eval()<br>
├── list_evals()<br>
└── _transition() 守卫<br>
<br>
plan_management.py<br>
├── submit_plan() / approve() / reject()<br>
<br>
session_execution.py<br>
├── open_session() / conduct_turn()<br>
├── close_session()<br>
<br>
report_submission.py<br>
└── submit_report()
</div>
</div>
</div>
<div class="mt-4">
<h4 class="font-semibold mb-1">收益</h4>
<ul class="list-disc pl-5 space-y-1">
<li><strong>可测试性</strong><code>conduct_turn()</code> 的测试不需要 setup 整个状态机</li>
<li><strong>Locality</strong>:修改计划审批逻辑只动 <code>plan_management.py</code></li>
<li><strong>风险</strong>:状态转换完整性需要 characterization 测试锁定</li>
</ul>
</div>
</div>
</div>
<!-- Candidate 6: storage/db.py -->
<div class="card bg-white mb-6" id="candidate-6">
<div class="card-header bg-white flex items-center justify-between">
<div>
<h3 class="text-lg font-bold">6. storage/db.py — 15+ 表定义堆积</h3>
<p class="text-sm text-gray-500 mt-1">backend/agenteval/storage/db.py · 709 行</p>
</div>
<span class="badge-spec text-xs px-3 py-1 rounded-full font-medium">Speculative</span>
</div>
<div class="card-body">
<div class="mb-4">
<h4 class="font-semibold text-gray-600 mb-1">问题</h4>
<p>所有 SQLModel 表定义EvalTargetDB、ScenarioDB、EvalRunDB、TurnDB、EvalResultDB、CampaignDB、IntelligentEvalDB、IntelligentEvalSessionDB、IntelligentEvalMessageDB、IntelligentEvalDecisionLogDB、IntelligentEvalTaskQueueDB、IntelligentEvalConfigSnapshotDB、ExplorationSessionDB、ExplorationMessageDB、ModelConfigDB、FileCategoryDB、FileRecordDB 等 17+ 个表)+ engine 初始化 + session 工厂全在一个文件。随着新领域实体增加,这个文件会持续增长。</p>
</div>
<div class="highlight">
<strong>审慎建议:</strong>当前 709 行尚在可控范围。拆分的收益主要在"找表定义时不用翻太多行"。但如果未来再加 5+ 表(如 A/B 测试、回归基线),建议按领域拆分为 <code>db/targets.py</code><code>db/evaluations.py</code><code>db/intelligent_eval.py</code> 等。现阶段优先级低于候选 1-4。
</div>
</div>
</div>
<!-- Candidate 7: Frontend Test Gaps -->
<div class="card bg-white mb-6" id="candidate-7">
<div class="card-header bg-white flex items-center justify-between">
<div>
<h3 class="text-lg font-bold">7. 前端测试覆盖空白</h3>
<p class="text-sm text-gray-500 mt-1">12 测试文件 vs 75 源文件 · 页面组件零覆盖</p>
</div>
<span class="badge-spec text-xs px-3 py-1 rounded-full font-medium">Speculative</span>
</div>
<div class="card-body">
<div class="mb-4">
<h4 class="font-semibold text-gray-600 mb-1">问题</h4>
<p>前端测试集中在 <code>intelligent_eval/</code> 子组件和 <code>read/</code> 模块(最近重构新增),但以下区域完全无测试:</p>
</div>
<div class="overflow-x-auto mb-4">
<table class="w-full text-sm border-collapse">
<thead>
<tr class="bg-gray-50">
<th class="border p-2 text-left">未覆盖区域</th>
<th class="border p-2 text-left">代码行数</th>
<th class="border p-2 text-left">风险</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border p-2">所有 Page 组件 (11 个)</td>
<td class="border p-2">~4,100 行</td>
<td class="border p-2 text-red-600">高 — 用户直接交互界面</td>
</tr>
<tr class="bg-gray-50">
<td class="border p-2">RunList, CaseDetail, PeriodComparison 等共享组件</td>
<td class="border p-2">~1,200 行</td>
<td class="border p-2 text-orange-600">中 — 被多页面复用</td>
</tr>
<tr>
<td class="border p-2">useRunSession, useFiles, usePolling hooks</td>
<td class="border p-2">~480 行</td>
<td class="border p-2 text-orange-600">中 — 含 WS/REST 副作用</td>
</tr>
<tr class="bg-gray-50">
<td class="border p-2">所有 API 模块 (10 个)</td>
<td class="border p-2">~720 行</td>
<td class="border p-2 text-yellow-600">低 — 薄包装,但类型安全靠 tsc</td>
</tr>
</tbody>
</table>
</div>
<div class="highlight">
<strong>建议策略:</strong>不为测试而测试。优先在修改 Page 组件时(如候选 1 拆分 Campaigns.tsx同步补测试。hooks 的测试优先级高于 Page副作用逻辑更密集。API 层靠 TypeScript 类型检查即可。
</div>
</div>
</div>
<!-- Candidate 8: Engine/Storage Coupling -->
<div class="card bg-white mb-6" id="candidate-8">
<div class="card-header bg-white flex items-center justify-between">
<div>
<h3 class="text-lg font-bold">8. EvalEngine 与 Storage 耦合</h3>
<p class="text-sm text-gray-500 mt-1">backend/agenteval/evaluation/engine.py · 629 行</p>
</div>
<span class="badge-spec text-xs px-3 py-1 rounded-full font-medium">Speculative</span>
</div>
<div class="card-body">
<div class="mb-4">
<h4 class="font-semibold text-gray-600 mb-1">问题</h4>
<p><code>EvalEngine</code> 直接 import <code>get_session</code><code>RunRepository</code><code>ResultRepository</code>,在引擎内部管理数据库写入。核心评测执行引擎不是存储无关的——它既是领域逻辑(发消息、执行规则),又是持久化逻辑(写 run、写 result。同样模式出现在 <code>campaign_runner.py</code><code>analysis.py</code><code>exploration/judge.py</code></p>
</div>
<div class="mermaid mb-4">
graph LR
subgraph Engine["EvalEngine (629 lines)"]
A[send message] --> B[execute rules]
B --> C[write to DB]
end
subgraph Storage["直接依赖 Storage"]
D[get_session]
E[RunRepository]
F[ResultRepository]
end
Engine --> Storage
</div>
<div class="highlight">
<strong>审慎建议:</strong>解耦 Engine 需要引入 Unit-of-Work 或 Repository 注入模式,改造量大。当前通过 <code>MockChannel</code> + 内存 DB 测试已能覆盖。除非需要替换存储后端(如迁移到 Postgres否则改造 ROI 不高。标记为 speculative。
</div>
</div>
</div>
<!-- Summary & Top Recommendation -->
<div class="card bg-white mt-12" id="summary">
<div class="card-header bg-blue-50">
<h2 class="text-xl font-bold text-blue-900">总结与优先推荐</h2>
</div>
<div class="card-body">
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm border-collapse">
<thead>
<tr class="bg-gray-50">
<th class="border p-2 text-left">#</th>
<th class="border p-2 text-left">候选点</th>
<th class="border p-2 text-left">强度</th>
<th class="border p-2 text-left">改造量</th>
<th class="border p-2 text-left">核心收益</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border p-2">1</td>
<td class="border p-2">Campaigns.tsx 拆分</td>
<td class="border p-2"><span class="badge-strong text-xs px-2 py-0.5 rounded">Strong</span></td>
<td class="border p-2"></td>
<td class="border p-2">消除前端最大 god component</td>
</tr>
<tr class="bg-gray-50">
<td class="border p-2">2</td>
<td class="border p-2">storage/repository.py 拆分</td>
<td class="border p-2"><span class="badge-strong text-xs px-2 py-0.5 rounded">Strong</span></td>
<td class="border p-2"></td>
<td class="border p-2">与已有拆分模式对齐</td>
</tr>
<tr>
<td class="border p-2">3</td>
<td class="border p-2">前端重复工具函数收敛</td>
<td class="border p-2"><span class="badge-worth text-xs px-2 py-0.5 rounded">Worth</span></td>
<td class="border p-2"></td>
<td class="border p-2">消除 10+ 处重复</td>
</tr>
<tr class="bg-gray-50">
<td class="border p-2">4</td>
<td class="border p-2">runs.py 编排下沉</td>
<td class="border p-2"><span class="badge-worth text-xs px-2 py-0.5 rounded">Worth</span></td>
<td class="border p-2"></td>
<td class="border p-2">Router 回归纯翻译</td>
</tr>
<tr>
<td class="border p-2">5</td>
<td class="border p-2">lifecycle.py 状态机拆分</td>
<td class="border p-2"><span class="badge-worth text-xs px-2 py-0.5 rounded">Worth</span></td>
<td class="border p-2"></td>
<td class="border p-2">降低核心状态机认知负荷</td>
</tr>
<tr class="bg-gray-50">
<td class="border p-2">6</td>
<td class="border p-2">storage/db.py 表定义拆分</td>
<td class="border p-2"><span class="badge-spec text-xs px-2 py-0.5 rounded">Speculative</span></td>
<td class="border p-2"></td>
<td class="border p-2">找表定义更快</td>
</tr>
<tr>
<td class="border p-2">7</td>
<td class="border p-2">前端测试覆盖补全</td>
<td class="border p-2"><span class="badge-spec text-xs px-2 py-0.5 rounded">Speculative</span></td>
<td class="border p-2"></td>
<td class="border p-2">页面级回归保护</td>
</tr>
<tr class="bg-gray-50">
<td class="border p-2">8</td>
<td class="border p-2">Engine/Storage 解耦</td>
<td class="border p-2"><span class="badge-spec text-xs px-2 py-0.5 rounded">Speculative</span></td>
<td class="border p-2"></td>
<td class="border p-2">存储可替换性</td>
</tr>
</tbody>
</table>
</div>
<div class="green-highlight">
<h3 class="font-bold text-green-800 mb-2">Top Recommendation: 候选 2 — storage/repository.py 拆分</h3>
<p class="text-green-900">
<strong>理由:</strong>改造量最小(已有 4 个独立 repository 先例,模式成熟),收益确定性最高(与已有模式对齐,消除 814 行 god file风险最低纯机械拆分 + import 路径更新,不涉及逻辑变更)。
建议作为下一步立即执行的改进。
</p>
<p class="text-green-700 text-sm mt-2">
次选:候选 3前端重复工具函数收敛同样是小改造、高确定性收益。
</p>
</div>
</div>
</div>
</div>
<script>
mermaid.initialize({ startOnLoad: true, theme: 'default' });
</script>
</body>
</html>