代码结构重整:拆分超大文件

Context

agent-first-mail 的若干源文件已膨胀到难以维护,最突出的是 src/store/mod.rs(6618 行)——单个 impl Workspace 块横跨 95–4343 行,混杂 config / case / message / triage / archive / remote-sync / 渲染 / 引用收集 8 类职责。其余偏大文件:imap_pull.rs(1887)、config.rs(1833)、push_queue.rs(1130)。

本次工作是纯机械、行为保持的模块拆分:把大文件按职责切成聚焦的子模块,结构清晰、单文件可控。不改任何逻辑、签名、公共 API 路径或磁盘格式,只动:函数体整段搬移、可见性 token(fnpub(super) fn)、mod 声明、pub(crate)/pub(super) use 再导出。成功判据:每一步后 cargo build && cargo test && cargo clippy 全绿(基线:59 个单元测试通过、0 clippy 错误)。

范围:所有较大文件都整理,store 取中等粒度

关键约束:Rust 可见性

Workspace 是单一类型,其 impl 可分散到多个子模块文件(Rust 合法,多 impl 块)。规则:

  1. pub 方法与类型路径稳定——runner.rs/pipe.rs 调用的 Workspace::* 公共方法可自由搬到任意 store/*.rs,无需再导出。
  2. 无修饰 fn foo() 私有于其所在子模块——若 A 子模块定义、B 子模块调用,必须升为 pub(super)(对父模块 store 可见,即对所有兄弟子模块可见)。只在本子模块内调用的保持私有。不确定时一律 pub(super)(不越出 crate,安全)。
  3. 外部路径必须稳定(最高风险)——lib.rspub mod store;。当前可经 crate::store::X 触达的自由函数搬入子模块后,须在 mod.rspub use sub::X; 再导出。已核实的外部引用:
    • now_rfc3339(留在 mod.rs)、clean_body_textrender_message_sectionrender_message_section_with_config(均 pub,被 mail/smtp_send/imap_pull 调用 → render.rs 后再导出)
    • render_triage_view(pub(crate),imap_pull 调用 → triage.rs 后再导出)
    • Workspace::{ensure_archive_eligible, message_remote_locations, message_remote_locations_any, add_remote_flags, read_message_by_id, relocate_message, ensure_message_ids_unreferenced}(pub(crate),push_queue 调用;方法路径稳定,保持 pub(crate) 即可)

目标布局

src/store/
  mod.rs          # Workspace 结构体、刷新统计结构体、mod 声明、再导出块;
                  # 生命周期/编排:at/discover/root/init/status/prune/pull/
                  # reconcile_remote_missing/config_*/remote_*/push/push_list/
                  # render_refresh/render_templates/log_*;now_rfc3339;merge_*_into_pull
  util.rs         # 共享 fs/string/校验/uid/time/rfc822 自由助手 + 高频 Workspace 小方法
                  # (require_workspace/append_audit_event/checked_reason/ui_language/
                  #  message_path/message_date/next_case_uid/...),全部 pub(super)
  cases.rs        # case CRUD + 解析/枚举 + 归档case操作 + notes + items;ArchivedCaseEntry
  messages.rs     # 处置(ignore/spam/unspam/archive/trash/untrash/unarchive) + 消息读取/
                  # relocate + related/thread + update_messages_workspace + move_to_deleted
  archive.rs      # 直邮归档类目(create/show/restore/move/rename/summary/notes + dir/io/索引渲染)
  triage.rs       # triage 视图 + case 消息视图渲染(derived_status/triage_candidate/
                  # write_triage_view/render_case_index/render_case_message_view/...)
  drafts.rs       # 草稿(validate/remove/compose/reply/create + 附件 fetch + 草稿状态);
                  # DraftValidation/DraftStateFile/DraftStateEntry
  render.rs       # 消息渲染自由函数 + thread 助手 + 模板助手(clean_body_text/
                  # render_message_section*/message_section_context/render_template/...)
  remote_sync.rs  # 远端位置 reconcile + 引用收集(collect_*_references/ensure_archive_eligible/
                  # message_remote_locations*/add_remote_flags/active_remote_locations/...)
  refs.rs         # 既有,CaseIndex,零改动
  tests.rs        # 由 mod.rs 内联 mod tests 搬出,#[cfg(test)] mod tests;

src/push_queue/
  mod.rs          # 全部 pub 函数(queue_*/push/list/mode_summary/remove_*) + 枚举/结构体 + preview_hint
  execute.rs      # push_outbound_*/push_action_steps/execute_*/push_special_use_move/
                  # resolve_action_mailbox_folder/mailbox_is_kind(入口 pub(super))
  preview.rs      # filtered_items/actions_for/item_summary_label/item_has_move_to/step_label
  io.rs           # read/write/delete_item/push_path/read_item_eml/find_case_path*/unique_push_id/...

src/imap_pull/
  mod.rs          # 全部 pub 包装 + 共享 pub 类型(PullTarget/MoveOutcome/RemoteMessage/
                  # FolderUidSnapshot/MailboxInfo) + pull_workspace/resolve_pull_targets/
                  # remote_*/uid_*/append_*/fetch_uid_snapshots/...;内联 mod tests 保留在此
  session.rs      # 所有 *_session + login_*/fetch_*/list_*/capability_move/require_move/...
  special_use.rs  # resolve_special_use_from_mailboxes + special_use_*/fallback_names/...
  identity.rs     # RemoteIndex/SavedMessage/ImapKey/save_remote_message/stable_message_id/
                  # add_remote_location/rfc822_*/fnv1a_hex/normalize_message_id/...

src/config/
  mod.rs          # 全部 serde 类型 + Default impl + default_*/parse_*/validate_* 自由函数 +
                  # 内联 mod tests + impl MailConfig 的 load/validate/默认构造等
  access.rs       # impl MailConfig 第二块:key 读写分发(get_key/set_key/get_mailbox_key/
                  # set_mailbox_key/get_pull_mailbox_action_key/set_*/get_archive_action_key/...)

config 只做最小低风险切分:把 key get/set 分发抽到 config/access.rs 的第二个 impl MailConfig,所有类型、Defaultdefault_* 自由函数、#[serde(default="...")] 耦合与内联测试全部留在 config/mod.rs。这样既覆盖“所有大文件都整理“,又不触碰 serde 路径耦合带来的高 churn。

逐项映射(职责桶)

完整 item→文件映射与每项可见性按上述桶归位。要点:

安全增量顺序(每步后 build+test+clippy 全绿,逐步提交)

  1. store/util.rs(叶子助手,解锁其余;加 pub(super) use util::{case_status, read_case_messages};)
  2. store/refs.rs(仅验证 super:: 解析,无搬移)
  3. store/render.rs(加 pub use render::{...};整 crate build 确认 mail/smtp/imap 外部调用仍链接)
  4. store/remote_sync.rs
  5. store/triage.rs(加 pub(crate) use triage::render_triage_view;)
  6. store/archive.rs
  7. store/messages.rs
  8. store/cases.rs
  9. store/drafts.rs
  10. store/tests.rs(最后,跨所有桶;靠编译收敛 import)
  11. store/mod.rs 收尾(应落到 ~600–900 行)
  12. push_queue/:io → execute → preview → mod 瘦身
  13. imap_pull/:session → special_use → identity → mod 瘦身(测试块留内联)
  14. config/:抽 access.rs(仅 key get/set 分发的第二个 impl MailConfig)

验证

风险与对策

  1. 外部路径断裂(首要)——所有搬走的 pub/pub(crate) 自由函数从 mod.rs 再导出;方法靠类型路径稳定。整 crate build 兜底。
  2. 跨子模块私有可见性——不确定即 pub(super);编译器 function ... is private 精确定位漏网。
  3. 内联测试可见性——store::tests 需显式 super::<bucket>::<item> 导入;imap_pull/config 测试块留内联,把该问题只留给 store 一次。
  4. 无环——所有桶向内依赖叶子 util.rs 与经 pub(super)Workspace 方法;跨 impl 调用按类型解析、无 use 环;共享结构体各只一处。
  5. serde 路径耦合——正是 config 不做完整拆分、只抽 access 的原因。
  6. yaml_double_quote 疑似既有死代码(0 内部调用)——保持私有、不删(删除属行为相邻,可能翻转 lint);render 步骤再 grep 确认。

待实现的关键文件