docs: 归档 2026-08-24 全面代码库健康审查报告

八个候选中 1-7 已在 3705945 落地(Campaigns.tsx 拆分、repository/db
按域拆包、lifecycle 拆分、编排下沉、工具函数收敛、测试补全);
候选 8(Engine/Storage 解耦)与两处 _translate 副本经核对语义刻意
不同,登记为刻意不做。
This commit is contained in:
sinohqb 2026-08-24 23:18:23 +08:00
parent bf1ec16ef6
commit 9f5da70c36
2 changed files with 657 additions and 0 deletions

View File

@ -7,3 +7,4 @@
| 2026-08-11 | Campaign 架构深化与正式线交付 | 已完成并部署 | [campaign-architecture-deepening-20260811-v1.0.md](campaign-architecture-deepening-20260811-v1.0.md) | | 2026-08-11 | Campaign 架构深化与正式线交付 | 已完成并部署 | [campaign-architecture-deepening-20260811-v1.0.md](campaign-architecture-deepening-20260811-v1.0.md) |
| 2026-08-21 | 智能评估架构深化scheduler 抽取 / 去重内化 / 状态机归一 / 残留清理 / 标签单一出口) | 已完成并推送 | [intelligent-eval-architecture-deepening-20260821-v1.2.html](intelligent-eval-architecture-deepening-20260821-v1.2.html) | | 2026-08-21 | 智能评估架构深化scheduler 抽取 / 去重内化 / 状态机归一 / 残留清理 / 标签单一出口) | 已完成并推送 | [intelligent-eval-architecture-deepening-20260821-v1.2.html](intelligent-eval-architecture-deepening-20260821-v1.2.html) |
| 2026-08-24 | 架构审查:五个深化候选(可见性接缝 / 智能作业归一 / repository 拆分 / 前端读取接缝 / api.ts 拆域) | 审查报告,候选已全部实施 | [codebase-architecture-review-20260824.html](codebase-architecture-review-20260824.html) | | 2026-08-24 | 架构审查:五个深化候选(可见性接缝 / 智能作业归一 / repository 拆分 / 前端读取接缝 / api.ts 拆域) | 审查报告,候选已全部实施 | [codebase-architecture-review-20260824.html](codebase-architecture-review-20260824.html) |
| 2026-08-24 | 全面代码库健康审查八个候选Campaigns.tsx 拆分 / repository 拆分 / 工具函数收敛 / 编排下沉 / lifecycle 拆分 / db 拆分 / 测试补全 / Engine 解耦) | 审查报告,候选 1-7 已全部实施,候选 8 刻意不做 | [codebase-architecture-review-health-20260824.html](codebase-architecture-review-health-20260824.html) |

View File

@ -0,0 +1,656 @@
<!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>