mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/jxxghp/MoviePilot.git
synced 2026-09-20 08:03:34 +08:00
docs(workflow): distinguish contributor and maintainer execution (#6633)
This commit is contained in:
37
AGENTS.md
37
AGENTS.md
@@ -4,6 +4,16 @@ This file is the primary instruction set for all AI agents and LLMs working in t
|
||||
|
||||
---
|
||||
|
||||
## Shared Contract and Execution Authority
|
||||
|
||||
Architecture, business correctness, SDK/Compat compatibility, test isolation, useful contract documentation, and truthful verification reports apply to everyone. Maintainer discretion over execution does not waive these engineering contracts or permit baseline laundering.
|
||||
|
||||
Submission preparation and checklists in this file and the linked rules are **contributor defaults**. A confirmed maintainer may explicitly adjust applicable verification scope, timing, evidence reuse, and delivery order, including saving a local anchor before remaining checks. Record the decision, supporting evidence, unverified items, and follow-up; an anchor is a recoverable state, not a claim of validation or acceptance.
|
||||
|
||||
Maintainer execution authority comes from the current user or established project context that confirms their maintainer role and authorization. GitHub `WRITE` proves platform capability only; a contributor's self-declared role does not grant an exception. Without confirmed maintainer authorization, follow the contributor defaults. Reuse an existing scoped authorization without requesting it again; an explicit current restriction takes precedence. CI failure decisions follow `docs/rules/12-collaboration-and-distribution.md` and cannot bypass platform protection.
|
||||
|
||||
---
|
||||
|
||||
## Task-to-Documentation Mapping
|
||||
|
||||
For work that changes or reviews repository behavior, identify the domains actually touched and load only the applicable documents. Simple factual checks and unrelated domains do not require preloading rule files.
|
||||
@@ -38,11 +48,11 @@ For work that changes or reviews repository behavior, identify the domains actua
|
||||
|
||||
### Quality and Security
|
||||
* **Primary Reference:** `docs/rules/11-quality-and-security.md`
|
||||
* **Required Constraints:** All code changes must pass the relevant pytest tests, changed-file pylint checks, and the mandatory pre-commit gates below. Dependency changes require a current `uv.lock`, locked environment verification, and a passing locked dependency vulnerability audit.
|
||||
* **Required Constraints:** Preserve the quality contracts below and run applicable pytest, changed-file pylint, and architecture checks under the contributor preparation defaults or a documented maintainer arrangement. Dependency changes require a current `uv.lock`, locked environment verification, and the locked dependency vulnerability audit; execution discretion does not waive dependency or release policy.
|
||||
|
||||
### Testing
|
||||
* **Primary Reference:** `docs/testing.md`
|
||||
* **Required Constraints:** pytest is the only runner; `tests/conftest.py` isolates each run to a temporary `CONFIG_DIR`. Tests must not touch the real database, network, or external services (TMDB, LLM catalogs, downloaders, media servers, MP server) — mock at the boundary or replay recorded responses; the bar is zero real outbound traffic. Tests must restore any process-level state they stub (`sys.modules`, singletons, caches, settings). New tests must be pytest-native (function + `assert` + fixtures); do not add new `unittest.TestCase`. Convert existing `TestCase` files to pytest-native opportunistically when you modify them. Before opening a PR to `v3`, run the affected tests and applicable local checks. Run the full local suite (`uv run --locked --no-sync python tests/run.py`) for dependency or lock changes, shared test infrastructure, database or startup paths, cross-module lifecycle, compatibility layers, broad behavior changes, or an explicit maintainer requirement. The changed path must pass; any unrelated failure must be reported and reproduced against the current `upstream/v3` baseline instead of silently expanding the PR. Documentation-only changes use applicable text and structure checks; the `.github/workflows/test.yml` gate verifies every PR/push to `v3`; a merge push may reuse a complete successful PR verification only when the tested merge tree and base match exactly (see `docs/testing.md`).
|
||||
* **Required Constraints:** pytest is the only runner; `tests/conftest.py` isolates each run to a temporary `CONFIG_DIR`. Tests must not touch the real database, network, or external services (TMDB, LLM catalogs, downloaders, media servers, MP server); mock at the boundary or replay recorded responses for zero real outbound traffic. Restore any process-level state stubbed by tests (`sys.modules`, singletons, caches, settings). New tests must be pytest-native (function + `assert` + fixtures); do not add new `unittest.TestCase`. When modifying an existing `TestCase` file, convert the entire file to pytest-native and verify equivalent behavior. The contributor verification scope and maintainer arrangements are defined in `docs/testing.md`. Documentation-only changes use applicable text and structure checks. GitHub Actions reuse remains subject to the exact tested merge tree and base contract in that document.
|
||||
|
||||
### Commands and Development Workflow
|
||||
* **Primary Reference:** `docs/rules/03-commands.md`
|
||||
@@ -132,11 +142,11 @@ Architecture, persistence, security, external protocols, cross-module lifecycle,
|
||||
* **Minimal Change Principle:** Prefer the smallest correct change. Do not perform unrelated refactors, mass renames, or formatting-only cleanup.
|
||||
* **Output Language:** Summaries, validation results, and risk notes default to Chinese unless the user requests otherwise.
|
||||
|
||||
### Mandatory Pre-Commit Gate
|
||||
### Contributor Preparation Defaults
|
||||
|
||||
These checks are part of implementation, not optional follow-up work. Before creating a commit that contains host code, tests, dependencies, architecture fixtures, or CI changes, inspect the final diff and reproduce every applicable local gate. If the user requests commit or push, do not commit first and defer validation to GitHub Actions. `.github/workflows/test.yml` and `.github/workflows/pylint.yml` are the source of truth; re-read them when they change instead of relying on this command list alone.
|
||||
For contributors, inspect the final diff and complete applicable local checks before committing host code, tests, dependencies, architecture fixtures, or CI changes. A request to commit or push alone does not waive that preparation. Confirmed maintainers may use the documented execution arrangements above, including a local anchor followed by remaining validation. `.github/workflows/test.yml` and `.github/workflows/pylint.yml` remain the CI source of truth; re-read them when they change.
|
||||
|
||||
Run the architecture policy tests before snapshot checks, in the same order as CI:
|
||||
Select checks by affected contracts: architecture policy and snapshots for ownership/import/contract changes, relevant ratchets for governed Python changes, and startup performance for startup changes. Run focused behavior tests and changed-file pylint for affected Python files. The following is a command reference, not a requirement to execute every command for every task. When both apply, run architecture policy tests before snapshot checks, in the same order as CI. Shared test environments map these commands as described in `docs/development-setup.md`.
|
||||
|
||||
```bash
|
||||
uv run --locked --no-sync pytest \
|
||||
@@ -168,13 +178,24 @@ Apply the following acceptance rules:
|
||||
* **Reviewed baseline updates only:** Update a fixture only when the task intentionally changes the governed contract or the tool reports a genuine lower debt watermark. First prove the change complies with architecture policy, inspect the semantic diff, update coupled documentation/tests, run the non-writing check again, and include the fixture diff in the same commit. Complexity, concurrency, async-blocking, and host snapshot writers can overwrite regressions mechanically, so their ability to write is not approval to do so.
|
||||
* **Diagnose snapshot failures:** When `baseline.py --check-host` fails unexpectedly, rerun it with `--diagnostics` and inspect the affected JSON under `tests/fixtures/architecture/`. Do not infer that a changed snapshot is acceptable from test success alone.
|
||||
* **Test the behavior:** Run focused pytest coverage for every changed behavior. Run `uv run --locked --no-sync python tests/run.py` before commit for dependency/lock changes, shared test infrastructure, database or startup paths, cross-module lifecycle, compatibility layers, or broad behavior changes. Do not weaken, skip, or delete tests to satisfy a gate without proving equivalent coverage.
|
||||
* **Check changed Python files:** Run pylint on every Python file in the final diff using the same comparison scope as `.github/workflows/pylint.yml`; deleted files are excluded. Obtain the list with `git diff --name-only --diff-filter=ACMRT HEAD -- '*.py'`, then pass the non-empty result to `uv run --locked --no-sync pylint <paths...>`. For broad Python changes, also run `uv run --locked --no-sync pylint app/`.
|
||||
* **Check changed Python files:** For PR preparation, take the deduplicated union of `git diff --name-only --diff-filter=ACMRT <pr-base>...HEAD -- '*.py'`, `git diff --cached --name-only --diff-filter=ACMRT -- '*.py'`, and `git diff --name-only --diff-filter=ACMRT -- '*.py'`, plus intended new files from `git ls-files --others --exclude-standard -- '*.py'`. Resolve `<pr-base>` to the actual target base ref (normally `upstream/v3`). Keep staged and unstaged comparisons separate: they can cancel against HEAD while the index still contains a broken change. Exclude unrelated local files from the planned commit.
|
||||
|
||||
Before commit validation, confirm the index contains exactly the authorized changes: ordinary `git commit` writes the index, not the working tree. Stage only the selected paths/hunks and record `git write-tree` before exporting or linting. Use the working-tree shortcut only when all tracked files match the index (`git diff --quiet` succeeds) and no extra configuration or import inputs can alter lint behavior. Then run `uv run --locked --no-sync pylint <paths...>` on the non-empty selection. Code, project configuration, and project-local lint dependencies must come from the same planned tree. Filter candidates by existence in the planned index tree, so staged deletions/renames are excluded but unstaged deletions do not hide content that would still be committed.
|
||||
|
||||
Otherwise, including partial staging, validate a temporary checkout of the index instead of linting the working-tree version or staging unrelated hunks. This selects the inputs for the existing lint check, not an additional gate. From the repository root, export the index into a fresh temporary directory:
|
||||
|
||||
```bash
|
||||
INDEX_CHECKOUT="$(mktemp -d)"
|
||||
git checkout-index --all --prefix="${INDEX_CHECKOUT}/"
|
||||
```
|
||||
|
||||
Run pylint from that directory on the selected Python paths present there, using the original absolute test-environment interpreter (`<test-env>/bin/python -m pylint <paths...>`), with project configuration and project-local lint dependencies resolved from the export, not the original working tree or extra local inputs. The export copies indexed files only; it does not stage anything or change the working tree. Confirm `git write-tree` still matches the recorded tree immediately before committing. Commit that verified index without `-a`, path arguments, or another staging step that changes the tree; any index change invalidates the affected evidence. CI PRs compare base to HEAD; CI pushes use the workflow's before/current SHA range. For broad Python changes, also check `app/` in the same validated tree.
|
||||
* **Verify dependency changes:** If `pyproject.toml` or `uv.lock` changed, run `uv lock --check`, `uv sync --locked --offline --inexact --no-dev --check`, the full `uv run --locked --no-sync python tests/run.py`, and the locked `pip-audit` commands in `docs/rules/03-commands.md`. Do not commit an out-of-date or locally generated alternative lock file.
|
||||
* **Protect coverage:** Changes in `app/application/` or `app/domain/` must preserve the CI line-coverage floor. Add focused tests for new branches. Local macOS coverage is diagnostic only; never write or commit the canonical coverage baseline from it.
|
||||
* **Verify the final tree:** Re-run affected gates after all fixes and generated fixture updates, then inspect `git diff --check`, `git status --short`, and the complete diff. Report the exact commands and outcomes; never claim a gate was run if it was skipped or failed.
|
||||
* **Close the remote loop:** After an authorized push or PR creation, inspect GitHub checks for the exact pushed commit. Do not describe delivery as complete while a required check is pending or failing. Diagnose failures from logs, fix the root cause locally, rerun the relevant pre-commit gates, and push a new commit; do not weaken the workflow or ratchet to obtain green CI.
|
||||
* **Close the remote loop:** After an authorized push or PR creation, inspect GitHub checks for the exact pushed commit and the actual required checks/Ruleset. Diagnose failures from logs and fix unresolved substantive issues caused, worsened, or newly exposed by this change. Handle unrelated failures under the documented maintainer decision in `docs/rules/12-collaboration-and-distribution.md`; ordinary optional or pending automation does not automatically block delivery. Do not weaken workflows or ratchets, bypass platform protection, or claim unfinished required verification passed.
|
||||
|
||||
Documentation-only changes may use applicable text, link, structure, and diff checks instead of the full local Python suite, but must state that scope explicitly. If an applicable gate cannot run because of a confirmed platform or infrastructure limitation, stop before commit unless the user explicitly accepts the exception, and report the unverified gate and expected CI coverage.
|
||||
Documentation-only changes, including documentation contract tests, use applicable text, link, structure, focused test, and diff checks instead of the product suite; state that scope explicitly. Contributors report a blocked applicable check and obtain a maintainer decision before proceeding beyond the affected boundary. A documented maintainer arrangement may already cover that limitation; reuse it within scope and report what remains unverified.
|
||||
|
||||
### Conflict Resolution
|
||||
|
||||
|
||||
@@ -29,6 +29,26 @@ uv sync --locked
|
||||
uv sync --locked --no-dev --no-install-project
|
||||
```
|
||||
|
||||
以上命令以独立 checkout 的仓内 `.venv` 为默认。多仓工作区若已有共享运行环境 `.venv` 和
|
||||
隔离测试环境 `.venv-test`,按工作区说明选择,不创建另一套仓内环境。以下变量指向实际工作区
|
||||
根目录,路径使用绝对路径,避免 `--directory` 改变相对环境路径的含义:
|
||||
|
||||
```bash
|
||||
MOVIEPILOT_WORKSPACE="${MOVIEPILOT_WORKSPACE:?set absolute workspace root}"
|
||||
UV_PROJECT_ENVIRONMENT="${MOVIEPILOT_WORKSPACE}/.venv" \
|
||||
uv sync --locked --directory "${MOVIEPILOT_WORKSPACE}/MoviePilot"
|
||||
UV_PROJECT_ENVIRONMENT="${MOVIEPILOT_WORKSPACE}/.venv-test" \
|
||||
uv run --directory "${MOVIEPILOT_WORKSPACE}/MoviePilot" --locked --no-sync \
|
||||
python -c 'import sys; print(sys.executable)'
|
||||
```
|
||||
|
||||
测试与静态检查均在目标后端 checkout 工作目录执行:把公共命令中的
|
||||
`uv run --locked --no-sync` 映射到上述 `.venv-test`,或直接用该环境的 Python 执行
|
||||
`-m pytest`、`-m pylint` 及检查脚本。先确认解释器、锁文件和所需依赖组相符;`--no-sync`
|
||||
只避免同步,不证明已安装依赖匹配。环境创建/重建按工作区指令执行,不在普通验证中重建或
|
||||
同步共享环境。测试不加载运行用 `app.env`,由测试引导隔离临时 `CONFIG_DIR`;本地服务启动
|
||||
则遵循工作区的子进程环境加载约定。
|
||||
|
||||
### 2. 依赖分层与事实源
|
||||
|
||||
主程序只维护以下依赖事实源:
|
||||
@@ -198,55 +218,30 @@ Docker 镜像发布前还会使用 Trivy 扫描 OS 与语言包;根目录 `.tr
|
||||
- 核查时官方稳定版 `v1.75.1` 和主分支仍引用同一 gRPC 版本。例外只匹配镜像内 `usr/bin/rclone`
|
||||
和上述精确依赖 PURL;更新 rclone 摘要时必须重新核查两个架构,包含修复后应移除例外。
|
||||
|
||||
### 6. 提交代码前的检查
|
||||
### 6. Contributor 提交准备
|
||||
|
||||
在提交代码之前,请确保完成以下步骤:
|
||||
公共提交准备按 [AGENTS.md](../AGENTS.md) 的 contributor default 执行;已确认的维护者可以
|
||||
依据有效证据明确调整适用检查范围、时机和交付顺序,包括先保存本地 anchor 再补验证。记录
|
||||
决定、证据、未验证项和后续安排,已有同范围授权不重问;这不取消架构、兼容、正确性和真实报告。
|
||||
|
||||
1. **确认依赖分层正确**:运行时包进入 `[project].dependencies`;测试、覆盖率、静态检查和构建辅助进入 `[dependency-groups].dev`;插件依赖不并入主程序运行时依赖。
|
||||
按改动选择检查,命令入口统一见 [命令参考](rules/03-commands.md) 与
|
||||
[测试规范](testing.md):
|
||||
|
||||
2. **运行依赖漏洞检查**:确保锁定的运行时依赖通过 `pip-audit`。
|
||||
1. **依赖变化**:确认依赖分层、`uv.lock`、锁定环境一致性和 `pip-audit`;平台条件依赖还需对应平台安装证据。
|
||||
2. **行为变化**:运行受影响测试;依赖/锁文件、共享脚手架、数据库、启动、跨模块生命周期、兼容或大范围行为变化运行 `python tests/run.py` 全量。纯文档及其契约测试使用对应文本、结构、链接和 focused 检查。
|
||||
3. **架构与静态检查**:按受影响合同选择架构策略、snapshot 和 ratchet;策略测试先于 snapshot。Pylint 候选范围分别取 PR base 到 HEAD、暂存 diff、未暂存 diff 及本次新文件的并集;以实际待提交的索引 tree 过滤路径并检查内容。仅暂存本次选择的路径/hunks;代码、项目配置与仓内 lint 依赖必须来自同一 tree。只有全部 tracked 工作树与索引一致(`git diff --quiet`)且没有会影响 lint 的额外配置或导入输入时才使用工作树快捷路径,否则(含部分暂存)按 `AGENTS.md` 导出索引验证。这是现有 lint 的输入选择,不增加门禁;大范围 Python 变化在同一待提交 tree 中检查 `app/`。
|
||||
4. **最终核对**:复用仍有效的证据,只重跑被后续变化失效的检查;检查最终 diff、`git diff --check` 和工作树。CI 失败归属及维护者预授权按 [协作规则](rules/12-collaboration-and-distribution.md) 处理。
|
||||
|
||||
3. **运行测试**:如果项目中包含测试,请确保所有测试都通过。运行以下命令以执行测试:
|
||||
主仓架构检查不依赖独立插件仓;官方插件兼容观察通过每周或手工工作流单独运行,仅上传语义差异
|
||||
报告,不自动更新基线。普通主仓改动不要求额外检出插件仓或运行观察任务。宿主架构门禁和 changed-file
|
||||
Pylint 由 `v3` PR/push 的 GitHub Actions 执行,`app/` 全量 Pylint 是建议性报告。
|
||||
|
||||
```bash
|
||||
uv run --locked --no-sync pytest
|
||||
```
|
||||
|
||||
`python tests/run.py` 在本地默认把排序后的测试文件按向上取整的连续区间切成 4 片,
|
||||
并启动 4 个独立 pytest 进程;GitHub Actions 使用同一入口的 `--shard N/TOTAL`
|
||||
参数启动对应分片。需要单进程调试时使用 `python tests/run.py --serial`。Coverage job
|
||||
会在 `v3` 的 PR / push 中将同一全量入口切成 8 个并行分片,分别上传覆盖率数据,再由
|
||||
单一报告 job 合并并检查 Application 与 Domain 的固定 80% 基线;它不是只在手工触发时
|
||||
运行的建议性报告。每个 Coverage 分片预算为 15 分钟(其中测试 step 为 10 分钟),
|
||||
报告合并与 ratchet 预算为 10 分钟,用于容纳 Ubuntu Runner 的性能波动,不得通过跳过
|
||||
测试文件或覆盖率产物规避超时。
|
||||
|
||||
4. **运行架构与静态门禁**:主仓架构检查不依赖独立插件仓;官方插件兼容观察单独运行,
|
||||
任何检查命令都不会写入 fixture。
|
||||
|
||||
```bash
|
||||
uv run --locked --no-sync python scripts/architecture/baseline.py --check-host
|
||||
uv run --locked --no-sync python scripts/architecture/baseline.py \
|
||||
--check-plugins --plugin-repo ../MoviePilot-Plugins \
|
||||
--report official-plugin-architecture-report.json
|
||||
uv run --locked --no-sync pylint app/
|
||||
uv run --locked --no-sync python scripts/architecture/ruff_ratchet.py
|
||||
uv run --locked --no-sync python scripts/architecture/mypy_ratchet.py
|
||||
uv run --locked --no-sync python -m coverage erase
|
||||
uv run --locked --no-sync python -m coverage run tests/run.py --serial
|
||||
uv run --locked --no-sync python -m coverage json
|
||||
uv run --locked --no-sync python scripts/architecture/coverage_ratchet.py
|
||||
```
|
||||
|
||||
GitHub Actions 会在 `v3` 的 PR/push 中独立执行宿主架构门禁,并对本次改动的 Python
|
||||
文件执行 Pylint 硬门禁;`app/` 全量结果作为建议性报告上传。最新官方插件仓通过每周
|
||||
或手工观察工作流检查,只上传语义差异报告,不会自动更新已提交基线。
|
||||
|
||||
Ruff/Mypy 基线只允许收紧:新增诊断或类型错误增长都会被拒绝;覆盖率门禁固定要求
|
||||
Application 与 Domain 均不低于 80%,不随运行时语句计数变化。Mypy 完整
|
||||
ratchet 固定按 Linux/Python 3.14 分析;Coverage 检查只接受 GitHub Actions 的
|
||||
Ubuntu/Python 3.14、locked 依赖和串行全量测试工件,本机 macOS 报告仅用于诊断,
|
||||
不得直接写入并提交。受治零错误文件仍由 `mypy.ini` 的 `files=` 维护。
|
||||
Ruff/Mypy 基线只允许收紧,不接受新增诊断或类型错误增长;受治零错误文件由 `mypy.ini` 的
|
||||
`files=` 维护。Mypy 完整 ratchet 固定按 Linux/Python 3.14 分析。Coverage job 在 `v3` PR/push
|
||||
运行,按 `tests/run.py` 的分片合同执行并合并报告,Application 与 Domain 固定不低于 80%。
|
||||
每个 Coverage 分片预算为 15 分钟(测试 step 为 10 分钟),报告与 ratchet 为 10 分钟;不得靠
|
||||
跳过测试或产物规避超时。Coverage 只接受 GitHub Actions 的 Ubuntu/Python 3.14、locked
|
||||
依赖和全量测试工件,本机 macOS 报告仅供诊断,不得写入并提交 canonical baseline。
|
||||
|
||||
### 7. 参考资源
|
||||
|
||||
|
||||
@@ -7,13 +7,15 @@ This document is the project command reference, not an exhaustive shell allowlis
|
||||
## Development Environment Setup
|
||||
|
||||
```bash
|
||||
# Create the locked development/test environment
|
||||
# Create the locked development/test environment (standalone checkout)
|
||||
uv sync --locked
|
||||
|
||||
# Create a runtime-only environment
|
||||
uv sync --locked --no-dev --no-install-project
|
||||
```
|
||||
|
||||
Shared workspaces may map the environment to an explicit absolute path as documented in `docs/development-setup.md`. Choose the environment before running these commands; a runtime environment and an isolated test environment need not be the same directory.
|
||||
|
||||
---
|
||||
|
||||
## Dependency Management
|
||||
@@ -55,9 +57,8 @@ uv run --locked --no-sync pytest tests/test_xxx.py::test_function_name
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Run at minimum the tests directly related to the change.
|
||||
- If the change affects common modules, startup flow, CLI, or agent runtime behavior, expand the scope to the full test suite.
|
||||
- If the task only changes documentation, state explicitly that tests were not run. Do not claim checks that were not executed.
|
||||
- Contributor preparation runs related tests and expands to the full suite for the shared-impact triggers in `docs/testing.md`. Maintainer execution arrangements follow `AGENTS.md`.
|
||||
- For documentation tasks, report actual text/structure checks and documentation contract tests separately from unrun product tests. Do not claim checks that were not executed.
|
||||
|
||||
---
|
||||
|
||||
@@ -73,7 +74,7 @@ uv run --locked --no-sync pylint app/chain/download.py
|
||||
|
||||
**Rules:**
|
||||
- After Python code changes, ensure no new error-level issues are introduced.
|
||||
- Warning-level issues in new code should be minimized but are not an absolute gate.
|
||||
- Use the changed-file selection and preparation arrangement in `AGENTS.md`; the workflow's configured changed-file result is the gate, while the full application report is advisory for ordinary scoped changes.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
Verification preparation below is the contributor default. Confirmed maintainers may adjust scope, timing, and evidence reuse under `AGENTS.md`; shared correctness, compatibility, isolation, and honest reporting still apply. Full-suite triggers follow `docs/testing.md` and the affected behavior, not a directory name alone.
|
||||
|
||||
### What to Run
|
||||
|
||||
```bash
|
||||
@@ -14,7 +16,7 @@ uv run --locked --no-sync pytest
|
||||
|
||||
### When to Expand Scope
|
||||
|
||||
Run the full test suite when changing:
|
||||
Use the following as likely shared-impact boundaries. Run the full test suite when changes affect their shared behavior, lifecycle, or compatibility contract:
|
||||
- `app/runtime/`, `app/adapters/`, or `app/runtime/compat/` - config, events, managers, adapters, and compatibility boundaries
|
||||
- `app/chain/base.py` — chain base class
|
||||
- `app/modules/__init__.py` — module base class
|
||||
@@ -25,7 +27,7 @@ Run the full test suite when changing:
|
||||
|
||||
### Honest Reporting
|
||||
|
||||
- If a task only changes documentation, state explicitly that tests were not run.
|
||||
- For documentation tasks, report the actual text/structure checks and documentation contract tests; distinguish these from product tests that were not run.
|
||||
- Do not claim "all tests pass" unless you ran them.
|
||||
- Do not describe unexecuted checks as completed.
|
||||
|
||||
@@ -42,11 +44,12 @@ Run the full test suite when changing:
|
||||
## Static Analysis
|
||||
|
||||
```bash
|
||||
# Full application report; use the AGENTS.md selector for changed-file checks
|
||||
uv run --locked --no-sync pylint app/
|
||||
```
|
||||
|
||||
- After any Python code change, ensure no new **error-level** pylint issues are introduced.
|
||||
- Warning-level issues in new code should be minimized but are not an absolute gate for submission.
|
||||
- Changed-file pylint must pass the workflow's configured check under the applicable preparation arrangement; select the PR-base plus uncommitted union from `AGENTS.md`. The full application report is advisory for ordinary scoped changes.
|
||||
- Do not introduce error-level issues; handle warning-level findings according to the configured changed-file check rather than treating all warnings as exempt.
|
||||
- Do not suppress pylint warnings with `# pylint: disable` without a documented reason.
|
||||
|
||||
---
|
||||
@@ -130,12 +133,12 @@ The `API_TOKEN` value in `settings` is the source of truth. It is set at initial
|
||||
|
||||
---
|
||||
|
||||
## Pre-Submission Checklist
|
||||
## Contributor Pre-Submission Checklist
|
||||
|
||||
Before marking any task as complete:
|
||||
Before contributor submission, check applicable items below. Maintainer execution arrangements follow `AGENTS.md`; a local anchor does not imply completion or acceptance. The quality contracts remain shared.
|
||||
|
||||
- [ ] Related pytest tests pass
|
||||
- [ ] No new pylint error-level issues in `pylint app/`
|
||||
- [ ] Changed-file pylint passes for the PR-base plus uncommitted union defined in `AGENTS.md`; use full `pylint app/` for broad Python changes
|
||||
- [ ] If dependencies changed: the package is in the correct `pyproject.toml` group, `uv.lock` is current, the locked project consistency check and runtime dependency audit pass
|
||||
- [ ] If CLI behavior changed: `docs/cli.md` and related tests are updated
|
||||
- [ ] If MCP/API behavior changed: `docs/mcp-api.md` and related skill files are updated
|
||||
|
||||
@@ -41,7 +41,7 @@ ci: improve docker build cache
|
||||
|
||||
### Rules
|
||||
|
||||
- Local commits follow the active workflow, an approved plan, or current user authorization. Existing authorization does not require a second confirmation; push, PR, merge, and release remain separate delivery boundaries.
|
||||
- Local commits follow the active workflow, an approved plan, or current user authorization. Existing scoped authorization does not require a second confirmation, including when the confirmed project context defines a requested PR as tracking through merge. Push, PR, merge, and release each need coverage by that authorization; a PR request does not by itself authorize a release. Maintainer arrangements and local anchors follow `AGENTS.md`.
|
||||
- Keep the subject line under 72 characters.
|
||||
- Use the imperative mood in the subject line ("add", "fix", "remove", not "added", "fixed", "removed").
|
||||
- If a commit introduces a breaking change, append `!` after the type and include `BREAKING CHANGE:` in the footer.
|
||||
@@ -81,12 +81,16 @@ ci: improve docker build cache
|
||||
- pytest test suite
|
||||
- pylint static analysis
|
||||
- Docker image build (on main branch or tags)
|
||||
- Do not merge code that fails CI unless there is an explicit, documented reason and user approval.
|
||||
- Contributors must report CI failures and obtain a documented maintainer decision before merging with failed checks. A documented maintainer decision may preauthorize the same scoped failures; an existing applicable decision plus merge authorization does not require another approval.
|
||||
- Record the failed check, evidence of its cause and relationship to the change, and the applicable decision. Maintainers may authorize proceeding with proven unrelated base failures, automation/infrastructure/quota failures, or non-actionable review feedback. Logs, a current target-base reproduction, or other sufficient evidence must show the change did not cause, worsen, or newly expose the issue; failure status alone is insufficient. Do not silently expand the PR to fix unrelated issues or report failed/unrun checks as passed.
|
||||
- Follow actual required checks, Rulesets, and platform merge restrictions. Unresolved substantive issues owned by the change block merge; a scoped failure decision is not permission to bypass protection or waive shared architecture, compatibility, and correctness contracts. Maintainer authority follows the confirmed user/project context in `AGENTS.md`, not GitHub `WRITE` or a contributor's self-declaration.
|
||||
|
||||
---
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
The following preparation is the contributor default. Confirmed maintainers may adjust verification timing and delivery order under `AGENTS.md`, while keeping the evidence and remaining work explicit.
|
||||
|
||||
- Keep PRs focused on a single concern. Separate refactors, features, and bug fixes into distinct PRs when practical.
|
||||
- Include in the PR description:
|
||||
- What changed and why
|
||||
|
||||
@@ -29,6 +29,25 @@ uv run --locked --no-sync python tests/run.py --shard 1/4 # 只跑指
|
||||
- **复现 CI 用干净环境**:使用 `uv sync --locked` 从 `uv.lock` 创建环境,再以
|
||||
`uv run --locked --no-sync` 运行测试,避免本地额外包、未锁定解析结果或编译产物掩盖问题。
|
||||
|
||||
共享工作区的 `.venv-test` 与上述 `uv` 命令映射见 [开发环境设置](development-setup.md);
|
||||
`--no-sync` 不会验证已安装依赖是否匹配锁文件。
|
||||
|
||||
## 验证范围与维护者安排
|
||||
|
||||
Contributor 默认在提交前运行受影响测试与适用本地检查。依赖或锁文件、共享测试脚手架、数据库、
|
||||
启动路径、跨模块生命周期、兼容层、大范围行为改动或维护者明确要求时,运行完整
|
||||
`uv run --locked --no-sync python tests/run.py`。纯文档及其契约测试采用文本、结构、链接和
|
||||
focused 测试验证,不要求启动产品全套测试。
|
||||
|
||||
已确认的维护者可按 `AGENTS.md` 明确调整验证范围、时机及交付顺序,复用仍有效的源码、锁文件、
|
||||
脚手架和环境证据,也可先保存本地 anchor 再补检查。记录依据、未验证项和后续安排;HEAD 变化
|
||||
本身不要求本地全量重跑,但实际变化使哪些证据失效,就重跑哪些检查。这不改变 GitHub Actions
|
||||
按精确 merge tree/base 复用的实现合同,也不取消测试隔离或下述 TestCase 整文件迁移要求。
|
||||
|
||||
改动涉及的行为必须有可信验证。失败须判断是否由本次改动造成、加重或重新触达;无关失败用当前
|
||||
目标 base 复现或其他充分证据说明,并按 `docs/rules/12-collaboration-and-distribution.md`
|
||||
的已记录维护者决定处置。已预授权的同一范围无需逐次重问,缺少证据时保留未知,不伪报通过。
|
||||
|
||||
## 隔离模型(`tests/conftest.py`)
|
||||
|
||||
收集任何测试模块、`import app.*` **之前**,conftest 完成两件事:
|
||||
@@ -167,6 +186,6 @@ def test_recognize_prefers_explicit_identity(sample_meta, monkeypatch):
|
||||
- **门禁**:`.github/workflows/test.yml` 在指向 `v3` 的 `pull_request` / `push` 及手动触发时,从 `uv.lock` 同步环境。独立 `architecture` job 先运行宿主依赖、运行契约和基线 CLI 快速门禁;全量测试通过 `coverage run --parallel-mode tests/run.py --shard N/8` 分到 8 个 job,一次执行同时验证单测并采集覆盖率。每个分片都有独立进程和临时 `CONFIG_DIR`,不共用 SQLite 或进程级状态,由单一报告 job 合并后检查 Application 与 Domain 的固定 80% 基线。
|
||||
- **跨仓观察**:`.github/workflows/architecture-observe.yml` 每周或手工检出官方插件仓最新 `main`,使用 `--check-plugins` 比较公开导入、Hook 和动态 API 契约。它只上传 `official-plugin-architecture-report.json`,不会自动刷新 fixture;语义变化必须人工审查后显式执行 `--write-plugins`。
|
||||
- **静态检查**:`.github/workflows/pylint.yml` 对指向 `v3` 的 PR、推送和手工触发运行 Pylint。PR/推送改动到的 Python 文件是硬门禁;`app/` 全量扫描保留为建议性 JSON 构建工件,存量告警不会掩盖或阻塞本次增量治理。
|
||||
- **PR 本地验证**:提交前运行受影响测试和适用的静态检查。涉及依赖或锁文件、共享测试基建、数据库、启动链、跨模块生命周期、兼容层或大范围行为变化时,运行 `uv run --locked --no-sync python tests/run.py` 完成本地全量;需要断点、输出顺序或测试污染诊断时使用 `--serial`。所有测试都应确认受影响路径通过且 socket 探针无真实出站,验证说明准确标注执行范围。若存在无关失败,必须在当前 `upstream/v3` 基线上独立复现并在 PR 中如实说明;不得静默扩大当前 PR 去修复基线问题。纯文档变更执行适用的文本、结构和 diff 检查,CI 继续运行全量门禁。
|
||||
- **PR 本地验证**:按上文「验证范围与维护者安排」选择 contributor 默认检查或已记录的维护者安排,统一处理证据复用与失败归属;需要断点、输出顺序或测试污染诊断时使用 `--serial`。确认受影响路径与零真实出站,准确标注验证范围;本地执行安排不改变 CI 的全量验证与复用合同。
|
||||
- **覆盖率门禁**:`Unit Tests with Coverage` jobs 会在 `v3` 的 PR、push 和手工触发中通过 `tests/run.py --shard N/8` 并行采集覆盖率数据,`Coverage Report` 再合并全部分片并只读检查 Application 与 Domain 是否达到 Ubuntu/Python 3.14 canonical 的固定 80% 行覆盖率基线,同时上传 JSON / XML 工件。低于 80% 会阻塞;达到或超过 80% 不要求同步运行时语句计数。macOS 本地报告只用于诊断,不直接作为可提交基线。
|
||||
- 复现 CI 使用 `uv sync --locked`;主程序运行依赖位于 `[project].dependencies`,pytest 与覆盖率工具位于默认 `dev` 依赖组。
|
||||
|
||||
230
tests/test_workflow_documentation.py
Normal file
230
tests/test_workflow_documentation.py
Normal file
@@ -0,0 +1,230 @@
|
||||
"""公共协作规则的身份、证据和变更范围合同;不导入产品运行态。"""
|
||||
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def _read(relative: str) -> str:
|
||||
return (ROOT / relative).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_execution_discretion_keeps_shared_contracts_and_contributor_defaults():
|
||||
"""维护者可调整执行,但身份、质量和真实报告不可由贡献者自行豁免。"""
|
||||
agents = _read("AGENTS.md")
|
||||
authority = agents.split("## Shared Contract and Execution Authority\n", 1)[1]
|
||||
authority = authority.split("\n---", 1)[0]
|
||||
for contract in (
|
||||
"business correctness", "SDK/Compat compatibility", "test isolation",
|
||||
"truthful verification", "contributor defaults", "local anchor",
|
||||
"unverified items", "current user or established project context",
|
||||
"contributor's self-declared role does not grant an exception",
|
||||
"GitHub `WRITE` proves platform capability only",
|
||||
"explicit current restriction takes precedence",
|
||||
):
|
||||
assert contract in authority
|
||||
assert "## Canonical Package Ownership" in agents
|
||||
assert "**No baseline laundering:**" in agents
|
||||
assert "**Reviewed baseline updates only:**" in agents
|
||||
assert "convert the entire file to pytest-native" in agents
|
||||
assert "### Contributor Preparation Defaults" in agents
|
||||
assert "documentation contract tests" in agents
|
||||
assert "## Contributor Pre-Submission Checklist" in _read(
|
||||
"docs/rules/11-quality-and-security.md"
|
||||
)
|
||||
assert "### 6. Contributor 提交准备" in _read("docs/development-setup.md")
|
||||
testing = _read("docs/testing.md")
|
||||
assert "整文件转成 pytest 原生" in testing
|
||||
assert "**PR 本地验证**:按上文「验证范围与维护者安排」" in testing
|
||||
|
||||
|
||||
def test_ci_failure_decisions_can_be_reused_only_within_authorized_scope():
|
||||
"""同范围预授权避免重复批准,仍要求归属证据、merge 授权和平台放行。"""
|
||||
collaboration = _read("docs/rules/12-collaboration-and-distribution.md")
|
||||
for contract in (
|
||||
"Contributors must report CI failures",
|
||||
"documented maintainer decision may preauthorize the same scoped failures",
|
||||
"existing applicable decision plus merge authorization",
|
||||
"Record the failed check",
|
||||
"did not cause, worsen, or newly expose the issue",
|
||||
"Unresolved substantive issues owned by the change block merge",
|
||||
"not permission to bypass protection",
|
||||
"not GitHub `WRITE` or a contributor's self-declaration",
|
||||
):
|
||||
assert contract in collaboration
|
||||
assert "docs/rules/12-collaboration-and-distribution.md" in _read("AGENTS.md")
|
||||
|
||||
|
||||
def test_shared_environment_mapping_keeps_runtime_and_tests_separate():
|
||||
"""公共 uv 命令可映射共享测试环境,并保留隔离与依赖一致性要求。"""
|
||||
setup = _read("docs/development-setup.md")
|
||||
assert 'UV_PROJECT_ENVIRONMENT="${MOVIEPILOT_WORKSPACE}/.venv"' in setup
|
||||
assert 'UV_PROJECT_ENVIRONMENT="${MOVIEPILOT_WORKSPACE}/.venv-test"' in setup
|
||||
assert "不证明已安装依赖匹配" in setup
|
||||
assert "测试不加载运行用 `app.env`" in setup
|
||||
assert "临时 `CONFIG_DIR`" in setup
|
||||
for relative in ("AGENTS.md", "docs/testing.md", "docs/rules/03-commands.md"):
|
||||
assert "development-setup.md" in _read(relative)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def git(tmp_path):
|
||||
"""在临时仓库执行 Git,隔离提交身份、签名与用户 hook。"""
|
||||
def run(*args):
|
||||
return subprocess.run(
|
||||
[
|
||||
"git", "-c", "user.name=Docs Test", "-c", "user.email=docs@example.invalid",
|
||||
"-c", "commit.gpgsign=false", "-c", "core.hooksPath=/dev/null", *args,
|
||||
],
|
||||
cwd=tmp_path, check=True, capture_output=True, text=True,
|
||||
).stdout
|
||||
|
||||
return run
|
||||
|
||||
|
||||
def _selected_python_paths(git):
|
||||
paragraph = _read("AGENTS.md").split("* **Check changed Python files:**", 1)[1]
|
||||
paragraph = paragraph.split("\n", 1)[0]
|
||||
commands = re.findall(r"`(git (?:diff|ls-files) [^`]+)`", paragraph)
|
||||
assert len(commands) == 4
|
||||
paths = set()
|
||||
for command in commands:
|
||||
paths.update(git(*shlex.split(command.replace("<pr-base>", "pr-base"))[1:]).splitlines())
|
||||
return paths
|
||||
|
||||
|
||||
def test_documented_python_selection_covers_commits_and_uncommitted_union(tmp_path, git):
|
||||
"""执行文档选择命令,覆盖多提交、暂存、未暂存、新文件、改名与后续删除。"""
|
||||
git("init", "-q")
|
||||
for name in ("committed.py", "staged.py", "unstaged.py", "deleted.py", "old.py"):
|
||||
(tmp_path / name).write_text("value = 1\n", encoding="utf-8")
|
||||
git("add", ".")
|
||||
git("commit", "-qm", "base")
|
||||
git("branch", "pr-base")
|
||||
for name in ("committed.py", "deleted.py"):
|
||||
(tmp_path / name).write_text("value = 2\n", encoding="utf-8")
|
||||
git("add", ".")
|
||||
git("commit", "-qm", "behavior")
|
||||
git("mv", "old.py", "renamed.py")
|
||||
git("commit", "-qm", "rename")
|
||||
(tmp_path / "staged.py").write_text("value = 3\n", encoding="utf-8")
|
||||
git("add", "staged.py")
|
||||
(tmp_path / "unstaged.py").write_text("value = 4\n", encoding="utf-8")
|
||||
(tmp_path / "deleted.py").unlink()
|
||||
(tmp_path / "new.py").write_text("value = 5\n", encoding="utf-8")
|
||||
(tmp_path / "notes.md").write_text("notes\n", encoding="utf-8")
|
||||
|
||||
paths = _selected_python_paths(git)
|
||||
# 未暂存删除不会从待提交 tree 移除文件;新文件也要先选入提交才存在于索引。
|
||||
indexed = set(git("ls-files").splitlines())
|
||||
assert "deleted.py" in paths & indexed
|
||||
assert "new.py" in paths - indexed
|
||||
git("add", "--", "unstaged.py", "deleted.py", "new.py")
|
||||
indexed = set(git("ls-files").splitlines())
|
||||
assert paths & indexed == {
|
||||
"committed.py", "staged.py", "unstaged.py", "renamed.py", "new.py",
|
||||
}
|
||||
|
||||
|
||||
def test_staged_syntax_error_survives_head_cancellation_and_fails_index_lint(tmp_path, git):
|
||||
"""工作树恢复 HEAD 后仍检查坏索引,索引导出和 lint 不改写暂存内容。"""
|
||||
git("init", "-q")
|
||||
target = tmp_path / "target.py"
|
||||
clean = "value = 1\n"
|
||||
broken = "value = (\n"
|
||||
target.write_text(clean, encoding="utf-8")
|
||||
git("add", "--", "target.py")
|
||||
git("commit", "-qm", "base")
|
||||
git("branch", "pr-base")
|
||||
target.write_text(broken, encoding="utf-8")
|
||||
git("add", "--", "target.py")
|
||||
target.write_text(clean, encoding="utf-8")
|
||||
|
||||
assert git("status", "--porcelain", "--", "target.py") == "MM target.py\n"
|
||||
assert git("diff", "--name-only", "HEAD", "--", "*.py") == ""
|
||||
assert _selected_python_paths(git) == {"target.py"}
|
||||
index_tree = git("write-tree")
|
||||
|
||||
# 执行文档中的索引导出命令;临时目录由 pytest 负责回收。
|
||||
checkout = tmp_path / "index-checkout"
|
||||
checkout.mkdir()
|
||||
command = re.search(r"^ (git checkout-index .+)$", _read("AGENTS.md"), re.MULTILINE)
|
||||
assert command is not None
|
||||
git(*shlex.split(command[1].replace("${INDEX_CHECKOUT}", str(checkout)))[1:])
|
||||
assert (checkout / "target.py").read_text(encoding="utf-8") == broken
|
||||
|
||||
def lint(directory):
|
||||
return subprocess.run(
|
||||
[
|
||||
sys.executable, "-m", "pylint", "--rcfile=/dev/null", "--persistent=n",
|
||||
"--disable=all", "--enable=syntax-error", "target.py",
|
||||
],
|
||||
cwd=directory, check=False, capture_output=True, text=True,
|
||||
)
|
||||
|
||||
assert lint(tmp_path).returncode == 0
|
||||
result = lint(checkout)
|
||||
assert result.returncode != 0
|
||||
assert "E0001" in result.stdout
|
||||
assert git("write-tree") == index_tree
|
||||
assert target.read_text(encoding="utf-8") == clean
|
||||
git("commit", "-qm", "indexed content")
|
||||
assert git("rev-parse", "HEAD^{tree}") == index_tree
|
||||
assert git("show", "HEAD:target.py") == broken
|
||||
|
||||
|
||||
def test_unstaged_pylint_config_cannot_hide_index_undefined_variable(tmp_path, git):
|
||||
"""源码一致但工作树配置屏蔽 E0602 时,快捷路径拒绝并以索引配置检查。"""
|
||||
git("init", "-q")
|
||||
target = tmp_path / "target.py"
|
||||
config = tmp_path / ".pylintrc"
|
||||
indexed_config = "[MESSAGES CONTROL]\ndisable=all\nenable=syntax-error,undefined-variable\n"
|
||||
working_config = "[MESSAGES CONTROL]\ndisable=all\nenable=syntax-error\n"
|
||||
target.write_text("value = 1\n", encoding="utf-8")
|
||||
config.write_text(indexed_config, encoding="utf-8")
|
||||
git("add", "--", "target.py", ".pylintrc")
|
||||
git("commit", "-qm", "base")
|
||||
git("branch", "pr-base")
|
||||
target.write_text("value = missing_value\n", encoding="utf-8")
|
||||
git("add", "--", "target.py")
|
||||
config.write_text(working_config, encoding="utf-8")
|
||||
index_tree = git("write-tree")
|
||||
|
||||
assert _selected_python_paths(git) == {"target.py"}
|
||||
assert git("diff", "--quiet", "--", "target.py") == ""
|
||||
shortcut = re.search(r"`(git diff --quiet[^`]*)`", _read("AGENTS.md"))
|
||||
assert shortcut is not None
|
||||
with pytest.raises(subprocess.CalledProcessError) as blocked:
|
||||
git(*shlex.split(shortcut[1])[1:])
|
||||
assert blocked.value.returncode == 1
|
||||
|
||||
checkout = tmp_path / "index-checkout"
|
||||
checkout.mkdir()
|
||||
command = re.search(r"^ (git checkout-index .+)$", _read("AGENTS.md"), re.MULTILINE)
|
||||
assert command is not None
|
||||
git(*shlex.split(command[1].replace("${INDEX_CHECKOUT}", str(checkout)))[1:])
|
||||
assert (checkout / "target.py").read_bytes() == target.read_bytes()
|
||||
assert (checkout / ".pylintrc").read_text(encoding="utf-8") == indexed_config
|
||||
|
||||
def lint(directory):
|
||||
return subprocess.run(
|
||||
[
|
||||
sys.executable, "-m", "pylint", "--rcfile=.pylintrc", "--persistent=n",
|
||||
"--reports=n", "--score=n", "target.py",
|
||||
],
|
||||
cwd=directory, check=False, capture_output=True, text=True,
|
||||
)
|
||||
|
||||
assert lint(tmp_path).returncode == 0
|
||||
result = lint(checkout)
|
||||
assert result.returncode == 2
|
||||
assert "E0602" in result.stdout
|
||||
assert git("write-tree") == index_tree
|
||||
assert config.read_text(encoding="utf-8") == working_config
|
||||
Reference in New Issue
Block a user