跳转至

EvoCode 架构审查报告

审查对象:docs/04-架构设计.md(v1.0)· 对照基线:01 需求分析 / 02 开发指导 / 03 开发规范 审查方式:文档交叉一致性 + 需求追溯 + 可实现性推演 · 2026-08-10 修订状态:2026-08-10 第一轮修订已全部执行(04/02/03/01);同日新增《06-API 契约》并完成第二轮审查(§9),第二轮回合修订已执行。


1. 审查结论总览

等级 数量 含义
P0 阻断 3 文档自相矛盾或与需求脱节,不修会让实现踩坑
P1 重要 8 契约/API/机制缺失,功能做不出来或联调必卡
P2 建议 6 优化项,不影响主线,按时间取舍

整体评价:架构骨架(三进程边界、SPI 插件、降级链、单向依赖、A0-A3 演进)方向正确、可答辩性强。但存在 3 处结构性矛盾(LLM 出口双头、无状态与管线编排冲突、文件预览 API 缺失),必须在动手写代码前统一。


2. P0 阻断问题(必须修正)

P0-1:LLM 调用出口"双头"——铁律与设计自相矛盾

位置:04 §3 铁律表 / §3.2 / §3.5 / §7.3;对照 02 §6.2、02 附录 D

矛盾: - §3 铁律表明写 backend "不拥有……LLM 直接调用" - §3.2 却在 backend 列出 service/ai/LLMClient + OpenAiCompatibleLLMClient + MockLLMClient + ChatService + RagService - §3.5 标题"AI 能力出口(backend:LLMClient SPI)" - §7.3 时序图中 backend ChatService → LLMClient.stream 直连 LLM - 同时 analyzer 又有 ai/report_generator.py + prompts.py(02 附录 D 的 7 个 prompt 都在 analyzer)

后果:报告生成到底走 analyzer 还是 backend?两套 LLM 配置、两套 prompt、两处重试降级逻辑,联调时必然左右互搏。

修复方案(二选一,推荐 A)

方案 A(推荐):LLM 出口统一在 analyzer
  backend 删除 service/ai 包;ChatService 改为调 analyzer 新端点
  POST /analyze/v1/chat(SSE 由 analyzer 直接产生,backend 仅做鉴权/会话持久化/反向代理)
  → 铁律成立:backend 永不直连 LLM;prompt/config/重试/降级单点管理
  → 代价:SSE 链路变为 frontend → backend → analyzer 两层代理(见 P1-6 缓冲处理)

方案 B:LLM 出口统一在 backend(analyzer 只做检索)
  分析类生成(报告/解释/文档)也迁回 backend —— 但报告生成依赖分析摘要与解析结果,
  会迫使 backend 持有分析上下文,破坏"backend 不做解析"边界。不推荐。

连带修改:04 §3.2 删除 ai 包行、§3.5 改写为"LLM 出口唯一化"、§7.3 时序改图;02 §6.2 增加 /analyze/v1/chat;02 附录 F 的成本/选型文字同步。

P0-2:analyzer"无状态"与 PipelineRegistry 跨阶段编排冲突

位置:04 §3.3 PipelineRegistry / §4.3"所有分析端点无状态";对照 02 §7(后端逐阶段编排)

矛盾: - 04 §3.3 管线设计为"分析类型 → 阶段列表"(如 FULL = SCAN+REPORT),暗示 analyzer 内跨阶段编排 - 04 §4.3 又声明"所有分析端点无状态:请求内完成,不保存服务端会话" - 02 §7.2 是后端逐阶段调 /analyze/scan/analyze/report,进度由后端映射

后果:若 FULL 在 analyzer 内一次跑完(10 分钟级请求),HTTP 长连接 + 进度无法上报,多 worker 下任务漂移,"无状态"承诺失效。

修复方案(推荐)

编排归属:跨阶段编排留在 backend(维持 02 §7 设计)
PipelineRegistry 降级为"端点内部子步骤组合器":
  /analyze/v1/scan    = filescanner → langdetect → stackdetect → loc(内部管线)
  /analyze/v1/quality = sonar → 归一化
  /analyze/v1/report  = 摘要组装 → LLM → 规则版降级
  → 每个端点同步完成、无状态、可多 worker;进度 = 后端按阶段权重线性映射
新增分析器 = analyzer 新端点 + 该端点内注册子步骤 + 后端 AnalysisAsyncRunner 加一个阶段
  (后端改动仅 AnalysisAsyncRunner 与权重表,其余不变)

连带修改:04 §3.3 管线章节改写(明确"端点内组合、端点间编排在后端");§8 SPI-2 的"接入成本 ≤2 天"描述同步。

P0-3:缺"文件内容读取"API——FR-6.3(引用可点击预览)无法实现

位置:02 §6.1 API 表只有 GET /projects/{id}/files(元数据列表);04 §7.3 时序假设存在内容读取

遗漏:AI 医生引用卡片、报告风险引用、质量 issue 原文片段,都要"点击 → Monaco 预览该文件该行",但全文档无内容端点。

修复方案:新增契约

GET /api/v1/projects/{id}/files/content?path=<相对路径>
  - 安全:路径白名单(FileNode 表内路径 + 在 storage_path 内)+ 大小上限 2MB + 二进制拒绝
  - 响应:{ path, language, content, loc }
  - 分页/行号跳转由前端 Monaco 处理(提供 line 参数)

连带修改:02 §6.1 API 表、04 §7.3 时序图、03 规范 §7 安全清单加"任意文件读取防护"用例。


3. P1 重要问题(建议在对应阶段实现前修复)

P1-1 分析结果幂等语义未定义

后端调用 analyzer 超时重试时,scan 可能重复执行;file_node 若重复插入会堆积。 修复:约定"分析结果按 analysis_id 重建(先删后插)";AnalysisAsyncRunner 每阶段落库前先清理该 analysis_id 旧数据。02 §7.3 补一条。

P1-2 报告"重新生成(AI)"无 API

01 §7.4 报告页有"重新生成(AI)"按钮,02 API 表没有对应端点。 修复POST /api/v1/analyses/{id}/report/regenerate(不重扫,只重跑 LLM 步骤,覆盖 report_json 并记录 regenerated_at)。

P1-3 analyzer 错误契约未定义,3xxx 映射是空头支票

02 有 3xxx 错误码段,但 analyzer 返回什么错误体、backend 如何映射未定义。 修复:analyzer 统一错误体 {"error": {"code": "SCAN_TIMEOUT|LLM_FAILED|SONAR_UNAVAILABLE|…", "message": "…"}};AnalyzerClient 按 code 映射到 ErrorCode 3xxx 并携带原因。

P1-4 会话历史无截断策略

04 §7.3 / 02 附录 D.6 的 {history} 未定义长度管理,长会话会爆 token。 修复:保留最近 N=6 轮 + 更早内容滚动摘要(analyzer 侧 chat_service 实现);写入 chat_message 时记录 tokens 字段(DDL 已预留思路)。

P1-5 知识块重建策略未定

knowledge_chunk 只挂 project_id,每次分析是否全量重建? 修复:每次 FULL 分析成功后按 analysis_id 全量重建(简单、一致);后续可优化为按文件 hash 增量(SPI 预留,不承诺)。

P1-6 SSE 经两层代理的缓冲问题

采用 P0-1 方案 A 后,SSE 链路 frontend→backend→analyzer。nginx/后端反向代理必须关缓冲,否则流式失效。 修复:nginx proxy_buffering off + X-Accel-Buffering: no 头;backend 侧用 SseEmitter/响应流直接转发 analyzer 流,不缓存。

P1-7 分析型 CPU 任务与 LLM IO 混用同一线程池

04 §4.3 分析端点走 Starlette 线程池,LLM 等待也占线程,解析任务可能被 IO 等待饿死。 修复:analyzer 拆两个池——parser_pool(CPU×2,跑解析)+ llm_pool(IO 型,异步 httpx 或独立池);两端点按类型选择执行池。

P1-8 大报告与列表页性能无缓存

Redis 已预装但用途未定义。 修复(P2 阶段):报告页与项目列表缓存于 Redis(key: report:{analysisId}projects:list:{page},TTL 5min);任务状态不缓存(实时性要求高)。在 02 附录 C.3 补说明。


4. P2 建议问题(按时间取舍)

# 问题 建议
P2-1 前端 types/api.ts 单文件将膨胀到上千行 按模块拆分 types/project.ts / analysis.ts / …
P2-2 无任何运行指标(分析耗时/成功率/LLM 成本) 简单埋点表 analysis_metric(或日志统计):每次任务记录 stage 耗时,答辩可展示"分析性能数据"
P2-3 ArchUnit 只覆盖 backend,analyzer 无对应检查 analyzer 加 import-linter(python 包,依赖方向检查)或人工评审清单
P2-4 文件内容接口的读取权限与"全文发送开关"未挂钩 全文发送开关(Q-4)实现时,控制 analyzer 是否携带文件片段,与内容接口无关但需联动说明
P2-5 删除项目与进行中分析的竞态只在需求层描述 时序上明确:删除 → 标记 CANCELLED → 等执行线程退出 → 再清库(事务顺序图补一节)
P2-6 上传大 zip 时"快扫"与"全量扫描"重复全量遍历 快扫结果可缓存标记(scan_fast_done),全量扫描复用文件清单,只补 LOC 精度

5. 修订后的一致架构(关键决策一览)

LLM 出口      analyzer 唯一(backend 永不直连 LLM)          [P0-1A]
编排归属      backend 跨阶段编排;analyzer 端点内组合         [P0-2]
analyzer    无状态、端点同步完成、可多 worker、错误契约统一   [P0-2/P1-3]
结果写入     按 analysis_id 先删后插(幂等重建)              [P1-1]
SSE          frontend→backend(转发,不缓存)→analyzer(生成)    [P0-1A/P1-6]
文件读取     GET /files/content(白名单+上限+只读)          [P0-3]

6. 修订清单(哪些文档改哪里)

文档/章节 修订内容 对应问题
04 §3 铁律表 "LLM 直接调用"条目改为"LLM 出口在 analyzer(chat 经 /analyze/v1/chat)" P0-1
04 §3.2 删除 service/ai/ 包;ChatService 改为编排 + 会话持久化 P0-1
04 §3.3 PipelineRegistry 定位改为"端点内子步骤组合" P0-2
04 §3.5 改写为"LLM 出口唯一化(analyzer)" P0-1
04 §4.3 增加 parser_pool / llm_pool 拆分 P1-7
04 §7.3 时序改为经 analyzer /chat 流式 P0-1
04 §7.5(增) 删除中分析的时序 P2-5
02 §6.1 新增 content 与 regenerate 端点 P0-3/P1-2
02 §6.2 新增 /analyze/v1/chat;analyzer 错误体契约 P0-1/P1-3
02 §7.3 幂等重建语义 P1-1
02 附录 D.6 历史截断策略 P1-4
03 §7 安全清单加文件内容读取用例 P0-3
03 §4 analyzer 依赖方向检查(import-linter) P2-3
01 §12 追溯矩阵补 FR-6.3 ↔ content API 关联 P0-3

7. 未发现问题的部分(确认有效)

  • 三进程边界与契约化方向(除 LLM 出口外)成立
  • SPI-1/3/7(语言解析器/LLM/图表)的"配置级扩展"设计正确
  • 降级链设计(LLM→规则版、Sonar→N/A、GitHub→zip)覆盖所有外部依赖
  • 单向依赖 + ArchUnit 防腐方案可执行
  • A0-A3 演进规划与"时间不足裁剪策略"务实
  • 线程池/超时/进度映射细节完整(除 P1-7 拆分外)

8. 建议的执行顺序

  1. 立即:按 §6 修订 04/02/03 三份文档(半天工作量)
  2. A0 实现前:以修订后契约为准写 P0 骨架(LLM 出口、编排、content API 先落位)
  3. P3/P6 前:完成 P1-2/3/4/6(报告重生成、错误契约、历史截断、SSE 转发)
  4. 可选:P2 项按时间窗口排入

9. 第二轮审查(2026-08-10,对象:06 API 契约 + 修订后 02/04)

9.1 结论

等级 数量 说明
P1 5 契约与 DDL/需求脱节的缺口,联调必踩
P2 5 一致性与健壮性问题

9.2 发现与修复状态

# 等级 位置 问题 修复(已执行)
R-1 P1 06 §3 无会话列表/删除;01 §7.9 界面要求"会话列表(新建/删除)" 06 缺 GET /projects/{id}/chatsDELETE /chats/{id} 06 新增 §3.15;02 §6.1 行更新
R-2 P1 06 §3.9 aiAdvice/ai_status 但 02 V002 dependency 无对应列 契约引用不存在的字段 02 V002 补 ai_advice JSONB + ai_status
R-3 P1 06 §5.8 / 04 §7.3 知识块"按 analysis_id 重建",但 V006 knowledge_chunk 无 analysis_id 无法按分析重建 02 V006 补 analysis_id,索引同步
R-4 P1 01 FR-8.2 热点为 AI 判断结果,DDL 无落库表 每次 GET /evolution 会重复重算/重调 LLM 02 新增 V007 hotspot
R-5 P1 06 §4.4 重复发送复用 2002("分析任务占用"语义不符) 错误码语义错位 新增 2007(发送过频)/ 2008(重生成中)
R-6 P2 regenerate 触发 SUCCEEDED→RUNNING 转移未定义 状态机缺分支 02 §7.1 补转移;06 §3.7 补前置条件与转移
R-7 P2 06 §5.8 rag/index 让后端把全文件内容经 HTTP 发给 analyzer 大项目内存/隐私面 改为 analyzer 直读磁盘(req 只传 codeDir)
R-8 P2 06 §2.1 3001→502 的 HTTP 映射无全局约定 各层可能不一致 06 补 GlobalExceptionHandler 映射约定
R-9 P2 06 §3.6 reportSource 与 §3.7 source 命名不一 前端易错 统一为 source
R-10 P2 错误码 2004 无触发场景 死码 保留并注明"预留"

9.3 待办(未修复,进入 backlog)

  • 会话标题自动生成的触发时机(建会话后异步生成 or 首条消息后生成)→ 实现时定(P6)
  • GET /files/content 的并发限流(防止大文件频繁读取拖垮 IO)→ 加简单令牌桶(P3)

10. 第三轮交叉审查(新增文档一致性)

审查对象:07 数据字典 + 08 测试计划(新增文档),与 01/02/03/04/06 交叉核对 · 2026-08-10 · 只读审查,修订由主会话执行 执行状态(2026-08-10):C-1~C-17 已全部执行完毕。执行说明:C-1/C-2 因数据库尚未部署,三列(report_source/prompt_version/regenerated_at)直接并入 V001__init.sql 与 02 §5 DDL(不新增 V008),07 §3.2/§5.2 同步;C-7 连带修复 02 §4 包结构、02 §8.1、03 §9.3/§1.2 同源残留;C-14 采用"新增 2009 映射 400",ErrorCode 枚举与单测已同步(2009 GIT_CLONE_FAILED)。

10.1 结论

等级 数量 说明
P0 1 07 内部自相矛盾且 06 契约依赖,落库/响应必踩坑
P1 6 跨文档明显不一致,联调或验收时暴露
P2 10 建议性:清单/样例/脚本/交叉引用口径统一

整体评价:07 与 02 DDL 主体一致(dependency.ai_advice/ai_status、knowledge_chunk.analysis_id、hotspot.module/risk_level、analysis.stage 均已对齐,前两轮修复有效);08 与 01 的 AC 覆盖主体完整,错误码编号(1002/2002/2003/2005/2007/2008/3001)与 06 §2.2 无冲突。但存在 1 处 07 内部自相矛盾(报告来源字段无落库列)、6 处跨文档明显不一致(AC-2 缺用例、P1 门禁错位、ai_status 口径、报告结构样例、04 LLM 残留引用),建议在写代码/建表前统一。

10.2 发现与建议修订

# 等级 位置 问题 建议修订
C-1 P0 07 §5.2 vs §3.2;02 V001;06 §3.6/3.7;08 T-U-10/11 analysis 表无 report_source/prompt_version 落库列:07 §5.2 声明"source 与 promptVersion 存 analysis 层字段或外层包装",但 §3.2 表与 02 V001 DDL 均无此列 → 06 报告/历史响应的 sourcepromptVersion 字段无存储支撑,08 T-U-10/11"落库 source=LLM/RULES"无法实现 02 新增 V008:analysis 补 report_source VARCHAR(10)(LLM/RULES)+ prompt_version VARCHAR(20);07 §3.2 同步两字段;07 §5.2 删除"外层包装"表述
C-2 P1 07 §3.2 vs 02 V001;06 §3.7 analysis.regenerated_at 在 07 存在、02 V001 DDL 缺失;06 §3.7 regenerate 需记录 regeneratedAt 02 V008 补 regenerated_at TIMESTAMPTZ;06 §3.7 注明该字段来源
C-3 P1 08 §2/§5 vs 01 §13 AC-2(GitHub 创建项目)无任何用例:08 无 GitCloneService 单测、无 GitHub 冒烟(T-I-01 仅 zip 链路;T-A-14 是 git log 统计,非克隆) 08 补:T-U 克隆成功/私有仓库/不存在/超时用例 + 集成冒烟(clone→档案),并入 P1 门禁
C-4 P1 08 §8 vs 01 §13 门禁 P1 门禁错位:01 P1=AC-1/2/8/10/11;08 P1 用例组缺 AC-1(档案字段非空+误差<10%,无集成级用例)与 AC-2;唯一覆盖 AC-1 的 T-I-01 被排在 P2 → P2 门禁"12 AC 全通过"不可验证 08 §8 P1 加入 T-I-01(或拆分档案用例)与新增 GitHub 用例;01/08 门禁一一对应
C-5 P1 06 §3.9 vs 07 §3.4/§4;02 V002 ai_status 口径冲突:06 注"null=未生成",07/02 为 NOT NULL DEFAULT 'NONE'(枚举 NONE/PENDING/DONE/FAILED)→ 前端按 null 判断永不成立 06 §3.9 改为"aiStatus='NONE'=未生成",三处统一 NONE
C-6 P1 02 §8.2/D.1/E.2/E.3 vs 06 §3.7;07 §5.2 报告结构口径不一:§8.2 prompt 与 D.1/E.2 缺 techStackdimensions.stars;E.3 把 source 放进 report_json 内部,与 06(data 层)及 07 §5.2(不在 report_json 内)直接矛盾 → 08 T-U-12 JSON 校验按哪套结构? 02 附录 D.1/E.2/E.3 按 06 §3.7 结构重写;source/promptVersion 一律放 data 层
C-7 P1 04 §2 图/§3.1/§3.2/§4.2/§10.1/§9 vs 04 §3.5 铁律 LLMClient 引用残留(P0-1A 修复未清干净):总体架构图与 §3.1 依赖图仍有 service→LLMClient;config/LLMConfig"LLMClient 装配";§4.2 线程表"LLM 调用"行;§10.1 ArchUnit 引用 OpenAiCompatibleLLMClient;§9 对策列 MockLLMClient —— 均与"backend 永不直连 LLM"矛盾 04 删除/改写 6 处:LLM 配置归 analyzer;线程表删 LLM 行;ArchUnit 示例改为验证 chat 转发不直连
C-8 P2 04 §3.3 vs 06 §5.4/§5.7;07 §4 analyzer api/routes 缺 chat.pyexplain.py;schemas 清单缺 ChatRequest(06 §5.7 请求体);backend enums 清单缺 ChatRole/AiStatus/HotspotLevel 等(07 §4 已有) 04 §3.3 补路由与 ChatRequest schema;§3.2 enums 按 07 §4 补全
C-9 P2 03 附录A.3 vs 07 §4/§3.2 伪代码阶段值 REPORT_DONE 不在 Stage 枚举(QUEUED/SCAN/SCAN_DONE/REPORT/DONE);FAILED 时 progress=-1 违反 0-100 约定 REPORT;失败分支 progress 置 0 或注明不刷新
C-10 P2 07 §1 vs 07 §3.3~3.16 通用约定"业务表含 deleted"与实际不符:16 表中仅 project/analysis 含 deleted(快照/结果/会话/向量表均无) 07 §1 注明 deleted 仅主档/任务表,列例外清单
C-11 P2 01 §13 vs 02 §13 vs 08 §8 高频冒烟口径不一:01 列 6 个 AC 却标"7 项";02 为 7 个 AC(含 AC-5);08 为 7 条用例对应 6 个 AC 统一为 02 版本(AC-1/3/5/6/7/8/9),01/08 同步
C-12 P2 08 §1 vs 02 §2.3;03 §4.5/§4.6 08 引用的 scripts/check-contract.ps1smoke.ps1 未在 02 §2.3 脚本清单(仅 start-dev/stop-dev/init-db)定义;"import-linter"行有工具无命令(03 §4.5 仅说"纳入 pytest 或 CI") 02 §2.3 补 3 个脚本说明;03 §4.6 补 import-linter 命令示例
C-13 P2 03 §2.4/附录A.1 vs 06 §2.2;02 §6.3 03 错误码表与 ErrorCode 示例未含 06 新增的 2004~2008、5003;而 02 §6.3 指引"见开发规范 §2.4" 03 §2.4 注明"以 06 §2.2 为唯一来源"或补全枚举;附录 A.1 同步
C-14 P2 06 §3.1 vs §2.2 GitHub 克隆失败用 3001(analyzer 不可达/内部错误)+502,语义错位(属业务错误) 新增 2009(仓库克隆失败)映射 400,或明确 3001 语义扩展
C-15 P2 07 §4 vs 06 §2.2 07 枚举字典未含错误码枚举,与 06 §2.2 无衔接 07 §4 增加"错误码(引用 06 §2.2,实现为单文件 ErrorCode)"条目
C-16 P2 08 头部声明 vs 08 §3~§8 "覆盖《06 契约》全端点"不实:dependencies、quality-issues、explain、docs 端点无对应用例(P3/P7 门禁缺接口用例) 08 补契约用例或改声明为"覆盖 06 主要端点"
C-17 P2 08 §1 性能行 性能预算章节为 §7,表格误写"见 §6"(§6 是安全测试) 改"见 §7"

10.3 确认一致的部分(未发现问题)

  • 07 vs 02 DDL:16 张表字段/类型/索引/枚举主体一致(含 ai_advice/ai_status、analysis_id、hotspot、stage 等前两轮修复项)
  • 07 vs 06:report_json 结构与 §3.7 一致;citations [{file,line,excerpt}] 与 SSE citations 事件一致;source 位置约定一致(仅存储列缺失,见 C-1)
  • 08 vs 06:用例引用的错误码(1002/2002/2003/2005/2007/2008/3001)全部存在、无矛盾;2004 预留说明一致
  • 08 vs 02 §14:性能预算(≤10min / ≤120s / ≤1s / ≤3s / 并发 3)完全一致
  • 08 vs 03 §10:验证命令(mvn test / pytest -q / ruff / npm run lint && npm run build)一致
  • 04 vs 06:backend 类清单与端点边界一致(FileController/content、ReportService/regenerate、ChatController/SSE 均在)