Files
Auto-Finance/AGENTS.md
T
wandering e1acf2de84 docs: 文档全面对齐 MCP 架构,并脱敏公开操作指南
接口与入门:
- 新增 docs/MCP.md:注册方法(DSH patch + 通用 client)、返回信封、会话目录布局、标准流程、
  硬约束与未实现清单;工具参考段由**运行时 input_schema 自动生成**,与代码同源不会漂移
- 删除 docs/API.md(856 行 Flask 路由/SSE 文档,描述的端点已不存在)
- 重写根 README.md:为什么放弃自带前端、职责分工表、快速开始、注册配置、流程链、结构与风险
- 重写 AGENTS.md:命令表换成 mcp/mcp-smoke/upload;mypy 目标改 src;新增「提交纪律」
  (pre-commit 的 stash 窗口会回滚未暂存改动)、COM 与 Playwright 线程约束、多进程共存提醒

包文档:src/README.md 与各包 README 按现状重写;新增 src/mcp/README.md(写工具的规矩、
宿主侧四个坑)与 src/upload/README.md(路由表与安全边界);tests/scripts/config 三处索引更新。

提示词迁移:src/infra/llm/ 整个删除,三份系统提示词迁到 .agents/skills/reimbursement/references/
(invoice-fields / travel-info-fields / normal-info-fields),删掉 can_submit 与 suggestion
(save_info 也会主动剥离),「严格只输出 JSON」改为「交给对应工具落盘」;新增 SKILL.md 作为
宿主侧操作纪律(已被 DSH 热加载为 reimbursement 技能)。

作废处理:删除 .agents/docs/guides/系统事件流全景图.md(SSE 与 file_events/agent_events 通道已整体
移除,现在没有任何事件流);给项目架构全景图与架构分析-2026-06-15 加「已过时」横幅保留历史;
config.example.json 去 consumable_storage 加 upload_pin;.env.example 去 LLM 段并说明已不再自动加载 .env。

安全:docs/报销操作指南.md 的示例配置里原来写着**真实姓名、16 位公务卡号、内网 LLM 地址与工号**
(自 9fe5d70/2026-05-25 起在版本历史里),本次全部换成占位符。历史清洗与令牌轮换另议。

门禁(改动含 src/infra/llm 删除,故重跑全部门禁):
  ruff check PASS | ruff format --check PASS(42) | mypy src PASS(34) | deptry PASS(36) |
  pytest 107 passed | scripts/mcp_smoke.py PASS(17 工具 / instructions 1449 字节 / case_list ok)
申购单在依赖清理后另已实机复测:2.4s 生成,产物单元格读回正确。
2026-10-19 22:40:06 +08:00

127 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description:
alwaysApply: true
---
---
last_reviewed: 2026-10-19
---
# AGENTS — 项目操作指南
本文件为 Agent 提供高信号量的项目操作知识,避免重复探索。
## 文档边界
* **禁止使用表情文字**输出任何内容。
* `docs/` 目录存放面向开源用户、外部贡献者的公开文档。
* `.agents/` 目录存放维护规范、实施方案、经验总结等内部资料。
* 报销业务的操作纪律在 `.agents/skills/reimbursement/SKILL.md`;工具契约在 `docs/MCP.md`。
## 开发命令(必须使用 uv)
项目用 `uv` 管理依赖,版本锁在 `uv.lock`。
| 操作 | Makefile (跨平台) | tasks.py (Windows) |
|------|-------------------|---------------------|
| 安装依赖 + pre-commit | `make install` | `python tasks.py install` |
| 代码检查(lint+format+typecheck+deptry) | `make check` | `python tasks.py check` |
| 运行测试(含覆盖率报告) | `make test` | `python tasks.py test` |
| MCP stdio 自检(起子进程握手列工具) | `make mcp-smoke` | `python tasks.py mcp-smoke` |
| 启动上传收件器 | `make upload` | `python tasks.py upload` |
| 打印 MCP 注册参数 | `make mcp` | `python tasks.py mcp` |
| 清理缓存和虚拟环境 | `make clean` | `python tasks.py clean` |
没有 CLI 产品入口,也没有 Web 界面:业务入口只有 `python -m src.mcp.server`(stdio),
工具在宿主里表现为 `mcp__arb__<tool>`。
**注意:** `tasks.py` 的 `check` 用 `&&` 连接(PowerShell 不支持 `&&`,但 tasks.py 内部是单行字符串,没问题)。
## 代码质量工具链(执行顺序)
1. **Ruff lint** — `uv run ruff check .` (select: E, F, W, I, N, UP, B; ignore: E501)
2. **Ruff format** — `uv run ruff format --check .` (line-length: 120)
3. **MyPy strict** — `uv run python -m mypy src`(整个 src 都在 strict 下,不只是某个入口文件)
4. **deptry** — `uv run deptry .`(检测未声明、未使用、过时依赖)
`uv run mypy` 在本机偶尔报 "uv trampoline failed to canonicalize script path",用 `uv run python -m mypy` 代替。
### 提交纪律(血的教训)
* **门禁退出码要单独确认再提交**,别把"跑检查"和"提交"写在同一条命令链里——检查失败也能走完流程。
* 本仓库 `pre-commit` 只挂 ruff,但它会 **stash 未暂存改动**再跑,跑完才还原。
若这个过程被中断(超时/杀进程),未暂存的文件会**被回滚成 HEAD 版本**且 `git fsck` 捞不回来。
本项目改造过程中真发生过一次,四个文件的改动靠锚点重放才恢复。
* 因此:提交前先把工作区改动 `git add` 干净,或用 `--no-verify` 提交并手动跑完等价检查(在提交说明里写明)。
## 项目架构(MCP 服务器模式)
**唯一业务入口**:`python -m src.mcp.server`(stdio)。编排(读图判断、缺料追问、是否提交)由宿主 Agent 完成,
本项目**不调用任何大模型**,只提供确定性工具与内核。
- `src/mcp/` — 工具与协议层:`server.py` 注册与启动,`tools/` 按 materials / extraction / analysis / documents 分模块,
`schemas.py` 抽取字段定义(单一真源),`envelope.py` 统一 `{ok, ...}` / `{ok:false, reason, next_action}` 信封
- `src/cases.py` — 会话目录 `cases/<case_id>/` 的唯一实现:材料、收件箱、产物与**阶段推导**(阶段由产物推导,不存在状态机文件)
- `src/core/` — 确定性内核:`extraction/`(缓存读写、PDF 渲染与文本层、自动转正)、`matching/`(金额匹配 + `runner.py` 从缓存匹配)、`validation/`(声明式规则校验)
- `src/infra/` — 基础设施:`browser/`(Playwright 填报,工具层待接入)、`documents/`(CSV、申购单 Excel COM)、`com_thread.py`(COM 单线程串行 actor)
- `src/upload/` — 上传收件器(**独立进程** `python -m src.upload.server`):手机扫码/PC 上传落 `cases/<id>/inbox/`,不含业务编排,绝不由 MCP 子进程拉起
已删除:`src/agent/`(手写状态机与 SSE)、`src/web/`(Flask 界面)、`src/main.py`+`pipeline*.py`(CLI/双管道)、
`core/extraction/{extractor,llm_extractor}.py`(内置 LLM 与事件日志)、`infra/llm/`(提示词迁到 `.agents/skills/reimbursement/references/`)、
`infra/documents/consumable.py`(Word 出库单下线)、依赖 `llama-index*` 与 `python-dotenv`。
### 宿主侧两条硬约定
* 调 MCP 工具**路径一律正斜杠**(`D:/Code/...`),反斜杠会被 DSH 的参数绑定层拒收。
* `required: true` 的字段(含值为空串的备注类)**必须存在于** info JSON 中——这是刻意约束,别改规则文件;
用 `mcp__arb__info_skeleton` 取骨架再填值,别凭记忆造结构。
### 数据流关键产物
| 文件 | 生成者 | 作用 |
|------|--------|------|
| `pages/<stem>/p01.png` + `text.txt` + `meta.json` | `case_render_pages` | 给宿主读的页面与 PDF 文本层(含转正角度) |
| `.invoice_cache/<stem><ext>.json` | 宿主抽取 → `case_save_extraction` 规范化落盘 | 单张票据结构化数据(小改用 `case_patch_extraction`) |
| `match_result.json` / `invoice_groups.json` | `match_payments` | 支付与发票的关联、差旅/普通分组 |
| `invoice_summary.csv` / `payment_records.csv` | `match_payments` | 发票级与支付级汇总(申购单取数) |
| `travel_info.json` / `normal_info.json` | 宿主综合 → `save_info` | 差旅/普通报销的全部结构化数据 |
| `artifacts/*.xls` | `doc_requisition` | 低值易耗品申购单 |
### 缓存机制
会话缓存在 `cases/<case_id>/.invoice_cache/`,文件名与源文件同名(`发票1.pdf` → `.invoice_cache/发票1.pdf.json`)。
缓存 JSON 的 `_source_file` + 内容指纹决定"是否陈旧",`case_status` 会报 stale 项;删缓存即重抽。
## 重要约束
* **Windows-only**:申购单依赖 Microsoft Excel + COM(`pywin32`)。所有 Office COM 调用必须走
`src/infra/com_thread.py` 的单线程 actor——并发驱动 Word/Excel 会互抢 ActiveDocument 与模态框
* **浏览器自动化**:Playwright,填报时会打开 Chromium,请勿手动干扰;Playwright sync API 有 greenlet 线程亲和,
接入 MCP 时同样要固定到专属单线程(P3)
* **多进程共存**:宿主会保留旧代 stdio 子进程,subagent/teammate 也会各起一条 transport—— observed 4 个 PID 并存。
因此任何"独占资源"(浏览器、Office)都要有磁盘锁,别假设只有一个服务器实例
* **敏感信息**:`scripts/data/config.json` 含登录凭据、`cases/` 含身份证号与卡号,两者都已在 `.gitignore`,
绝不要提交,也不要回显密码
* **发票类型区分**:差旅发票(高铁票/酒店住宿)走差旅报销流程,不生成申购单;普通发票生成低值易耗品申购单
## 上传收件器
```bash
uv run python -m src.upload.server # 0.0.0.0:5055,手机扫码可用
uv run python -m src.upload.server --local # 只监听 127.0.0.1
uv run python -m src.upload.server --pin 1234 # 额外访问码
```
页面地址由 `case_create` 返回(含每会话令牌)。安全边界:令牌强制(无令牌 403)、只写不读(无列表无下载)、
扩展名白名单、25MB/文件、200 文件/会话、PIN 可选。二维码由服务端用 segno 生成,零外部 CDN。
## 测试
```bash
make test # pytest + coverage report (term-missing)
```
测试目录 `tests/`,配置在 `pyproject.toml`(`testpaths`、`pythonpath=["."]`)。
COM 相关路径(申购单)不进单测——需要本机装有 Excel,改动后用真实生成验证(约 2-5 秒)。
改完 MCP 工具记得 `make mcp-smoke`(它验的是 wire 上的工具清单,不是进程内对象)。