Skip to content

[D3] FormatLinter (5 checks) + evidence gate + LLM tiebreaker (排版质量门 + 路由 LLM 兜底) #14

@hitome0123

Description

@hitome0123

Parent epic: #11
Depends on: D1 (#12), D2 (#13)
Estimate: ~1 dev-day (~5h Opus)

范围 Scope

两件事打包:

  1. FormatLinter:自动检出 5 类排版问题并能挡 manuscript bundle(直接对应会议抱怨“字体大小不一/布局紧凑”)
  2. 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

  • 故意污染的 fixture .tex 命中所有 5 个 check
  • 干净 fixture(用现有 ICLR 样板)全绿
  • gate 在 block_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

  • tiebreaker 关闭时纯规则路径走通(fixture 测试)
  • tiebreaker 开启 + 有 LLM key 时调用走通(mock LLM 客户端)
  • 无 LLM key 时 graceful 回退到 top1(不 throw)
  • 新增 tests/test_venue_router_tiebreaker.py ≥ 3 case

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 文本)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions