docs: 归档 2026-08-24 架构审查报告(Phase 5)
All checks were successful
CI / test (push) Successful in 3m22s

将本轮架构审查 HTML 报告按先例归档至 docs/archive/,并同步
登记 docs/README.md 与 docs/archive/README.md 索引。

本轮五个深化候选已全部落地:
- 候选① 智能作业结算统一(ADR-0012)
- 候选② 可见性接缝(require_live_eval 单一接缝)
- 候选③ storage/repository.py 拆分
- 候选④ 前端 ReadSlot 资源接缝
- 候选⑤ api.ts 拆分为 10 个域模块

刻意不做项(CAS 共享原语、db.py 拆分、app.py 关停顺序归一、
会话/任务状态机转换表)已在 v3 计划与 ADR-0012 中记录。
This commit is contained in:
sinohqb 2026-08-24 05:54:54 +08:00
parent 4534df7e7a
commit 9588dcbfda
3 changed files with 399 additions and 0 deletions

View File

@ -71,6 +71,7 @@ AgentEvalTool 是一个智能体质量评估工具集平台,用于评估 AI
| **归档索引** | 已验收阶段的目标、决策、证据与后续约束 | [archive/README.md](archive/README.md) |
| **Campaign 架构深化** | 耐久作业、读模型、运行时和正式线交付总结 | [archive/campaign-architecture-deepening-20260811-v1.0.md](archive/campaign-architecture-deepening-20260811-v1.0.md) |
| **智能评估架构深化** | scheduler 抽取、去重内化、状态机归一、残留清理、标签单一出口 | [archive/intelligent-eval-architecture-deepening-20260821-v1.2.html](archive/intelligent-eval-architecture-deepening-20260821-v1.2.html) |
| **架构审查2026-08-24** | 五个深化候选可见性接缝、智能作业归一、repository 拆分、前端读取接缝、api.ts 拆域;候选已全部实施 | [archive/codebase-architecture-review-20260824.html](archive/codebase-architecture-review-20260824.html) |
### 2.7 外部参考

View File

@ -6,3 +6,4 @@
|---|---|---|---|
| 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-24 | 架构审查:五个深化候选(可见性接缝 / 智能作业归一 / repository 拆分 / 前端读取接缝 / api.ts 拆域) | 审查报告,候选已全部实施 | [codebase-architecture-review-20260824.html](codebase-architecture-review-20260824.html) |

View File

@ -0,0 +1,397 @@
<!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 type="module">
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
mermaid.initialize({ startOnLoad: true, theme: "neutral", securityLevel: "loose" });
</script>
<style>
body { font-family: -apple-system, "PingFang SC", "Helvetica Neue", sans-serif; }
.badge-strong { @apply bg-emerald-100 text-emerald-800 border-emerald-300; }
.iface { background:#fef3c7; border:2px solid #f59e0b; }
.impl { background:#0f172a; color:#e2e8f0; }
.impl-shallow { background:#e2e8f0; color:#334155; border:2px dashed #94a3b8; }
.seam { border-top:3px dashed #ef4444; }
.leak { box-shadow: 0 0 0 3px rgba(239,68,68,.35); }
pre.mermaid { background: transparent; }
</style>
</head>
<body class="bg-slate-100 text-slate-800">
<header class="bg-slate-900 text-slate-100 py-10 px-6">
<div class="max-w-6xl mx-auto">
<p class="text-emerald-400 font-mono text-sm mb-2">/improve-codebase-architecture · 第三轮</p>
<h1 class="text-3xl font-bold">架构审查总结 — AgentEvalTool</h1>
<p class="mt-3 text-slate-300 max-w-3xl">
2026-08-24 · 前两轮campaign v1.0、智能评估 v1.2)的候选已全部落地。本轮聚焦落地后新暴露与新残留的摩擦:
耐久作业结算序列、逻辑删除可见性、存储 monolith、前端读模型采纳面、api.ts 单文件。
</p>
<div class="mt-5 flex gap-6 text-sm flex-wrap">
<div><span class="text-2xl font-bold text-emerald-400">5</span> 深化候选</div>
<div><span class="text-2xl font-bold text-emerald-400">2</span> 已落地前轮v1.0 / v1.2</div>
<div><span class="text-2xl font-bold text-amber-400">3</span> 处"已删 eval 仍可见"泄漏</div>
<div><span class="text-2xl font-bold text-amber-400">1180</span> 行 repository.py · 12 个类</div>
</div>
</div>
</header>
<main class="max-w-6xl mx-auto px-6 py-10 space-y-12">
<!-- ============ 全景 ============ -->
<section>
<h2 class="text-xl font-bold mb-4">摩擦全景</h2>
<div class="bg-white rounded-xl shadow p-6">
<pre class="mermaid">
flowchart TB
subgraph BE["后端"]
IJ["intelligence_jobs.py<br/>两套手写结算序列"] --> AR["AsyncJobRepository<br/>claim/recovery 已深"]
R1["routers/intelligent_evals.py<br/>直 import ORM 表"] -.泄漏.-> VIS["逻辑删除可见性<br/>三处独立实现"]
DL["decision_logs._require_eval"] -.泄漏.-> VIS
REPO["storage/repository.py<br/>1180 行 · 12 类 · 5 域"]
APP["web/app.py<br/>手工编排 5 个 registry 关停顺序"]
end
subgraph FE["前端"]
READ["src/read 深模块<br/>仅 1 个消费者"] -.绕过.-> TABS["drawer 标签页群<br/>各自手写 fetch/polling"]
API["api.ts 909 行 · 12 域"]
end
classDef warn fill:#fef3c7,stroke:#f59e0b,stroke-width:2px;
class IJ,VIS,REPO,APP,TABS,API warn;
</pre>
</div>
</section>
<!-- ============ 候选 1 ============ -->
<section class="bg-white rounded-xl shadow overflow-hidden">
<div class="p-6 border-b border-slate-200 flex items-start justify-between gap-4">
<div>
<h2 class="text-2xl font-bold">① 耐久智能作业:结算序列收进单一执行器</h2>
<p class="text-slate-500 mt-1 font-mono text-sm">intelligence_jobs.py · AsyncJobRepository · campaigns 路由 · app.py</p>
</div>
<span class="shrink-0 px-3 py-1 rounded-full text-sm font-semibold border bg-emerald-100 text-emerald-800 border-emerald-300">Strong</span>
</div>
<div class="p-6 grid md:grid-cols-2 gap-8">
<div class="space-y-4 text-sm leading-relaxed">
<div>
<h3 class="font-semibold text-slate-900 mb-1">Files</h3>
<ul class="list-disc ml-5 space-y-0.5 text-slate-600">
<li><code>evaluation/intelligence_jobs.py</code> — 两个 executor53150、153263</li>
<li><code>storage/repository.py</code><code>AsyncJobRepository</code>782863</li>
<li><code>evaluation/campaign_runner.py:242-247</code><code>routers/campaigns.py:128-134</code></li>
<li><code>evaluation/comparison.py:245-280</code>(死代码 + 投影越界)</li>
</ul>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Problem</h3>
<p>v2 计划已建出 <code>AsyncJobRepository</code>claim / recovery / orphan 结算都深),但<strong>编排序列仍按 adapter 手抄</strong>:分析与周期对比两个 executor 各自复制「缺则入队 → claim_queued → upsert(generating) → try/except → failed/completed」。错误截断 <code>str(exc)[:500]</code> 出现两次123、250正式线跳过守卫两处campaign_runner.py:242 与 intelligence_jobs.py:136分析触发校验在路由与 executor 各一份(周期对比已有共享入口,分析没有);<code>build_comparison_payload</code> 是零调用死代码且与 <code>_build_auto_baseline_payload</code> 逐字节相同。</p>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Solution</h3>
<p>在执行器层提供单一深接缝 <code>execute(job_kind, work_fn)</code>入队幂等、认领、generating、失败归一截断、成功结算、重复触发幂等观察——全部收进 implementation。分析与周期对比退化为纯领域工作 adapter顺带删除死代码、把 <code>load_comparison_view</code> 投影移回读模型、分析校验对齐周期对比的共享入口。</p>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Benefits</h3>
<p><strong>Locality</strong>:结算顺序知识(先认领再 generating、失败先归一再落库只住一处。<strong>Leverage</strong>:第三个作业类型只需一个 work adapter。测试面从"两套序列各测一遍"收敛为执行器接口一组契约测试 + 每 adapter 的纯领域测试。两个 adapter 已存在——这是真接缝,不是假想接缝。</p>
</div>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-3 text-sm">Before / After</h3>
<div class="grid grid-cols-2 gap-3 text-xs mb-4">
<div>
<p class="text-center font-semibold text-red-600 mb-2">Before · 序列手抄 ×2</p>
<div class="iface rounded p-2 text-center text-slate-800">长接口:调用方须知认领顺序 / 截断 / 幂等</div>
<div class="impl-shallow rounded p-2 mt-1 text-center">分析 executor~100 行编排</div>
<div class="impl-shallow rounded p-2 mt-1 text-center">对比 executor~110 行同形编排</div>
</div>
<div>
<p class="text-center font-semibold text-emerald-600 mb-2">After · 单一执行器</p>
<div class="iface rounded p-2 text-center text-slate-800">小接口execute(kind, work_fn)</div>
<div class="impl rounded p-4 mt-1 text-center">深 implementation<br>认领·结算·归一·幂等</div>
<div class="flex gap-1 mt-1">
<div class="flex-1 bg-slate-200 rounded p-1 text-center">分析 adapter</div>
<div class="flex-1 bg-slate-200 rounded p-1 text-center">对比 adapter</div>
</div>
</div>
</div>
<pre class="mermaid">
sequenceDiagram
participant C as 触发方(路由/自动)
participant E as execute() 深模块
participant W as work adapter
C->>E: execute(kind, work_fn)
E->>E: 幂等入队 + claim_queued
E->>E: upsert(generating)
E->>W: 领域工作(无 Session 编排)
alt 成功
E->>E: upsert(completed + 结果)
else 异常
E->>E: 归一 + 截断 + upsert(failed)
end
</pre>
</div>
</div>
</section>
<!-- ============ 候选 2 ============ -->
<section class="bg-white rounded-xl shadow overflow-hidden leak">
<div class="p-6 border-b border-slate-200 flex items-start justify-between gap-4">
<div>
<h2 class="text-2xl font-bold">② 智能评估可见性接缝:逻辑删除语义三处收敛为一</h2>
<p class="text-slate-500 mt-1 font-mono text-sm">intelligent_eval/repository.py · decision_logs.py · routers/intelligent_evals.py · task_queue.py</p>
</div>
<span class="shrink-0 px-3 py-1 rounded-full text-sm font-semibold border bg-emerald-100 text-emerald-800 border-emerald-300">Strong</span>
</div>
<div class="p-6 grid md:grid-cols-2 gap-8">
<div class="space-y-4 text-sm leading-relaxed">
<div>
<h3 class="font-semibold text-slate-900 mb-1">Files</h3>
<ul class="list-disc ml-5 space-y-0.5 text-slate-600">
<li><code>intelligent_eval/repository.py</code> — 可见性谓词重复 6 处103、117、130、137、147、171</li>
<li><code>decision_logs.py:30-33</code><code>_require_eval</code> 绕过 repo 直查 ORM</li>
<li><code>routers/intelligent_evals.py:73-80</code> — 第三份实现,且路由直接 import <code>IntelligentEvalDB</code></li>
<li><code>task_queue.list_tasks:330</code> — 删除不感知,监控面板泄漏已删评估</li>
</ul>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Problem</h3>
<p>逻辑删除2d2c5a2一周前落地后「已删即 404」由<strong>三个独立实现</strong>分别裁决repo 的 <code>get()</code> 隐藏 + <code>get_including_deleted()</code><code>decision_logs._require_eval</code> 直查 ORM、路由 <code>_require_eval_exists</code> 再直查 ORM——路由跨过 repository 接缝直接握表,等于把删除语义焊死在 HTTP 层。附带浅点:配置快照 11 字段序列化 dict 在路由里逐字写了两遍399416、430442expired 会话的 Markdown 标注住在路由242252<code>count_decisions</code> 全量载入后 <code>len()</code>112121</p>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Solution</h3>
<p>可见性收进 repository 单一谓词(内部 <code>_visible()</code>),对外只留 <code>require_live_eval(session, id)</code> 服务接缝——三处调用方改走它,路由删掉 ORM import。快照序列化与 expired 标注下沉到读模型投影;<code>count_decisions</code><code>func.count</code></p>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Benefits</h3>
<p><strong>Locality</strong>:删除语义改动(如未来回收站、级联策略)只改一处。<strong>正确性</strong>:消除三处分歧风险——当前任务监控已实际泄漏已删评估。测试面:逻辑删除契约从分散的集成断言收敛为 repo 接口一组测试。这是新功能知识<strong>正在扩散</strong>的窗口期,越早收敛成本越低。</p>
</div>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-3 text-sm">Before / After</h3>
<div class="grid grid-cols-2 gap-3 text-xs mb-4">
<div>
<p class="text-center font-semibold text-red-600 mb-2">Before · 三处裁决"已删即 404"</p>
<div class="space-y-1">
<div class="bg-slate-200 rounded p-1.5 text-center">repo.get() 隐藏删除</div>
<div class="bg-red-50 border border-red-300 rounded p-1.5 text-center">decision_logs 直查 ORM</div>
<div class="bg-red-50 border border-red-300 rounded p-1.5 text-center">路由直查 ORMimport 表)</div>
<div class="bg-amber-50 border border-amber-300 rounded p-1.5 text-center">task_queue 完全不知情</div>
</div>
</div>
<div>
<p class="text-center font-semibold text-emerald-600 mb-2">After · 单一可见性接缝</p>
<div class="iface rounded p-2 text-center text-slate-800">require_live_eval(id)</div>
<div class="impl rounded p-3 mt-1 text-center">repository_visible 谓词 + get/get_including_deleted</div>
<div class="flex gap-1 mt-1">
<div class="flex-1 bg-slate-200 rounded p-1 text-center">decision_logs</div>
<div class="flex-1 bg-slate-200 rounded p-1 text-center">路由</div>
<div class="flex-1 bg-slate-200 rounded p-1 text-center">task_queue</div>
</div>
</div>
</div>
<pre class="mermaid">
flowchart LR
subgraph NOW["现状:三份语义"]
A["router 直查<br>IntelligentEvalDB"] --> D["DB"]
B["decision_logs<br>session.get()"] --> D
C["repo.get()<br>隐藏 deleted"] --> D
end
classDef bad fill:#fee2e2,stroke:#ef4444;
class A,B bad;
</pre>
</div>
</div>
</section>
<!-- ============ 候选 3 ============ -->
<section class="bg-white rounded-xl shadow overflow-hidden">
<div class="p-6 border-b border-slate-200 flex items-start justify-between gap-4">
<div>
<h2 class="text-2xl font-bold">③ 存储层 monolithrepository.py / db.py 按域拆分</h2>
<p class="text-slate-500 mt-1 font-mono text-sm">storage/repository.py1180 行)· storage/db.py709 行)</p>
</div>
<span class="shrink-0 px-3 py-1 rounded-full text-sm font-semibold border bg-amber-100 text-amber-800 border-amber-300">Worth exploring</span>
</div>
<div class="p-6 grid md:grid-cols-2 gap-8">
<div class="space-y-4 text-sm leading-relaxed">
<div>
<h3 class="font-semibold text-slate-900 mb-1">Files</h3>
<ul class="list-disc ml-5 space-y-0.5 text-slate-600">
<li><code>storage/repository.py</code> — 1180 行、12 个 repository 类、跨约 5 个域</li>
<li><code>storage/db.py</code> — 709 行、20 张表一个模块</li>
<li>对比项:<code>intelligent_eval/repository.py</code>510 行、单域)是良好范本</li>
</ul>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Problem</h3>
<p>文件级 <strong>locality</strong> 欠佳目录Target/Scenario、执行Run/Result、评估活动Campaign + 两个智能作业、探索式评测ExplorationSession/Message同居一文件探索对10571163与异步作业仓库782996完全自包含却被迫与无关域共享导航成本。AI 与新成员定位"活动的 CAS 写入"必须扫过全部 12 个类。CAS 条件写模式也在此三度独立实现680739、802828 与 intelligent_eval 的 190248同文件却不共享原语。</p>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Solution</h3>
<p>纯移动不改行为:拆出 <code>storage/exploration_repository.py</code><code>storage/async_job_repository.py</code>或随候选①并入执行器模块db.py 可暂不动(表定义集中有 create_all 的便利)。可选第二步:把三份 CAS 收敛为一个共享条件写原语——但需先过<strong>删除测试</strong>:三处的条件字段与冲突语义确有差异,只有当共享原语接口小到不泄漏这些差异时才值得。</p>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Benefits</h3>
<p>每域一个文件 = 改动、bug、知识同址导航成本从"扫 1180 行"降到"开对应文件"。本身不产生 leverage故标记为 Worth exploring——它是其他候选①②的天然搭车项单独做价值有限。</p>
</div>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-3 text-sm">Before / After</h3>
<div class="text-xs space-y-3">
<div>
<p class="text-center font-semibold text-red-600 mb-2">Before · 一文件 12 类 5 域</p>
<div class="border-2 border-slate-400 rounded p-2">
<p class="text-center font-mono mb-1.5">storage/repository.py — 1180 行</p>
<div class="grid grid-cols-3 gap-1">
<div class="bg-sky-100 rounded p-1 text-center">Target</div>
<div class="bg-sky-100 rounded p-1 text-center">Scenario</div>
<div class="bg-violet-100 rounded p-1 text-center">Run</div>
<div class="bg-violet-100 rounded p-1 text-center">Result</div>
<div class="bg-emerald-100 rounded p-1 text-center">Campaign</div>
<div class="bg-emerald-100 rounded p-1 text-center">AnalysisJob</div>
<div class="bg-emerald-100 rounded p-1 text-center">ComparisonJob</div>
<div class="bg-rose-100 rounded p-1 text-center">ExplSession</div>
<div class="bg-rose-100 rounded p-1 text-center">ExplMessage</div>
</div>
</div>
</div>
<div>
<p class="text-center font-semibold text-emerald-600 mb-2">After · 每域一文件</p>
<div class="grid grid-cols-2 gap-1.5">
<div class="bg-sky-50 border border-sky-300 rounded p-1.5 text-center">catalog_repository</div>
<div class="bg-violet-50 border border-violet-300 rounded p-1.5 text-center">run_repository</div>
<div class="bg-emerald-50 border border-emerald-300 rounded p-1.5 text-center">campaign_repository</div>
<div class="bg-rose-50 border border-rose-300 rounded p-1.5 text-center">exploration_repository</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============ 候选 4 ============ -->
<section class="bg-white rounded-xl shadow overflow-hidden">
<div class="p-6 border-b border-slate-200 flex items-start justify-between gap-4">
<div>
<h2 class="text-2xl font-bold">④ 前端读取模块src/read 从单消费者扩展为统一资源接缝</h2>
<p class="text-slate-500 mt-1 font-mono text-sm">src/read/* · components/intelligent_eval/* · hooks/useCampaignReport.ts · hooks/useResource.ts</p>
</div>
<span class="shrink-0 px-3 py-1 rounded-full text-sm font-semibold border bg-emerald-100 text-emerald-800 border-emerald-300">Strong</span>
</div>
<div class="p-6 grid md:grid-cols-2 gap-8">
<div class="space-y-4 text-sm leading-relaxed">
<div>
<h3 class="font-semibold text-slate-900 mb-1">Files</h3>
<ul class="list-disc ml-5 space-y-0.5 text-slate-600">
<li><code>read/intelligentEval.ts</code>118 行,有测试)· <code>read/useIntelligentEvalRead.ts</code>83 行,有测试)</li>
<li><code>components/intelligent_eval/</code> — ExecutionProcess480 行、DecisionProcess、ConfigSnapshots、TaskQueueMonitor</li>
<li><code>hooks/useCampaignReport.ts:16-21</code><code>hooks/useResource.ts</code></li>
</ul>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Problem</h3>
<p><code>src/read</code> 是一个<strong>真正的深模块</strong>ReadSlot 相位机、竞态守卫、静默刷新、可注入 adapter且有测试——但只有 IntelligentEvals.tsx 一个消费者,连它自己都绕过读模块做 mutation。五个 drawer 标签页各自手写同形 fetch 样板DecisionProcess:25-35、ConfigSnapshots:32-42、TaskQueueMonitor:39-50<code>ACTIVE_STATUSES</code> 定义两份;<code>ReadPhase/ReadSlot</code> 在 useCampaignReport.ts 里平行重造Blob 导出仪式重复三遍;同一份决策日志有三种新鲜度策略(父级 5s 轮询详情 + ExecutionProcess 独立 5s 轮询 + DecisionProcess 只挂载取一次。组件群零测试EvalReport / EvalOverview / DecisionProcess / ConfigSnapshots / TaskQueueMonitor</p>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Solution</h3>
<p>把 ReadSlot 泛化为通用资源接缝(或强制 drawer 标签页走既有 <code>useResource</code> + 轮询门控),决策日志的新鲜度策略由父级读模块统一供给一次;常量与导出仪式各自归位单一出口。组件退化为纯渲染,状态经 props/reducer 注入。</p>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Benefits</h3>
<p><strong>Leverage</strong>:竞态守卫、静默刷新、轮询暂停这些已写好且已测的行为,一次学习全组件受益。<strong>可测性</strong>:接口即测试面——五个零覆盖组件的测试从"mock 整个 api + 时序"变成对纯 reducer/props 的断言,这正是 sessionReducer 已验证的形状。深模块已存在、adapter 需求已出现campaign 报告平行重造)——符合"两个 adapter 才是真接缝"。</p>
</div>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-3 text-sm">Before / After</h3>
<div class="text-xs space-y-3">
<div>
<p class="text-center font-semibold text-red-600 mb-2">Before · 深模块闲置,样板四散</p>
<div class="flex gap-1.5">
<div class="w-1/3 impl rounded p-2 text-center self-stretch">src/read<br><span class="text-amber-300">深·有测试</span><br><span class="text-red-300">1 个消费者</span></div>
<div class="w-2/3 grid grid-cols-2 gap-1">
<div class="bg-red-50 border border-red-300 rounded p-1.5 text-center">ExecutionProcess<br>手写 5 个 fetch 槽</div>
<div class="bg-red-50 border border-red-300 rounded p-1.5 text-center">DecisionProcess<br>手写样板</div>
<div class="bg-red-50 border border-red-300 rounded p-1.5 text-center">ConfigSnapshots<br>手写样板</div>
<div class="bg-red-50 border border-red-300 rounded p-1.5 text-center">TaskQueueMonitor<br>手写样板</div>
</div>
</div>
</div>
<div>
<p class="text-center font-semibold text-emerald-600 mb-2">After · 资源接缝统一供给</p>
<div class="iface rounded p-2 text-center text-slate-800">useReadResource(slot, adapter) — 竞态/轮询/静默刷新内置</div>
<div class="grid grid-cols-3 gap-1 mt-1.5">
<div class="bg-slate-200 rounded p-1 text-center">列表页</div>
<div class="bg-slate-200 rounded p-1 text-center">5 个 drawer 标签</div>
<div class="bg-slate-200 rounded p-1 text-center">活动报告</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============ 候选 5 ============ -->
<section class="bg-white rounded-xl shadow overflow-hidden">
<div class="p-6 border-b border-slate-200 flex items-start justify-between gap-4">
<div>
<h2 class="text-2xl font-bold">⑤ api.ts 单文件按域拆分</h2>
<p class="text-slate-500 mt-1 font-mono text-sm">frontend/web/src/api.ts909 行 · 12 域)</p>
</div>
<span class="shrink-0 px-3 py-1 rounded-full text-sm font-semibold border bg-slate-200 text-slate-600 border-slate-300">Speculative</span>
</div>
<div class="p-6 grid md:grid-cols-2 gap-8">
<div class="space-y-4 text-sm leading-relaxed">
<div>
<h3 class="font-semibold text-slate-900 mb-1">Files</h3>
<p class="text-slate-600"><code>src/api.ts</code> — auth、targets、scenarios、modelConfigs、runs、reports、stats、exploration、campaigns、intelligentEvals16 个端点、files全部类型内联。</p>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Problem</h3>
<p>v1.2 已点名的残留浅点。单文件本身不深不浅——axios 拦截器统一错误是真实深度——但 12 域同居迫使每次改动载入全部类型;报告 Markdown 下载的 DOM 副作用835843住在"接口定义单一出口"里属越界。注意:它是<strong>locality 问题而非 depth 问题</strong>,单独拆不产生 leverage。</p>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Solution</h3>
<p>保留 <code>api/index.ts</code> 作为拦截器与 axios 实例的唯一住处(现有唯一出口语义不丢),各域拆为 <code>api/intelligentEvals.ts</code>Blob 导出仪式与候选④合并到同一个下载工具。</p>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-1">Benefits</h3>
<p>导航与 diff 噪声下降;域类型就近。无行为变化、无测试变化——因此只做搭车项,不单独立项。</p>
</div>
</div>
<div>
<h3 class="font-semibold text-slate-900 mb-3 text-sm">Before / After</h3>
<div class="text-xs">
<div class="border-2 border-slate-400 rounded p-3 mb-3">
<p class="text-center font-mono mb-2">api.ts — 909 行</p>
<div class="flex flex-wrap gap-1 justify-center">
<span class="bg-slate-200 rounded px-1.5 py-0.5">auth</span><span class="bg-slate-200 rounded px-1.5 py-0.5">targets</span><span class="bg-slate-200 rounded px-1.5 py-0.5">scenarios</span><span class="bg-slate-200 rounded px-1.5 py-0.5">modelConfigs</span><span class="bg-slate-200 rounded px-1.5 py-0.5">runs</span><span class="bg-slate-200 rounded px-1.5 py-0.5">reports</span><span class="bg-slate-200 rounded px-1.5 py-0.5">stats</span><span class="bg-slate-200 rounded px-1.5 py-0.5">exploration</span><span class="bg-slate-200 rounded px-1.5 py-0.5">campaigns</span><span class="bg-amber-100 rounded px-1.5 py-0.5">intelligentEvals +DOM 副作用</span><span class="bg-slate-200 rounded px-1.5 py-0.5">files</span>
</div>
</div>
<div class="text-center text-2xl text-slate-400 my-1"></div>
<div class="grid grid-cols-2 gap-1.5">
<div class="iface rounded p-1.5 text-center text-slate-800">api/index.ts<br>实例 + 拦截器</div>
<div class="bg-slate-200 rounded p-1.5 text-center">api/&lt;domain&gt;.ts ×11</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============ Top ============ -->
<section class="bg-slate-900 text-slate-100 rounded-xl shadow p-8">
<h2 class="text-2xl font-bold mb-3">Top recommendation — 先做 ②</h2>
<p class="text-slate-300 leading-relaxed max-w-4xl">
<strong class="text-emerald-400">候选②(智能评估可见性接缝)应当第一个做。</strong>
理由有三:其一,逻辑删除是一周前刚落地的新功能,其领域知识<strong>正在扩散</strong>——三处独立实现 + 路由直 import ORM 表 + 任务监控已实际泄漏已删评估,每晚一天收敛成本更高;其二,它是五个候选里<strong>爆炸半径最小</strong>的——纯收敛、零行为变化、已有 +100 行集成测试做行为锁;其三,它顺带把快照序列化与 expired 标注移出路由,巩固 v1.2 已确立的"路由只做 HTTP 翻译"纪律。
</p>
<p class="text-slate-300 leading-relaxed max-w-4xl mt-3">
紧随其后做 <strong class="text-emerald-400">候选①</strong>(结算序列——真有两个 adapter接缝是真的③⑤ 作为搭车项,④ 作为前端独立战役(涉及组件改造与补测试,体量最大)。
</p>
<p class="text-slate-500 text-sm mt-5">词汇module / interface / implementation / depth / seam / adapter / leverage / localitycodebase-design领域名词取自 CONTEXT.md。</p>
</section>
</main>
</body>
</html>