贡献指南¶
开发环境¶
# 安装依赖(含 dev 组;workspace 成员已取消,现在等价于装根包)
uv sync --all-packages
# 确认测试通过
uv run pytest tests/test_zotero_server.py tests/test_paper_to_zotero.py -v
运行测试¶
# 全部测试
uv run pytest -v
# 仅 Zotero MCP Server 测试
uv run pytest tests/test_zotero_server.py -v
# 仅 paper-to-zotero 测试
uv run pytest tests/test_paper_to_zotero.py -v
# 单个测试类
uv run pytest tests/test_zotero_server.py::TestZoteroSearch -v
# 覆盖率
uv run pytest --cov=hydrolitagent/zotero --cov-report=term-missing
测试模式¶
所有测试使用:
- pytest 框架
- unittest.mock(MagicMock、AsyncMock、patch)模拟外部依赖
- AAA 模式(Arrange → Act → Assert)
模拟外部依赖¶
测试文件顶部的 stub 模式确保模块在依赖未安装时也能导入:
# 在导入被测模块之前 stub 外部包
import sys
from unittest.mock import MagicMock
_pyzotero_module = MagicMock()
sys.modules["pyzotero"] = _pyzotero_module
sys.modules["pyzotero.zotero"] = _pyzotero_module.zotero
from hydrolitagent.zotero import server
测试命名¶
class TestZoteroSearch:
def test_search_returns_formatted_items(self, mock_zot_client, ...):
...
def test_search_handles_empty_results(self, mock_zot_client, ...):
...
def test_search_handles_api_error(self, mock_zot_client):
...
添加新 MCP 工具¶
向 server.py 添加新工具的标准步骤:
1. 编写工具函数¶
@mcp.tool()
async def zotero_new_tool(param: str, limit: int = 10) -> str:
"""One-line description of what this tool does.
Longer description of the tool's purpose, what it returns,
and any important notes about usage.
"""
if not param:
return _make_error("zotero_new_tool", "param is required")
if not _zot:
return _make_error("zotero_new_tool", "Web API client not initialized")
try:
# 实现逻辑
result = await asyncio.to_thread(_zot.some_method, param, limit=limit)
return json.dumps(result, ensure_ascii=False, indent=2)
except Exception as e:
logger.error("zotero_new_tool failed: %s", e)
return _make_error("zotero_new_tool", str(e))
2. 编写测试¶
在 tests/test_zotero_server.py 中创建新的 Test Class:
class TestZoteroNewTool:
def test_returns_expected_format(self, mock_zot_client):
...
def test_missing_param_returns_error(self, mock_zot_client):
...
def test_handles_api_error(self, mock_zot_client):
...
def test_handles_no_client(self):
with patch.object(server, "_zot", None):
result_json = asyncio.run(server.zotero_new_tool(param="test"))
result = json.loads(result_json)
assert result["error"] is True
3. 更新文档¶
在 docs/user-guide/mcp-tools.md 中添加新工具的文档段落。
代码风格¶
- 遵循 PEP 8
- 所有函数签名使用 type annotations
- docstring 描述每个工具的用途、参数和返回值
- 使用
_make_error()统一错误返回格式 - 使用
_format_item()统一条目输出格式 - 日志使用
logging模块(不用print())
构建文档站点¶
# 本地预览(热重载)
uv run mkdocs serve
# 构建静态站点
uv run mkdocs build
# 严格模式(检查 broken links)
uv run mkdocs build --strict
发布在线文档¶
在线文档地址:Hydro AI Papers。
它只展示 docs/ 构建出的静态网页,不运行 Zotero 或 AI 制卡服务。
日常更新¶
- 修改
docs/中的 Markdown;增加页面时,在根目录mkdocs.yml的nav中补上导航。 - 在仓库根目录运行
uv sync --locked,再运行uv run mkdocs build --strict。 - 用
uv run mkdocs serve本地检查排版、链接与流程图;检查完成后按Ctrl+C停止预览。 - 按课题组约定提交修改。PR 会运行文档构建检查,但不会发布;文档相关修改进入
main后自动发布。 - 在仓库 Actions → Documentation 查看构建和部署结果,再打开在线文档确认新内容。
工作流监听 docs/、mkdocs.yml、依赖与自身配置的变更;其他代码变更不会触发文档发布。
如需主动重新发布,可在 Actions → Documentation → Run workflow 选择 main。
手动运行其他分支只构建、不部署。
仓库首次设置¶
- 在 Settings → Pages → Build and deployment → Source 选择 GitHub Actions。
- 发布工作流使用
github-pages环境;如果设置环境保护规则,允许main部署。 - 在仓库右侧 About → Website 填写
https://iheadwater.github.io/mylibrary/。 mkdocs.yml的site_url必须与实际站点地址一致,包括末尾/。
工作流使用仓库 uv.lock 安装依赖,只上传 site/ 目录;不需要添加 Zotero、DeepSeek 等业务密钥。
不要把 site/ 或 .venv/ 提交到 Git,它们已被忽略。
出错与回退¶
严格构建失败时,先看 Build documentation 的日志,修复问题后再提交;构建失败不会发布新版本。
构建通过但部署失败时,检查 Pages 发布来源、工作流权限与 github-pages 环境是否等待审批。
如果已发布的文档需要回退,用 git revert <需要撤销的提交号> 创建撤销提交并推送 main;
工作流会重新构建和发布。不要用强制推送删除课题组的提交历史。
版本管理与 GitHub Release¶
本项目用 uv 管理固定版本:pyproject.toml 的 [project].version 是唯一版本来源,
uv.lock 与安装环境由 uv 同步。沿用 setuptools 构建方式,不混用基于 Git tag 的动态版本。
首次 GitHub Release 从 v0.0.1 开始;此前文件里的 0.1.0 未创建对应 tag 或 Release。
开发下一版¶
先明确下一版要解决的问题、验收条件及升级类型,再实施小步修改。只选择下面的一种:
# 小修复:例如 0.0.1 -> 0.0.2
uv version --bump patch
# 新能力:例如 0.0.1 -> 0.1.0
uv version --bump minor
# 不兼容的重大变化:例如 0.0.1 -> 1.0.0
uv version --bump major
这些是互斥示例,不要连续执行三条。版本选择由维护者判断;uv 负责递增版本、更新锁文件和同步环境,
不是每提交一次代码就自动升版本。0.x 阶段接口仍在演化,发布说明要明确兼容性变化。
需要设定精确版本时用 uv version <版本号>,不要手改 uv.lock。
发布步骤(在仓库根目录执行)¶
下面的 0.0.1 / v0.0.1 是首发示例,后续必须替换为实际待发布版本。
# 安装锁定依赖,并验收
$env:PYTHONUTF8 = "1"
uv sync --locked
uv run pytest -rs
uv run hydrolitagent cards --help
uv run python scripts/paper_validate.py --draft hydrolitagent/cards/assets/examples/subject-card-validation/draft.json --paper-input hydrolitagent/cards/assets/examples/subject-card-validation/paper-input.json --report-dir outputs/release-validation
uv run mkdocs build --strict
uv version --short
# 维护者审阅本版变更与发布说明后提交,并推送 main(或按团队约定合并 PR)
# 注意:git add 只列本次要发布的文件,不使用 git add . 混入其他人的改动。
# git add -- <本次文件列表>
# git commit -m "chore: prepare release v0.0.1"
git push origin main
确认 Actions → Release checks 的主线检查成功后,记下已经验收的完整提交 SHA。 对这个确定的提交打 tag,而不是对可能已被他人推进的远端最新主线盲目打 tag:
# 把 <已验收提交SHA> 替换为该提交的完整 SHA
git tag -a v0.0.1 <已验收提交SHA> -m "mylibrary v0.0.1"
git push origin refs/tags/v0.0.1
再次确认该 tag 的 Release checks 成功,再创建 GitHub Release。 首发说明在 v0.0.1 发布说明;后续各版新增自己的说明文件。
gh release create v0.0.1 --repo iHeadWater/mylibrary --verify-tag --title "mylibrary v0.0.1" --notes-file docs/releases/v0.0.1.md
发布说明必须写清本版能力、安装入口、验收结果、跳过项与已知边界;不能把离线示例通过写成真实论文生成通过。
测试中的跳过项保留原因,不通过排除失败用例来宣称全绿。
Release checks 自动核对项目版本、安装版本与 tag,并运行测试、离线示例和严格文档构建。
它不替维护者选择版本号,也不自动创建 Release。
Windows 验收使用 UTF-8 模式(PYTHONUTF8=1);CI 测试步骤的 TEMP / TMP 指向 runner 标准临时目录,
避免托管用户的短路径别名与解析后的完整路径在断言中混用。原测试及业务代码不因 CI 失败而被排除或削弱。
本项目目前只发布 GitHub tag + Release,不上传 PyPI,不需要 uv publish、PyPI 密钥或 Trusted Publishing,
也不要求制作 wheel/sdist 发布附件。用户通过源码快照与 uv 使用。
已公开的 tag/Release 不通过删除重建来移动版本;发布错误时说明问题并发布修正版。