e1acf2de84
接口与入门: - 新增 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 生成,产物单元格读回正确。
127 lines
7.7 KiB
Markdown
127 lines
7.7 KiB
Markdown
---
|
||
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 上的工具清单,不是进程内对象)。
|