openathlete-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openathlete_mcp-0.1.0/.agents/plugins/marketplace.json +12 -0
- openathlete_mcp-0.1.0/.codex-plugin/mcp.json +16 -0
- openathlete_mcp-0.1.0/.codex-plugin/plugin.json +11 -0
- openathlete_mcp-0.1.0/.env.example +9 -0
- openathlete_mcp-0.1.0/.github/pull_request_template.md +14 -0
- openathlete_mcp-0.1.0/.github/workflows/ci.yml +65 -0
- openathlete_mcp-0.1.0/.github/workflows/release.yml +61 -0
- openathlete_mcp-0.1.0/.gitignore +24 -0
- openathlete_mcp-0.1.0/.mcp.json +14 -0
- openathlete_mcp-0.1.0/AGENTS.md +327 -0
- openathlete_mcp-0.1.0/CONTRIBUTING.md +83 -0
- openathlete_mcp-0.1.0/DESIGN.md +708 -0
- openathlete_mcp-0.1.0/LICENSE +21 -0
- openathlete_mcp-0.1.0/PKG-INFO +132 -0
- openathlete_mcp-0.1.0/README.md +105 -0
- openathlete_mcp-0.1.0/athlete_db.py +25 -0
- openathlete_mcp-0.1.0/coach-core.md +128 -0
- openathlete_mcp-0.1.0/coach-user.default.md +35 -0
- openathlete_mcp-0.1.0/coros_mcp_proxy.py +539 -0
- openathlete_mcp-0.1.0/coros_tools.json +1660 -0
- openathlete_mcp-0.1.0/dev/coros_tools_fixed.json +1715 -0
- openathlete_mcp-0.1.0/dev/coros_tools_raw.json +1715 -0
- openathlete_mcp-0.1.0/dev/fit_bench.py +105 -0
- openathlete_mcp-0.1.0/dev/garmin_workout_smoke.py +127 -0
- openathlete_mcp-0.1.0/dev/make_official_fix.py +147 -0
- openathlete_mcp-0.1.0/dev/official-fix/changed-schemas-only.recommended.json +1241 -0
- openathlete_mcp-0.1.0/dev/official-fix/tools-list.minimal.json +1717 -0
- openathlete_mcp-0.1.0/dev/official-fix/tools-list.recommended.json +2071 -0
- openathlete_mcp-0.1.0/dev/probe_dedup_match.py +139 -0
- openathlete_mcp-0.1.0/dev/query_garmin_daily.py +63 -0
- openathlete_mcp-0.1.0/dev/query_garmin_metrics.py +63 -0
- openathlete_mcp-0.1.0/dev/query_garmin_thresholds.py +66 -0
- openathlete_mcp-0.1.0/dev/query_training_status.py +60 -0
- openathlete_mcp-0.1.0/dev/test_proxy_stdio.py +50 -0
- openathlete_mcp-0.1.0/dev/verify_mcp_entry.py +53 -0
- openathlete_mcp-0.1.0/fit_laps.py +169 -0
- openathlete_mcp-0.1.0/kimi.plugin.json +26 -0
- openathlete_mcp-0.1.0/migrate.py +121 -0
- openathlete_mcp-0.1.0/openathlete_mcp.py +23 -0
- openathlete_mcp-0.1.0/pyproject.toml +42 -0
- openathlete_mcp-0.1.0/pytest.ini +3 -0
- openathlete_mcp-0.1.0/requirements.txt +13 -0
- openathlete_mcp-0.1.0/server.json +23 -0
- openathlete_mcp-0.1.0/skills/openathlete/SKILL.md +283 -0
- openathlete_mcp-0.1.0/src/openathlete/__init__.py +3 -0
- openathlete_mcp-0.1.0/src/openathlete/adapters/__init__.py +1 -0
- openathlete_mcp-0.1.0/src/openathlete/adapters/coros.py +334 -0
- openathlete_mcp-0.1.0/src/openathlete/adapters/coros_official.py +175 -0
- openathlete_mcp-0.1.0/src/openathlete/adapters/garmin.py +289 -0
- openathlete_mcp-0.1.0/src/openathlete/cli.py +254 -0
- openathlete_mcp-0.1.0/src/openathlete/config.py +169 -0
- openathlete_mcp-0.1.0/src/openathlete/credentials.py +43 -0
- openathlete_mcp-0.1.0/src/openathlete/db/__init__.py +1 -0
- openathlete_mcp-0.1.0/src/openathlete/db/athlete_db.py +1231 -0
- openathlete_mcp-0.1.0/src/openathlete/home.py +19 -0
- openathlete_mcp-0.1.0/src/openathlete/locks.py +169 -0
- openathlete_mcp-0.1.0/src/openathlete/mcp/__init__.py +5 -0
- openathlete_mcp-0.1.0/src/openathlete/mcp/envelope.py +66 -0
- openathlete_mcp-0.1.0/src/openathlete/mcp/schemas.py +402 -0
- openathlete_mcp-0.1.0/src/openathlete/mcp/server.py +123 -0
- openathlete_mcp-0.1.0/src/openathlete/mcp/startup.py +39 -0
- openathlete_mcp-0.1.0/src/openathlete/mcp/tools.py +463 -0
- openathlete_mcp-0.1.0/src/openathlete/memory.py +831 -0
- openathlete_mcp-0.1.0/src/openathlete/queries.py +435 -0
- openathlete_mcp-0.1.0/src/openathlete/structure.py +651 -0
- openathlete_mcp-0.1.0/src/openathlete/sync_state.py +1744 -0
- openathlete_mcp-0.1.0/src/openathlete/workout.py +882 -0
- openathlete_mcp-0.1.0/sync_garmin_to_coros.py +380 -0
- openathlete_mcp-0.1.0/tests/conftest.py +4 -0
- openathlete_mcp-0.1.0/tests/test_anchors.py +61 -0
- openathlete_mcp-0.1.0/tests/test_athlete_db.py +198 -0
- openathlete_mcp-0.1.0/tests/test_athlete_db_pipeline.py +273 -0
- openathlete_mcp-0.1.0/tests/test_canonical.py +345 -0
- openathlete_mcp-0.1.0/tests/test_cli_workout.py +39 -0
- openathlete_mcp-0.1.0/tests/test_config.py +112 -0
- openathlete_mcp-0.1.0/tests/test_coros_adapter.py +147 -0
- openathlete_mcp-0.1.0/tests/test_coros_official.py +123 -0
- openathlete_mcp-0.1.0/tests/test_coros_proxy.py +72 -0
- openathlete_mcp-0.1.0/tests/test_coros_proxy_stdio.py +167 -0
- openathlete_mcp-0.1.0/tests/test_db_cli.py +81 -0
- openathlete_mcp-0.1.0/tests/test_garmin_adapter.py +82 -0
- openathlete_mcp-0.1.0/tests/test_locks.py +73 -0
- openathlete_mcp-0.1.0/tests/test_mcp_server.py +246 -0
- openathlete_mcp-0.1.0/tests/test_mcp_startup.py +41 -0
- openathlete_mcp-0.1.0/tests/test_mcp_stdio.py +266 -0
- openathlete_mcp-0.1.0/tests/test_mcp_tools.py +266 -0
- openathlete_mcp-0.1.0/tests/test_memory.py +526 -0
- openathlete_mcp-0.1.0/tests/test_plugin_manifests.py +56 -0
- openathlete_mcp-0.1.0/tests/test_queries.py +133 -0
- openathlete_mcp-0.1.0/tests/test_release_metadata.py +47 -0
- openathlete_mcp-0.1.0/tests/test_sync_archive.py +72 -0
- openathlete_mcp-0.1.0/tests/test_sync_cli.py +106 -0
- openathlete_mcp-0.1.0/tests/test_sync_state.py +477 -0
- openathlete_mcp-0.1.0/tests/test_workout.py +600 -0
- openathlete_mcp-0.1.0/tests/test_workout_structure.py +274 -0
- openathlete_mcp-0.1.0/uninstall.py +221 -0
- openathlete_mcp-0.1.0/uv.lock +1492 -0
- openathlete_mcp-0.1.0/workout_structure.py +17 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"openathlete": {
|
|
4
|
+
"command": "uv",
|
|
5
|
+
"args": ["run", "./openathlete_mcp.py"],
|
|
6
|
+
"cwd": ".",
|
|
7
|
+
"required": true,
|
|
8
|
+
"startup_timeout_sec": 120
|
|
9
|
+
},
|
|
10
|
+
"coros": {
|
|
11
|
+
"command": "uv",
|
|
12
|
+
"args": ["run", "./coros_mcp_proxy.py"],
|
|
13
|
+
"cwd": "."
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openathlete",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "统一佳明/高驰运动数据的 AI 训练助手:跨会话记忆、训练建议、课表写回",
|
|
5
|
+
"skills": "./skills/",
|
|
6
|
+
"mcpServers": "./.codex-plugin/mcp.json",
|
|
7
|
+
"interface": {
|
|
8
|
+
"displayName": "OpenAthlete",
|
|
9
|
+
"shortDescription": "佳明/高驰运动数据统一 × AI 训练助手"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## 变更说明
|
|
2
|
+
|
|
3
|
+
<!-- 说明变更目的、主要内容和关联 Issue。 -->
|
|
4
|
+
|
|
5
|
+
## 验证
|
|
6
|
+
|
|
7
|
+
<!-- 填写实际执行的命令和结果;未执行的项目请说明原因。 -->
|
|
8
|
+
|
|
9
|
+
- [ ] 已运行 `uv run --locked --extra dev python -m pytest -q`,全部测试通过
|
|
10
|
+
- [ ] 行为、接口或开发流程变化已同步更新相关文档;无变化则说明不需要更新
|
|
11
|
+
- [ ] 未提交凭据、个人训练数据、本机私有路径或本机配置
|
|
12
|
+
- [ ] 涉及真实平台时仅做 `--dry-run` / `--limit` 小量验证,并已写明结果;不涉及则说明
|
|
13
|
+
|
|
14
|
+
合并前必须确认 `CI` 工作流的 `tests` 检查通过,不得以本地结果代替必需的 CI 检查。
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
pytest:
|
|
19
|
+
name: pytest (${{ matrix.os }}, Python ${{ matrix.python-version }})
|
|
20
|
+
runs-on: ${{ matrix.os }}
|
|
21
|
+
timeout-minutes: 20
|
|
22
|
+
strategy:
|
|
23
|
+
fail-fast: false
|
|
24
|
+
matrix:
|
|
25
|
+
os:
|
|
26
|
+
- ubuntu-latest
|
|
27
|
+
- windows-latest
|
|
28
|
+
python-version:
|
|
29
|
+
- "3.10"
|
|
30
|
+
- "3.12"
|
|
31
|
+
steps:
|
|
32
|
+
- name: Check out repository
|
|
33
|
+
uses: actions/checkout@v4
|
|
34
|
+
with:
|
|
35
|
+
persist-credentials: false
|
|
36
|
+
- name: Check out pinned upstream test dependency
|
|
37
|
+
uses: actions/checkout@v4
|
|
38
|
+
with:
|
|
39
|
+
repository: XiaoSiHwang/garmin-sync-coros
|
|
40
|
+
ref: 06cf52ef4ec5e6bec5300b81ebd75a0be8cdb949
|
|
41
|
+
path: upstream
|
|
42
|
+
persist-credentials: false
|
|
43
|
+
- name: Set up uv and Python
|
|
44
|
+
uses: astral-sh/setup-uv@v6
|
|
45
|
+
with:
|
|
46
|
+
python-version: ${{ matrix.python-version }}
|
|
47
|
+
enable-cache: true
|
|
48
|
+
- name: Run all tests
|
|
49
|
+
run: uv run --locked --extra dev python -m pytest -q
|
|
50
|
+
|
|
51
|
+
tests:
|
|
52
|
+
name: tests
|
|
53
|
+
if: always()
|
|
54
|
+
needs:
|
|
55
|
+
- pytest
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
steps:
|
|
58
|
+
- name: Check test matrix result
|
|
59
|
+
env:
|
|
60
|
+
PYTEST_RESULT: ${{ needs.pytest.result }}
|
|
61
|
+
run: |
|
|
62
|
+
if [ "$PYTEST_RESULT" != "success" ]; then
|
|
63
|
+
echo "Test matrix did not pass: $PYTEST_RESULT"
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Publish release to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v[0-9]+.[0-9]+.[0-9]+"
|
|
7
|
+
- "v[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
|
8
|
+
- "v[0-9]+.[0-9]+.[0-9]+[ab][0-9]+"
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- name: Check out repository
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
persist-credentials: false
|
|
21
|
+
- name: Set up uv and Python
|
|
22
|
+
uses: astral-sh/setup-uv@v6
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.12"
|
|
25
|
+
enable-cache: false
|
|
26
|
+
- name: Check release tag matches package version
|
|
27
|
+
env:
|
|
28
|
+
RELEASE_TAG: ${{ github.ref_name }}
|
|
29
|
+
run: test "$RELEASE_TAG" = "v$(uv version --short)"
|
|
30
|
+
- name: Build distributions
|
|
31
|
+
run: uv build
|
|
32
|
+
- name: Smoke test wheel
|
|
33
|
+
run: uv run --isolated --no-project --with dist/*.whl openathlete-mcp --help
|
|
34
|
+
- name: Smoke test source distribution
|
|
35
|
+
run: uv run --isolated --no-project --with dist/*.tar.gz openathlete-mcp --help
|
|
36
|
+
- name: Upload distributions
|
|
37
|
+
uses: actions/upload-artifact@v4
|
|
38
|
+
with:
|
|
39
|
+
name: dist
|
|
40
|
+
path: dist/
|
|
41
|
+
|
|
42
|
+
publish:
|
|
43
|
+
needs:
|
|
44
|
+
- build
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
environment:
|
|
47
|
+
name: pypi
|
|
48
|
+
permissions:
|
|
49
|
+
id-token: write
|
|
50
|
+
steps:
|
|
51
|
+
- name: Set up uv
|
|
52
|
+
uses: astral-sh/setup-uv@v6
|
|
53
|
+
with:
|
|
54
|
+
enable-cache: false
|
|
55
|
+
- name: Download distributions
|
|
56
|
+
uses: actions/download-artifact@v4
|
|
57
|
+
with:
|
|
58
|
+
name: dist
|
|
59
|
+
path: dist/
|
|
60
|
+
- name: Publish to PyPI
|
|
61
|
+
run: uv publish
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 凭据与本地数据,绝不提交
|
|
2
|
+
.env
|
|
3
|
+
data/
|
|
4
|
+
.venv/
|
|
5
|
+
__pycache__/
|
|
6
|
+
# 本地私有状态(含个人账号/训练信息)
|
|
7
|
+
STATUS.local.md
|
|
8
|
+
# 上游代码无 LICENSE,不可再分发;使用时自行 clone(见 AGENTS.md 运行手册)
|
|
9
|
+
upstream/
|
|
10
|
+
# 本机 MCP 配置(含本机绝对路径);对外示例请另建 .kimi-code/mcp.json.example
|
|
11
|
+
.kimi-code/
|
|
12
|
+
# 本地 scratch 记录(事件存档、临时分析),不入库
|
|
13
|
+
tmp/
|
|
14
|
+
# Python 构建、测试与覆盖率产物
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
htmlcov/
|
|
18
|
+
build/
|
|
19
|
+
dist/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
# 上游升级时生成的本机备份
|
|
22
|
+
upstream.bak.*/
|
|
23
|
+
# 探针原始响应可能含个人健康数据,只保留在本机
|
|
24
|
+
dev/last_*.json
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# AGENTS.md — OpenAthlete(运动数据统一 × AI 分析)
|
|
2
|
+
|
|
3
|
+
> 本文件是项目的工程事实源,也是面向 coding agent 的安装与使用说明书。
|
|
4
|
+
> **本文件随仓库公开发布**:禁止写入本地绝对路径、个人账号信息和个人训练数据;
|
|
5
|
+
> 私有状态写入 `STATUS.local.md`(已 gitignore)。本文只保留当前状态与长期结论,
|
|
6
|
+
> 决策和变更时间线由 git 历史承载,不在本文标注日期。
|
|
7
|
+
|
|
8
|
+
## 目标与交付形态
|
|
9
|
+
|
|
10
|
+
目标:统一佳明(Garmin)与高驰(COROS)的运动数据能力,让 coding agent 能分析
|
|
11
|
+
训练状态、规划训练。**产品不是同步工具,而是 agent 可用的运动数据能力。**
|
|
12
|
+
|
|
13
|
+
在此之上构建通用训练助手:跨会话记忆、训练建议与赛事战术、自动排课、每日推送和
|
|
14
|
+
可定制教练人格。数据层独立、多运行时共用:`~/.openathlete/`(SQLite + FIT 归档 +
|
|
15
|
+
Markdown 记忆)是核心资产,Kimi Code 插件与 OpenClaw 只是渠道。
|
|
16
|
+
**产品与架构契约见 [DESIGN.md](DESIGN.md),方案变更先改 DESIGN 再动代码。**
|
|
17
|
+
|
|
18
|
+
交付形态:
|
|
19
|
+
|
|
20
|
+
- **Kimi Code 用户**:GitHub 分发的 Plugin(Skill + 教练人格 + PEP 723 Python 脚本)。
|
|
21
|
+
- **Codex 用户**:`.agents` marketplace + `.codex-plugin` 原生薄适配,复用同一 Skill、
|
|
22
|
+
教练人格与 PEP 723 脚本,也可继续直接配置 stdio MCP。
|
|
23
|
+
- **其他 coding agent**:仓库 + 本文件即通用交付物;另提供 stdio MCP server,
|
|
24
|
+
通过 PyPI / `uvx openathlete-mcp` 接入各客户端。
|
|
25
|
+
- 发布名 OpenAthlete;仓库和 Plugin id 为 `openathlete`,PyPI/MCP 为
|
|
26
|
+
`openathlete-mcp`,MCP registry 为 `io.github.<用户名>/openathlete`。
|
|
27
|
+
|
|
28
|
+
### 开发与自用原则
|
|
29
|
+
|
|
30
|
+
1. **只交付源码,不出二进制**;所有失败路径给出结构化、可执行的下一步建议。
|
|
31
|
+
2. 日志写入 `~/.openathlete/logs/`;doctor 自检与真实排错案例持续补入本文件和 SKILL。
|
|
32
|
+
3. 真实账号验证一律用 `--dry-run` / `--limit` 小量运行,不做破坏性测试。
|
|
33
|
+
4. 公开文件保持脱敏;私有路径、账号区服和同步进度只写 `STATUS.local.md`。
|
|
34
|
+
5. 过渡期必须兼容 `.env`、`data/sync.db`、代理 token 和旧目录约定;
|
|
35
|
+
新状态统一写入 `~/.openathlete/`。
|
|
36
|
+
6. 自用验证应安装插件后在仓库外目录进行:改代码 → 重装 → `/reload` → 仓库外验证。
|
|
37
|
+
Windows 重装若报 EBUSY,先关闭相关会话或终止命令行含 `coros_mcp_proxy` 的进程。
|
|
38
|
+
|
|
39
|
+
## 当前状态速览
|
|
40
|
+
|
|
41
|
+
已完成并验证:
|
|
42
|
+
|
|
43
|
+
- **佳明→高驰活动同步**:`sync_garmin_to_coros.py` 已通过真实账号端到端与历史全量验证。
|
|
44
|
+
- **高驰官方 MCP**:经 `coros_mcp_proxy.py` 接入,33 个工具可用并通过 stdio 测试;
|
|
45
|
+
代理修复官方写工具 schema 导致的 Moonshot API 400(见“已知坑”第 9 条)。
|
|
46
|
+
首轮静态声明与后台初始化已通过 Codex 默认 optional 配置的真实 `queryDevices` 调用。
|
|
47
|
+
- **v0 插件骨架**:`kimi.plugin.json`、双层教练人格、会话入口 skill;
|
|
48
|
+
入口脚本支持 PEP 723 / `uv run`,状态迁至 `~/.openathlete/` 并自动迁移旧布局。
|
|
49
|
+
- **Codex 原生薄适配**:隔离环境的 marketplace/plugin 安装、skill 发现、双 MCP 注册和
|
|
50
|
+
统一九工具 stdio 已验证;OpenAthlete 使用 `required=true` 与 120 秒启动超时,
|
|
51
|
+
`readOnlyHint` 与真实 `read_memory` 调用也已通过。optional server 的首轮目录宽限见
|
|
52
|
+
已知坑第 15 条,不以 initialize/tools/list 冒充真实模型调用。
|
|
53
|
+
- **openathlete-mcp v0**:9 个统一工具已实现——训练看板、活动、负荷、恢复、
|
|
54
|
+
手动同步、当前计划、记忆读写与单次课表;COROS 官方 MCP 与 Garmin API 双平台
|
|
55
|
+
写回复用独立 adapter,不嵌套本地代理。读时同步使用分 stream 持久队列与连续覆盖,
|
|
56
|
+
记忆支持白名单 list/模板/legacy。静态声明与共享后台初始化分离,超时/失败分别返回
|
|
57
|
+
`STARTING` / `STARTUP_FAILED`。真实 stdio client 验证九工具与四种本地入口,
|
|
58
|
+
本地 wheel 可离线 `uvx` 启动;所有测试在 Python 3.12 与 3.10 均全绿,
|
|
59
|
+
Windows 与 WSL POSIX 文件锁分支已实测。**统一通道尚未做真实账号同步或写回验证**,
|
|
60
|
+
不以 mock 替代真实账号结论。
|
|
61
|
+
- **athlete.db 与 src 数据层 v0.1**:规范裸 FIT 归档入库、跑/骑结构解析、
|
|
62
|
+
哈希与主平台驱动的确定性 canonical、multisport 段级同一性、完整快照阈值锚点、
|
|
63
|
+
Edwards TRIMP、7 细区间分布、daily_metrics 与 daily_load;rebuild 保留非派生数据。
|
|
64
|
+
骑行按有效功率或可配置距离阈值分派结构解析(默认 15km,首次候选需用户确认)。
|
|
65
|
+
高驰 Training Hub adapter、统一 CLI、真实全量归档与 PMC 重算已验证。
|
|
66
|
+
根目录 `athlete_db.py` / `workout_structure.py` 保留为兼容 shim。
|
|
67
|
+
- **佳明 adapter 只读验证**:当前阈值与日度健康/睡眠字段已真实读取成功;
|
|
68
|
+
自动定时抓取、全历史健康回填和自动锚点写入仍未产品化。
|
|
69
|
+
- **佳明课表写回**:单段时间课、重复组距离课与指定日期排期均已真实创建并读回验证;
|
|
70
|
+
`workout.py` 已沉淀统一处方→Garmin mapper 与创建/排期服务(run/ride),
|
|
71
|
+
CLI 支持 dry-run;统一 `create_workout` 复用该服务,不为佳明嵌套另一个 MCP。
|
|
72
|
+
|
|
73
|
+
待做:doctor、每日定时抓取、PyPI 与 MCP registry、多周计划、Garmin 自动活动/健康抓取、
|
|
74
|
+
高驰→佳明同步、游泳趟级解析、record 流分段和 Zwift adapter。
|
|
75
|
+
|
|
76
|
+
## 目录结构与维护约定
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
./
|
|
80
|
+
├── README.md / LICENSE # 项目说明 / MIT 许可证
|
|
81
|
+
├── AGENTS.md / DESIGN.md # 工程说明 / 产品设计契约
|
|
82
|
+
├── CONTRIBUTING.md # 开发、测试、PR 与 main 保护规范
|
|
83
|
+
├── STATUS.local.md # 私有状态,gitignore
|
|
84
|
+
├── kimi.plugin.json # Kimi Plugin 清单
|
|
85
|
+
├── .github/workflows/ci.yml # 跨系统、跨 Python 版本全量测试 CI
|
|
86
|
+
├── .github/workflows/release.yml # 标签触发的 PyPI Trusted Publishing
|
|
87
|
+
├── .github/pull_request_template.md # PR 验证检查清单
|
|
88
|
+
├── .agents/plugins/marketplace.json # Codex marketplace 清单
|
|
89
|
+
├── .codex-plugin/plugin.json # Codex 原生插件清单
|
|
90
|
+
├── .codex-plugin/mcp.json # Codex 双 MCP 与 required/启动超时配置
|
|
91
|
+
├── .mcp.json # 其他宿主复用的通用双 MCP 配置
|
|
92
|
+
├── coach-core.md # 核心教练人格,随插件分发
|
|
93
|
+
├── coach-user.default.md # 用户人格模板
|
|
94
|
+
├── skills/openathlete/ # 会话入口、配置向导与排错
|
|
95
|
+
├── sync_garmin_to_coros.py # 佳明→高驰同步入口
|
|
96
|
+
├── coros_mcp_proxy.py # 高驰官方 MCP 的 stdio schema 修复代理(独立 L2)
|
|
97
|
+
├── coros_tools.json # COROS 首轮静态工具声明;后台刷新运行时缓存
|
|
98
|
+
├── openathlete_mcp.py # 统一九工具 stdio MCP 的 PEP 723 入口
|
|
99
|
+
├── fit_laps.py # 从原始 FIT 还原真实课型结构
|
|
100
|
+
├── athlete_db.py # 兼容 shim,实现位于 src/openathlete/db/
|
|
101
|
+
├── workout_structure.py # 兼容 shim,实现位于 src/openathlete/
|
|
102
|
+
├── migrate.py / uninstall.py # 数据移植 / 卸载清理
|
|
103
|
+
├── pyproject.toml # src layout,包名 openathlete-mcp
|
|
104
|
+
├── server.json # 官方 MCP Registry 元数据
|
|
105
|
+
├── src/openathlete/
|
|
106
|
+
│ ├── home.py / config.py # HOME 与非密配置(含时区/同步起点/写回平台)
|
|
107
|
+
│ ├── credentials.py # 凭据读取
|
|
108
|
+
│ ├── structure.py # 课型结构解析共享核心
|
|
109
|
+
│ ├── workout.py # 双平台处方、预览确认与 Garmin API 服务
|
|
110
|
+
│ ├── queries.py # canonical 活动/负荷/恢复/计划本地查询
|
|
111
|
+
│ ├── sync_state.py # 分 stream 同步、连续覆盖、父/段队列
|
|
112
|
+
│ ├── memory.py # 白名单记忆、front matter、legacy 与原子写
|
|
113
|
+
│ ├── locks.py # Windows/POSIX 文件锁与进程内锁
|
|
114
|
+
│ ├── mcp/ # 低层 SDK server、schema、envelope、dispatch
|
|
115
|
+
│ ├── db/athlete_db.py # athlete.db schema / ingest / anchor / stats / load
|
|
116
|
+
│ ├── adapters/ # coros web/官方写 client;garmin 阈值/健康/课表 API
|
|
117
|
+
│ └── cli.py # 统一 CLI(无参数或 serve 启动 MCP)
|
|
118
|
+
├── tests/ # pytest 合成数据测试
|
|
119
|
+
├── dev/ # 探针、响应样本与解析基准,不入产品路径
|
|
120
|
+
├── upstream/ # XiaoSiHwang/garmin-sync-coros,只 import 不修改
|
|
121
|
+
└── data/ # 旧运行时布局,仅作迁移源与日志残留
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
维护约定:
|
|
125
|
+
|
|
126
|
+
- 所有变更从最新 `main` 新建分支并通过 PR 合并;禁止直推、强推或删除 `main`。
|
|
127
|
+
`CI` 的 `tests` 汇总检查必须全绿,完整流程与仓库保护规则见 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
|
128
|
+
- 新功能优先进 `src/openathlete/`;根目录入口保持可用,逐步变薄 shim。
|
|
129
|
+
- 新模块必须配必要的 pytest 测试;提交前 `uv run --locked --extra dev python -m pytest -q` 全绿。
|
|
130
|
+
解析/契约逻辑与 IO 分离,纯函数优先,阈值常量化并注明出处。
|
|
131
|
+
- `.env` 与凭据文件由用户自己填写,Agent 不读取;模板见 `.env.example`。
|
|
132
|
+
- `.kimi-code/mcp.json` 含本机配置,不直接提交;官方直连留档不代替当前代理方案。
|
|
133
|
+
|
|
134
|
+
## 核心调研结论(已沉淀,勿重复调研)
|
|
135
|
+
|
|
136
|
+
### 健康数据
|
|
137
|
+
|
|
138
|
+
- 两平台都只接受活动文件导入,**均不提供睡眠/HRV/步数等健康数据写入接口**;
|
|
139
|
+
健康数据只能两端读出、本地汇总。Body Battery 没有高驰等价字段。
|
|
140
|
+
- 佳明只读使用 garth / python-garminconnect;高驰只读可使用官方 MCP。
|
|
141
|
+
- 佳明可写单次结构化课表并排期,但不能创建多周计划实体;高驰 MCP 可建 4–16 周计划。
|
|
142
|
+
因而跨平台计划统一抽象为一组按日期排期的 workout。
|
|
143
|
+
- 活动同步以 XiaoSiHwang/garmin-sync-coros 为基础;其他同步工具也只解决活动,
|
|
144
|
+
不改变上述健康数据边界。
|
|
145
|
+
|
|
146
|
+
### 阈值锚点数据源
|
|
147
|
+
|
|
148
|
+
- 佳明可直读跑步 LT2 心率/阈值速度、按运动类型的心率区间、骑行 FTP 和静息心率;
|
|
149
|
+
跑步 LT 历史区间也可读取。阈值速度的换算为 **阈值配速 s/km = 100 / speed**。
|
|
150
|
+
- 佳明没有 LT1 直接 API,需手填或估算;高驰可读取 lthr/ltsp,maxHR 缺口需手填。
|
|
151
|
+
- 端点探针见 `dev/query_garmin_thresholds.py`;原始响应写入本机忽略的
|
|
152
|
+
`dev/last_*.json`,不随仓库发布,也不在本文件重复维护 API 参数清单。
|
|
153
|
+
|
|
154
|
+
### 原生/导入活动来源判别
|
|
155
|
+
|
|
156
|
+
- 目前没有已确认、可跨平台通用的“手表原生 vs 上传导入”读取标记。
|
|
157
|
+
设备字段、`isOriginal` 和 lap 展示差异只能作启发式告警,不能当稳定契约。
|
|
158
|
+
- FIT `manufacturer/product` 只说明文件创建设备,不说明摄入渠道。当前实测支持
|
|
159
|
+
原始 FIT 跨平台上传后仍保留原字节/字段,因此字节一致副本不存在分析差别。
|
|
160
|
+
- 设计约定:副本保存 `fit_sha256`;字节一致时固定顺序选 canonical,内容分叉时按
|
|
161
|
+
用户声明的 `primary_platform` 优先。双平台初始化必须询问主表原生平台(DESIGN §3)。
|
|
162
|
+
|
|
163
|
+
### EvoLab 与导入数据
|
|
164
|
+
|
|
165
|
+
- EvoLab 核心指标基于 TRIMP,可从导入 FIT 重算,但只消费最近 42 天;
|
|
166
|
+
恢复指标不依赖睡眠/HRV,缺失健康数据主要影响独立健康监测层。
|
|
167
|
+
- 跑步功率、步频和步态等标准 FIT 字段可随原始文件导入;云端衍生评分不会迁移。
|
|
168
|
+
- 中转必须使用原始 FIT,第三方平台改写可能丢失步态等字段。
|
|
169
|
+
|
|
170
|
+
### 高驰官方 MCP
|
|
171
|
+
|
|
172
|
+
- 国区 `https://mcpcn.coros.com/mcp`,国际 `https://mcp.coros.com/mcp`;
|
|
173
|
+
Streamable HTTP + OAuth2,个人免费。
|
|
174
|
+
- 33 个工具覆盖活动、EvoLab、健康只读与训练写入;FIT 下载限 50 个/24h,
|
|
175
|
+
无删除操作,排期范围为今天起 90 天。
|
|
176
|
+
- 官方写工具 schema 会触发 Moonshot API 400,当前必须经本地代理接入;
|
|
177
|
+
MCP 配置变更需 `/reload` 或新会话生效。
|
|
178
|
+
|
|
179
|
+
### 高驰 Training Hub web API
|
|
180
|
+
|
|
181
|
+
- Partner API 不面向个人发凭据;数据层抓取使用非官方 Training Hub web API,
|
|
182
|
+
不经官方 MCP,因此不受其 FIT 下载限额约束。
|
|
183
|
+
- 登录 token 缓存 24h,响应 regionId 决定区域 API 地址;活动列表、详情、FIT 下载、
|
|
184
|
+
日度指标和体能评估的实现见 `src/openathlete/adapters/coros.py`。
|
|
185
|
+
- 睡眠分期需要单独 mobile token,且会踢掉手机 App,**不碰**;睡眠数据走官方 MCP。
|
|
186
|
+
|
|
187
|
+
## 已知坑 / 操作注意
|
|
188
|
+
|
|
189
|
+
1. **高驰单点登录**:同步或 API 登录期间不要打开高驰 App/网页,避免互踢。
|
|
190
|
+
2. **佳明 MFA**:garth 不支持两步验证,需先关闭佳明账号 MFA。
|
|
191
|
+
3. **凭据保护**:Agent 不读写 `.env` 或凭据文件,只引导用户自己填写,不让用户贴密码。
|
|
192
|
+
4. **Windows 中文输出**:入口脚本统一做 UTF-8 reconfigure,新脚本复用该处理。
|
|
193
|
+
5. **OSS 中转**:不需要用户云账号;STS 凭据由高驰签发,文件进入高驰自己的 bucket。
|
|
194
|
+
6. **小文件导入**:高驰 API 没有网页导入器的 20KB 下限。
|
|
195
|
+
7. **同步续传**:进度存 `~/.openathlete/sync.db`;连续失败 3 次会中止,重跑可续传;
|
|
196
|
+
状态 2 的异常条目会跳过,需删 db 后重试。
|
|
197
|
+
8. **上游变动**:非官方接口可能失效;佳明对数据中心 IP 有 429 限流。
|
|
198
|
+
9. **高驰 MCP × Moonshot 400**:官方写工具的嵌套 `$defs` 引用不被接受,
|
|
199
|
+
`coros_mcp_proxy.py` 将其提升到根部。排障用 `--selftest`,token 失效用 `--login`,
|
|
200
|
+
日志在 `~/.openathlete/logs/coros-mcp-proxy.log`。
|
|
201
|
+
10. **全历史同步**:`sync_garmin_to_coros.py` 不带 `--since` 会注册全部历史并从最老开始;
|
|
202
|
+
只想同步近期必须显式带 `--since YYYY-MM-DD`。
|
|
203
|
+
11. **同步判重**:上传前按高驰活动开始时间 ±120s 做服务端判重,失败时退回本地 db;
|
|
204
|
+
`--no-server-dedup` 可关闭。高驰不能批量删除,重复只能手动处理;
|
|
205
|
+
长期方案是 DESIGN §3 的活动同一性规则。
|
|
206
|
+
12. **高驰 STS 旧端点已关闭**:国区修复要求 upstream 包含 06cf52e,并传入登录
|
|
207
|
+
accessToken;国际区同类问题在上游尚未完整修复,不能照搬国区结论。
|
|
208
|
+
13. **导入活动 lap 失真**:高驰 lap 接口对导入活动通常只给自动距离圈,不能据此判课型;
|
|
209
|
+
用 `fit_laps.py` 解析原始 FIT。不要通过伪造设备 manufacturer/product 冒充原生记录。
|
|
210
|
+
14. **Windows 本地化时区名**:`datetime.astimezone()` 可能返回“中国标准时间”等显示名,
|
|
211
|
+
不是 IANA key。缺省系统时区统一经 `tzlocal` 解析并验证;显式配置仍只接受 IANA,
|
|
212
|
+
不做中文名硬编码映射,也不在错误 envelope 中再次调用会失败的解析路径。
|
|
213
|
+
15. **Codex optional MCP 首轮宽限**:optional server 的首轮目录捕获默认只有 1 秒宽限,
|
|
214
|
+
`startup_timeout_sec` 只限制单个 server 启动/初始 tools/list,不延长该宽限;
|
|
215
|
+
`mcp_optional_startup_grace_ms=0` 改为等待各自启动超时,`required=true` 还会把
|
|
216
|
+
初始化失败提升为会话失败。OpenAthlete 与 COROS 代理都使用静态声明与共享后台初始化:
|
|
217
|
+
COROS 首轮从 `coros_tools.json` 或较新的运行时缓存取声明,官方连接与刷新在后台完成,
|
|
218
|
+
过早调用返回 `STARTING`,失败返回 `STARTUP_FAILED`。OpenAthlete 在 Codex 专用配置为
|
|
219
|
+
`required=true` + 120 秒启动超时,COROS 保持 optional;`notifications/tools/list_changed`
|
|
220
|
+
不保证刷新模型目录。宽限参数只作旧版本/手动配置兜底,验收看真实 tools/call,
|
|
221
|
+
不得用 resources/read 冒充。
|
|
222
|
+
16. **Kimi 网页安装 504**:设备隧道超时后仍可能安装成功;先核对已安装列表、managed
|
|
223
|
+
manifest/SKILL 与 selftest,确认未安装或版本未更新才重试,避免重复安装。
|
|
224
|
+
|
|
225
|
+
## 运行手册(Runbook)
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# 开发用上游库;插件用户的钉定版本安装见 skills/openathlete/SKILL.md
|
|
229
|
+
git clone --depth 1 https://github.com/XiaoSiHwang/garmin-sync-coros upstream
|
|
230
|
+
|
|
231
|
+
# 开发环境(Windows 使用 .venv\Scripts\activate)
|
|
232
|
+
python -m venv .venv
|
|
233
|
+
source .venv/bin/activate
|
|
234
|
+
pip install -r requirements.txt
|
|
235
|
+
pip install -e .
|
|
236
|
+
|
|
237
|
+
# 佳明→高驰同步:先演练、再小量,日常增量必须带 --since
|
|
238
|
+
uv run ./sync_garmin_to_coros.py --limit 3 --dry-run
|
|
239
|
+
uv run ./sync_garmin_to_coros.py --limit 3
|
|
240
|
+
uv run ./sync_garmin_to_coros.py --since YYYY-MM-DD
|
|
241
|
+
|
|
242
|
+
# 高驰 MCP 代理
|
|
243
|
+
uv run ./coros_mcp_proxy.py --selftest
|
|
244
|
+
uv run ./coros_mcp_proxy.py --login
|
|
245
|
+
uv run ./coros_mcp_proxy.py --import-kimi-tokens
|
|
246
|
+
|
|
247
|
+
# 统一 MCP(无参数与 serve 等价;启动/list 不触网)
|
|
248
|
+
uv run ./openathlete_mcp.py
|
|
249
|
+
openathlete-mcp serve
|
|
250
|
+
openathlete-mcp
|
|
251
|
+
|
|
252
|
+
# 数据层与首配(workout-platform 是写回平台,primary-platform 是 canonical 选源)
|
|
253
|
+
openathlete-mcp coros login-check
|
|
254
|
+
openathlete-mcp db init
|
|
255
|
+
openathlete-mcp db config set --timezone Asia/Shanghai \
|
|
256
|
+
--activity-since YYYY-MM-DD --workout-platform garmin --primary-platform garmin
|
|
257
|
+
openathlete-mcp db config set --ride-training-min-km 15
|
|
258
|
+
|
|
259
|
+
# COROS 增量/历史回补:同一持久状态机;dry-run/limit 不扩展连续覆盖
|
|
260
|
+
openathlete-mcp coros pull --since YYYY-MM-DD --limit 5 --dry-run
|
|
261
|
+
# 不带 limit 的完整连续窗口全部入库后才扩展覆盖
|
|
262
|
+
openathlete-mcp coros pull --since YYYY-MM-DD --until YYYY-MM-DD
|
|
263
|
+
openathlete-mcp coros metrics --days 28
|
|
264
|
+
openathlete-mcp db ingest
|
|
265
|
+
openathlete-mcp db rebuild-load
|
|
266
|
+
openathlete-mcp db stats
|
|
267
|
+
|
|
268
|
+
# 佳明课表(统一 workout JSON;dry-run 不触网)
|
|
269
|
+
openathlete-mcp garmin workout create --file WORKOUT.json --dry-run
|
|
270
|
+
openathlete-mcp garmin workout create --file WORKOUT.json --date YYYY-MM-DD
|
|
271
|
+
openathlete-mcp garmin workout schedule --id WORKOUT_ID --date YYYY-MM-DD
|
|
272
|
+
|
|
273
|
+
# 测试(与 CI 相同,运行完整测试集)
|
|
274
|
+
uv run --locked --extra dev python -m pytest -q
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### PyPI 与 MCP Registry 发布
|
|
278
|
+
|
|
279
|
+
发布配置不等于发布完成。首次发布前,在 PyPI 配置 pending trusted publisher:项目
|
|
280
|
+
`openathlete-mcp`、仓库 `whz14/openathlete`、workflow `release.yml`、environment
|
|
281
|
+
`pypi`;并在 GitHub 创建同名 environment。Pending publisher 不预留包名。
|
|
282
|
+
|
|
283
|
+
每次发布先通过 PR 同步 `pyproject.toml`、`src/openathlete/__init__.py` 与
|
|
284
|
+
`server.json` 的版本并全量测试。标签必须等于包版本前加 `v`;标签 workflow 校验
|
|
285
|
+
版本、构建并冒烟后,才由 `pypi` environment 通过 Trusted Publishing 上传。
|
|
286
|
+
必须等 PyPI 包可见后再发布同版本 MCP 元数据,不能反序。
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
# 本地发布检查;重新构建,不上传旧 dist
|
|
290
|
+
uv run --locked --extra dev python -m pytest -q
|
|
291
|
+
uv build --clear
|
|
292
|
+
uvx --from dist/openathlete_mcp-X.Y.Z-py3-none-any.whl openathlete-mcp --help
|
|
293
|
+
|
|
294
|
+
# main 上已通过 CI 的发布提交;X.Y.Z 替换为实际版本
|
|
295
|
+
git tag -a vX.Y.Z -m vX.Y.Z
|
|
296
|
+
git push origin vX.Y.Z
|
|
297
|
+
|
|
298
|
+
# PyPI 页面可见后验证安装,再发布 MCP Registry
|
|
299
|
+
uvx --refresh-package openathlete-mcp openathlete-mcp --help
|
|
300
|
+
mcp-publisher login github
|
|
301
|
+
mcp-publisher publish
|
|
302
|
+
curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=openathlete"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## 路线图与依赖
|
|
306
|
+
|
|
307
|
+
- **阶段一(已完成)**:同步脚本、高驰 MCP 配置和基础调研。
|
|
308
|
+
- **阶段二**:补齐 doctor、运行日志和排错知识,完善仓库即交付物。
|
|
309
|
+
- **阶段三**:完善 Kimi Plugin 命令并发布;骨架、状态外移和 PEP 723 已完成。
|
|
310
|
+
- **阶段四(本地已实现)**:stdio MCP 九工具、读时同步 TTL、分 stream 队列与连续覆盖、
|
|
311
|
+
双平台单次课表写回已落地;长任务按约 20 条分批,使用 sync.db 续传并返回剩余量。
|
|
312
|
+
四种本地入口与离线 wheel 已验证;PyPI 与 MCP registry 发布仍待做。
|
|
313
|
+
- **阶段五**:完善训练助手、每日晨报、课表写回和可选 OpenClaw 渠道。
|
|
314
|
+
|
|
315
|
+
用户侧前置条件统一为 **uv**。当前脚本使用 PEP 723 + `uv run`;MCP 发布后切换
|
|
316
|
+
`uvx openathlete-mcp`。不采用 PyInstaller 单文件 exe,因为二进制违背 agent 可见、可修原则。
|
|
317
|
+
|
|
318
|
+
## 许可证与合规
|
|
319
|
+
|
|
320
|
+
- `upstream/` 无正式 LICENSE,作者在
|
|
321
|
+
[issue #43](https://github.com/XiaoSiHwang/garmin-sync-coros/issues/43)
|
|
322
|
+
公开允许使用;其佳明模块来自 running_page(MIT),收编时需补 MIT 署名。
|
|
323
|
+
- upstream 不随仓库分发,配置向导下载钉定版本;不使用 git submodule,
|
|
324
|
+
因为插件的 codeload zip 不会包含 submodule 内容。
|
|
325
|
+
- README 必须告知:非官方接口有风控/限流和失效风险,高驰存在单点登录互踢。
|
|
326
|
+
- 凭据、数据库和 FIT 均留在本地;安装/更新会访问代码与依赖下载源,平台查询和同步
|
|
327
|
+
会访问对应 API,云端大模型对话会带出随上下文发送的分析数据,README 需明确说明。
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# 参与开发
|
|
2
|
+
|
|
3
|
+
本文规定 OpenAthlete 的日常开发流程。项目结构、运行手册和产品设计契约分别见
|
|
4
|
+
[AGENTS.md](AGENTS.md) 与 [DESIGN.md](DESIGN.md)。
|
|
5
|
+
|
|
6
|
+
## 开发环境
|
|
7
|
+
|
|
8
|
+
前置条件是 Git 和 [uv](https://docs.astral.sh/uv/);Python 由 uv 按项目版本管理。
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
git clone https://github.com/whz14/openathlete.git
|
|
12
|
+
cd openathlete
|
|
13
|
+
uv sync --locked --extra dev
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
同步客户端等额外开发依赖及完整命令见 `AGENTS.md`。不得修改或重新分发
|
|
17
|
+
`upstream/`;真实账号验证必须使用 `--dry-run` / `--limit` 小量运行。
|
|
18
|
+
|
|
19
|
+
## 分支与提交
|
|
20
|
+
|
|
21
|
+
1. 从最新 `main` 创建短生命周期分支,分支名使用 `<类型>/<简述>`,例如
|
|
22
|
+
`feat/recovery-view`、`fix/timezone` 或 `chore/ci`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git switch main
|
|
26
|
+
git pull --ff-only origin main
|
|
27
|
+
git switch -c feat/short-description
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. **所有变更必须通过 Pull Request 合并到 `main`,包括维护者自己的变更。**
|
|
31
|
+
禁止直接推送、强制推送或删除 `main`,不得使用管理员权限绕过保护规则。
|
|
32
|
+
3. 一个 PR 聚焦一个目标,提交信息说明实际改动。不要混入格式化、重命名等无关变更。
|
|
33
|
+
4. 新功能优先进 `src/openathlete/`,新模块必须配必要的 pytest 测试;
|
|
34
|
+
解析与契约逻辑尽量和 IO 分离。
|
|
35
|
+
5. 依赖变化必须同时更新 `pyproject.toml` 与 `uv.lock`,不得为绕过失败而删除测试、
|
|
36
|
+
缩小测试范围或放宽断言。
|
|
37
|
+
|
|
38
|
+
## 测试与文档
|
|
39
|
+
|
|
40
|
+
提交 PR 前运行完整测试,不能只运行与改动相关的子集:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv run --locked --extra dev python -m pytest -q
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`CI` 工作流会在 Ubuntu、Windows × Python 3.10、3.12 下运行同一完整测试命令;
|
|
47
|
+
它先按既定提交 `06cf52e` 检出测试所需但未入库的 `upstream/`,不依赖本机文件。
|
|
48
|
+
四个矩阵任务不会因其中一个失败而提前取消;稳定的 `tests` 汇总任务只有在整个矩阵
|
|
49
|
+
成功时才通过。定向测试可用于开发调试,但不能替代提交前的全量验证。
|
|
50
|
+
|
|
51
|
+
行为、接口、安装方式或开发流程变化必须同步文档;产品或架构方案变化先更新
|
|
52
|
+
`DESIGN.md`,工程事实与运行手册变化更新 `AGENTS.md`。
|
|
53
|
+
|
|
54
|
+
## Pull Request 与合并
|
|
55
|
+
|
|
56
|
+
1. 推送功能分支并创建目标为 `main` 的 PR,填写模板中的变更说明和真实验证结果。
|
|
57
|
+
2. 处理审查意见,保持分支与最新 `main` 同步且无冲突。
|
|
58
|
+
3. 只有 `CI` 工作流的 **`tests` 检查成功**后才能合并。不得将本地测试、
|
|
59
|
+
其他 job 成功或失败的非必需检查当作替代,也不得跳过红色检查。
|
|
60
|
+
4. 当前不强制审批人数;协作人数允许时可再启用至少一人审查,不能要求作者批准自己的 PR。
|
|
61
|
+
|
|
62
|
+
## `main` 保护规则(仓库管理员)
|
|
63
|
+
|
|
64
|
+
文档约定必须由 GitHub 仓库设置落实。进入 **Settings → Rules → Rulesets → New branch
|
|
65
|
+
ruleset**,创建名为 `protect-main` 的规则集并设为 **Active**:
|
|
66
|
+
|
|
67
|
+
- **Target branches**:默认分支,或显式包含 `refs/heads/main`。
|
|
68
|
+
- **Bypass list**:为空,不给维护者或应用额外绕过权限。
|
|
69
|
+
- 启用 **Require a pull request before merging**,所需审批数保持为 0。
|
|
70
|
+
- 启用 **Require status checks to pass**,选择 `tests`,并要求分支在合并前保持最新。
|
|
71
|
+
- 启用 **Block force pushes** 和 **Restrict deletions**。
|
|
72
|
+
|
|
73
|
+
首次配置时若还搜索不到 `tests`,先保存其余规则,在首个 PR 触发 CI 后立即将它补为
|
|
74
|
+
必需检查;补齐前不得合并该 PR。规则集不是 Active 或缺少 `tests` 时,不能声称已强制
|
|
75
|
+
执行上述流程。
|
|
76
|
+
|
|
77
|
+
## 凭据与私人数据
|
|
78
|
+
|
|
79
|
+
- 不提交 `.env`、credentials、token、数据库、FIT、训练记忆、`STATUS.local.md`、
|
|
80
|
+
本机绝对路径或本机 MCP 配置;也不要在 PR 描述和日志中贴出这些内容。
|
|
81
|
+
- 不让 CI 访问真实佳明或高驰账号。测试使用合成数据和 mock,真实账号结论必须单独
|
|
82
|
+
标明验证范围,不能以 mock 代替。
|
|
83
|
+
- 调试日志和临时响应若可能包含个人数据,只保留在本机已忽略的路径。
|