Parent epic: #11
Depends on: D1 (#12), D2 (#13)
Estimate: ~1 dev-day (~5h Opus)
范围 Scope
两件事打包:
- FormatLinter:自动检出 5 类排版问题并能挡 manuscript bundle(直接对应会议抱怨“字体大小不一/布局紧凑”)
- LLM tiebreaker:venue router top1/top2 分差小于阈值时调 LLM 二选一,无 key 时 graceful 回退纯规则
改动 Changes
FormatLinter
- 新建
agents/format_linter.py,至少 5 项 check:
font_size_consistency — 扫 \fontsize / \large / \small 是否超出当前 venue adapter 的允许集
section_spacing — 连续 \section / \subsection 之间无正文段落
float_density — 单页 figure/table 数 > 阈值(按 venue 配置,默认 3)
citation_density — 每千字 \cite 数 < 3 报警
bib_style_match — \bibliographystyle{} 与所选 venue 的 adapter.bibstyle_name 一致
- 输出
format_lint_report.json:{check, severity, line, message, fix_hint}
- 编辑
agents/evidence_gate.py — 增 format_lint 维度,rule_set 可配 block_on_format_lint=true|false
LLM tiebreaker
- 编辑
agents/venue_router.py — 增 run_llm_tiebreaker(state, top_k=2, threshold=0.05)
- 仅当 top1/top2 分差 < threshold 时调用
- 无 LLM key(
ANTHROPIC_API_KEY / OPENAI_API_KEY 都缺)时跳过,回退纯规则 top1
- LLM prompt 模板放
agents/manuscript_templates/prompts/venue_tiebreaker.md
验收 Acceptance
FormatLinter
LLM tiebreaker
AI 可验收证据包(必须)
PR 必须生成:
artifacts/d3_format_linter_tiebreaker_acceptance.json
字段至少包含:
{
"base_ref": "...",
"commit": "...",
"format_linter": {
"dirty_fixture": "path/to/dirty.tex",
"clean_fixture": "path/to/clean.tex",
"dirty_status": "block",
"clean_status": "pass",
"checks_triggered": ["font_size_consistency", "section_spacing", "float_density", "citation_density", "bib_style_match"],
"report_path": "..."
},
"evidence_gate": {
"block_on_format_lint_true_blocks": true,
"block_on_format_lint_false_allows": true,
"persisted_report_location": "..."
},
"llm_tiebreaker": {
"mock_llm_called_when_margin_below_threshold": true,
"no_key_fallback_to_rule_top1": true,
"chosen_venue_with_mock": "...",
"chosen_venue_without_key": "..."
},
"test_command": "...",
"test_summary": "..."
}
防伪 / 防硬编码要求
- dirty fixture 必须分别触发 5 个 check,不能用一个总错误替代。
- 每条 lint finding 必须包含 line 或 span、severity、message、fix_hint。
- LLM tiebreaker 必须用 mock client 测试,AI 验收不依赖真实 API key。
- 无 key fallback 必须有测试覆盖,不能靠 try/except 吞掉异常。
- evidence gate 必须证明 blocked path 不会创建 manuscript bundle。
范围外 Out of scope
- 重排 LaTeX(不改文档,只检测)
- 自动修复 lint issue(仅产出
fix_hint 文本)
Parent epic: #11
Depends on: D1 (#12), D2 (#13)
Estimate: ~1 dev-day (~5h Opus)
范围 Scope
两件事打包:
改动 Changes
FormatLinter
agents/format_linter.py,至少 5 项 check:font_size_consistency— 扫\fontsize/\large/\small是否超出当前 venue adapter 的允许集section_spacing— 连续\section/\subsection之间无正文段落float_density— 单页 figure/table 数 > 阈值(按 venue 配置,默认 3)citation_density— 每千字\cite数 < 3 报警bib_style_match—\bibliographystyle{}与所选 venue 的adapter.bibstyle_name一致format_lint_report.json:{check, severity, line, message, fix_hint}agents/evidence_gate.py— 增format_lint维度,rule_set 可配block_on_format_lint=true|falseLLM tiebreaker
agents/venue_router.py— 增run_llm_tiebreaker(state, top_k=2, threshold=0.05)ANTHROPIC_API_KEY/OPENAI_API_KEY都缺)时跳过,回退纯规则 top1agents/manuscript_templates/prompts/venue_tiebreaker.md验收 Acceptance
FormatLinter
.tex命中所有 5 个 checkblock_on_format_lint=true时挡住 manuscript bundle 生成tests/test_format_linter.py≥ 7 case(5 check 各 1 + 1 干净 + 1 gate 阻断)format_lint_report.json写入agenda_evidence_gates.metrics_summary_json或 manuscript/evidence gate 等价可查询位置LLM tiebreaker
tests/test_venue_router_tiebreaker.py≥ 3 caseAI 可验收证据包(必须)
PR 必须生成:
字段至少包含:
{ "base_ref": "...", "commit": "...", "format_linter": { "dirty_fixture": "path/to/dirty.tex", "clean_fixture": "path/to/clean.tex", "dirty_status": "block", "clean_status": "pass", "checks_triggered": ["font_size_consistency", "section_spacing", "float_density", "citation_density", "bib_style_match"], "report_path": "..." }, "evidence_gate": { "block_on_format_lint_true_blocks": true, "block_on_format_lint_false_allows": true, "persisted_report_location": "..." }, "llm_tiebreaker": { "mock_llm_called_when_margin_below_threshold": true, "no_key_fallback_to_rule_top1": true, "chosen_venue_with_mock": "...", "chosen_venue_without_key": "..." }, "test_command": "...", "test_summary": "..." }防伪 / 防硬编码要求
范围外 Out of scope
fix_hint文本)