Files
Auto-Finance/AGENTS.md
T
wandering d3728bfe51 docs: 记录 git 历史清洗,并把文档里的提交号重映射到新历史
- 方案文档新增 §12:为什么洗(公开文档含真实工号/姓名/16 位公务卡号/内网 LLM 地址与 key,
  历史 config.json 含真实登录密码;另发现两份 .xls 是真实申购单、四组样本发票无法证明不含个人信息)、
  怎么做(git-filter-repo 删 19 条路径 + 全历史替换六类特征)、怎么验证(重扫 608 个 blob 零命中、
  与备份 HEAD 逐文件对比只有预期差异)、善后(改密码、轮换 Gitea token、旧 clone 重新拉、删备份 bundle)
- §11.5 第 3 项(模板选择)标记完成
- 按 .git/filter-repo/commit-map 把文档引用的 6 个提交号重映射(6 文件 10 处),
  否则所有「看提交 xxx」的指引在重写后的历史里都是死链
- src/infra/documents/README.md 改写模板章节;docs/MCP.md 文档产物段、README 结构表、
  AGENTS 重要约束同步模板解析顺序与 REQUISITION_TEMPLATE
- 另记一笔:.git/filter-repo/already_ran 时间戳是 2026-06-12,这个仓库六月中已洗过一次历史
2026-10-19 23:57:49 +08:00

128 lines
7.8 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 与模态框;
申购单空白模板在 `templates/`,可用环境变量 `REQUISITION_TEMPLATE` 指向别处,产物只写会话目录 `artifacts/`
* **浏览器自动化**: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 上的工具清单,不是进程内对象)。