日志补记下半场:三件套的落地细节、行宽为何定 100、PTH 为何不开、pyright 两个关键开关;新增三条可复用环境坑(write_text 会把 LF 变 CRLF、同文件多次 Edit 互相覆盖、git status 与 git diff 不一致的判别)。 项目长期约定同步更新:运行方式改为 uv 单一可信来源,新增代码风格约定与质量门禁命令。
60 lines
4.5 KiB
Markdown
60 lines
4.5 KiB
Markdown
# SimpleFarm 项目长期约定
|
||
|
||
## 是什么
|
||
终端农场经营游戏 + 小型多人游戏服务器(v2.0)。游戏跑在服务端进程里,
|
||
TUI 是纯客户端,同时开放 HTTP + WebSocket 接口给 AI/脚本调用(服务端不内置 AI)。
|
||
|
||
## 运行方式(v2.0 起由 uv 统一管理)
|
||
- **单一可信来源是 `pyproject.toml`**,`.venv/` 由 uv 创建;`uv.lock` **要提交**(可复现构建依据)。
|
||
加/删依赖走 `uv add` / `uv remove`,不要手工编辑依赖表,也不要再列 `pip install`。
|
||
- 所有命令带 `uv run ` 前缀:`uv run main.py serve --daemon`,网页 <http://127.0.0.1:8765/>,文档 /docs
|
||
- 玩:`uv run main.py play --farm 名字`;单机:`uv run main.py solo`
|
||
- AI/脚本入口:`main.py actions | do <动作> '<JSON>' | batch | state --json | watch | bot`
|
||
- 测试:`uv run python -m unittest discover -s tests`(97 项)
|
||
- 质量门禁(改动后都应跑一遍):
|
||
`uv run ruff check .` / `uv run ruff format .` / `uv run pyright`
|
||
- 不用 uv 的人:`requirements.txt` 是 `uv export` 的产物,只读、不要手工改。
|
||
README 与 main.py 顶部保留了一段"没装 uv?把 `uv run ` 去掉即可"的兜底说明,**别删**。
|
||
|
||
## 架构约定(改动时必须遵守)
|
||
- **分层**:`farm.engine` 只管规则,不认识网络/终端;`farm.tui` 不认识 socket;
|
||
`farm/server/*` 不认识终端。新增玩法动作只改 `farm/server/actions.py` 一张表。
|
||
- **接口同构**:`farm.client.RemoteFarm` 必须与 `FarmEngine` 同名同语义,
|
||
这样 `tui.py` 不需要区分本地/远程。加动作时两边都要补。
|
||
- **多牧场**:一个 farm = 一个玩家 = 一份 `saves/<名字>.json` + 一个引擎 + 独立时钟;
|
||
时钟由**一个** `FarmClock` 线程统一驱动,不要给每个牧场开线程。
|
||
- **存档类动作**:`save` / `reset` 必须由会话(FarmSession)处理,
|
||
不能直接落到 `engine.save()`(会写默认路径、覆盖别人的档)。
|
||
- **实时通道**:WebSocket(`/ws`)是唯一实时通道,推送节流 250ms,消息带自增 `seq`。
|
||
写操作优先走 WS(带 `id` 回执与 `snapshot`),只有 ws_closed/timeout/network
|
||
才退回 HTTP 重试;**业务错误必须原样抛出,不能重试**。
|
||
|
||
## 环境禁忌(本机特有)
|
||
- Bash 的 coreutils 残缺(`ls`/`head`/`tail`/`grep` 缺失),**不要用管道**,会整条命令失败;
|
||
查看/删除文件一律写 Python 脚本。
|
||
- 环境设了 `HTTP_PROXY` 但没有 `no_proxy`:任何本机 HTTP 访问都要走 `farm/net.py` 的
|
||
`open_url()`(回环直连),否则会拿到 502 并误判为"服务器没起来"。
|
||
- Windows 下守护进程的 pid 必须由**服务进程自己**写(见 `farm/server/__main__.py`),
|
||
父进程的 `Popen().pid` 不可信。
|
||
- **写文件必须指定行尾**:Windows 上 `pathlib.write_text()` 会把 `\n` 转成 `\r\n`,
|
||
破坏本仓库"统一 LF"的约定(`.gitattributes` 已声明 `eol=lf`)。
|
||
用脚本改文件时一律 `read_bytes()/write_bytes()`,或 `write_text(..., newline="\n")`。
|
||
- **同一文件在一条消息里多次 Edit 会被静默覆盖**(只落地一部分,随后报出"幽灵"错误)。
|
||
批量改同一文件时改用**幂等 Python 补丁脚本**:整文件读入 → 精确匹配替换 →
|
||
逐条打印 `ALREADY/APPLIED/MISS`,跑完即删。判断"已应用"要 `if new in text`,
|
||
不要比对两行文本(会出现 new 是 old 子串的假阳性)。
|
||
- `git status` 报 M 但 `git diff` 为空,是索引 stat 缓存过期,不是真差异;
|
||
`git add -A`(内容相同时等于无操作)即恢复干净。
|
||
|
||
## 代码风格约定(ruff / pyright 已固化在 pyproject.toml)
|
||
- 行宽 **100**;字符串统一双引号;`isort` 把 `farm` 视为第一方。
|
||
- 类型标注用 PEP 585/604 写法(`dict[str, int]` / `X | None`),不要 `Dict` / `Optional`。
|
||
- 吞异常要显式:`contextlib.suppress(...)` 优先于 `try/except/pass`。
|
||
- 异常链:转换后重抛用 `raise ... from exc`;有意隐藏原始上下文用 `from None`(**不要省略**,会触发 B904)。
|
||
- 路径保持 `str`(不要为了 lint 而转 `Path`);`farm/__init__.py` 的惰性 `__all__` 导出是刻意设计。
|
||
- 改完代码的标准动作:`uv run ruff format . && uv run ruff check . && uv run pyright && uv run python -m unittest discover -s tests`
|
||
|
||
## 相关文档
|
||
- 项目入口文档:`README.md`(接口表、WS 协议、边界处理、设计取舍全在里面)
|
||
- 当天工作记录:`.workbuddy/memory/2026-09-18.md`
|