mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-03 06:29:55 +08:00
docs: restructure AGENTS.md and add docs/rules agent documentation system (#5830)
This commit is contained in:
84
docs/rules/01-project-overview.md
Normal file
84
docs/rules/01-project-overview.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# 01 — Project Overview
|
||||
|
||||
## System Purpose
|
||||
|
||||
MoviePilot is a self-hosted media automation platform targeting Chinese-language users. It automates the full lifecycle of media acquisition and organization:
|
||||
|
||||
1. **Discovery** — monitors RSS feeds, subscription lists, and recommendation sources for new media releases.
|
||||
2. **Search** — queries configured torrent indexers to locate suitable torrents for subscribed media.
|
||||
3. **Download** — sends torrent tasks to a configured download client (qBittorrent, Transmission, rTorrent).
|
||||
4. **Transfer** — moves or hard-links completed downloads into a structured media library.
|
||||
5. **Scraping** — fetches metadata (posters, descriptions, episode info) from TMDB, TheTVDB, Douban, and Bangumi.
|
||||
6. **Media Server Integration** — notifies and refreshes Emby, Jellyfin, or Plex after files are organized.
|
||||
7. **Messaging** — sends status notifications through Telegram, WeChat, Feishu, Slack, Discord, and other channels.
|
||||
8. **AI Agent** — provides a conversational agent interface (via MCP and LLM chain) for natural-language management tasks.
|
||||
|
||||
---
|
||||
|
||||
## Repository Boundaries
|
||||
|
||||
### What Is in This Repository
|
||||
|
||||
| Path | Content |
|
||||
|---|---|
|
||||
| `app/` | FastAPI backend application |
|
||||
| `moviepilot` | Local CLI entrypoint (install, init, start, stop, update, agent) |
|
||||
| `app/api/endpoints/` | HTTP endpoint handlers |
|
||||
| `app/chain/` | Business orchestration layer |
|
||||
| `app/modules/` | Pluggable backend integrations (downloaders, media servers, etc.) |
|
||||
| `app/helper/` | Reusable low-level utilities |
|
||||
| `app/db/` | SQLAlchemy models and data access wrappers |
|
||||
| `app/core/` | Config, event system, module manager, plugin manager, security |
|
||||
| `app/schemas/` | Pydantic request/response models and shared enums |
|
||||
| `app/agent/` | LLM agent runtime |
|
||||
| `app/workflow/` | Workflow engine |
|
||||
| `database/versions/` | Alembic migration scripts |
|
||||
| `docs/` | CLI, MCP/API, and development workflow documentation |
|
||||
| `skills/` | AI agent skills and associated scripts |
|
||||
| `tests/` | Pytest test suite |
|
||||
|
||||
### What Is NOT in This Repository
|
||||
|
||||
* **Frontend source code** — lives in the separate `MoviePilot-Frontend` repository (Vue/TypeScript). Only the built `dist/` artifact is consumed here.
|
||||
* **Plugin source code** — plugins are installed into `app/plugins/` at runtime from external sources; they are not part of this repository.
|
||||
* **User config and runtime data** — `config/`, `.moviepilot.env`, `*.db` files are local runtime state. Do not modify or commit them unless explicitly requested.
|
||||
|
||||
---
|
||||
|
||||
## Deployment Models
|
||||
|
||||
### Docker (Primary)
|
||||
|
||||
The standard deployment method. A Docker image bundles the backend, frontend static files, and resource data. Users configure via environment variables and mount a config directory.
|
||||
|
||||
### Local CLI
|
||||
|
||||
An alternative for users running from source. The `moviepilot` CLI handles installation, initialization, service management, and updates. See `docs/cli.md` for the full command reference.
|
||||
|
||||
---
|
||||
|
||||
## Key External Dependencies (Domain Context)
|
||||
|
||||
| Service Type | Supported Backends |
|
||||
|---|---|
|
||||
| Torrent indexers | Site-specific spiders, Jackett/Prowlarr compatible |
|
||||
| Download clients | qBittorrent, Transmission, rTorrent |
|
||||
| Media servers | Emby, Jellyfin, Plex, TrimMedia, Zspace, Ugreen |
|
||||
| Metadata sources | TMDB, TheTVDB, Douban, Bangumi, Fanart |
|
||||
| Message channels | Telegram, WeChat, WeChatClawBot, Feishu, Slack, Discord, VoceChat, Synology Chat, WebPush, QQBot |
|
||||
| LLM providers | OpenAI-compatible, Anthropic, and other configurable providers |
|
||||
|
||||
---
|
||||
|
||||
## Business Domain Vocabulary
|
||||
|
||||
| Term | Meaning |
|
||||
|---|---|
|
||||
| Subscribe | A tracked media item (movie or TV series) that MoviePilot will automatically search and download |
|
||||
| Transfer | The process of moving or hard-linking downloaded files into the organized media library |
|
||||
| Chain | A business orchestration class that coordinates multiple modules for a use case |
|
||||
| Module | A pluggable backend integration loaded by the module manager |
|
||||
| Skill | A packaged AI agent capability that can be invoked via the MCP interface |
|
||||
| SystemConfig | Runtime key-value configuration stored in the database and managed via `SystemConfigKey` |
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
144
docs/rules/02-tech-stack.md
Normal file
144
docs/rules/02-tech-stack.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# 02 — Tech Stack
|
||||
|
||||
## Runtime and Language
|
||||
|
||||
| Item | Detail |
|
||||
|---|---|
|
||||
| Language | Python 3.11+ |
|
||||
| CI Python version | Python 3.12 |
|
||||
| Async runtime | asyncio (native), integrated with FastAPI/Uvicorn |
|
||||
|
||||
---
|
||||
|
||||
## Backend Framework
|
||||
|
||||
| Item | Detail |
|
||||
|---|---|
|
||||
| Web framework | FastAPI |
|
||||
| ASGI server | Uvicorn |
|
||||
| Data validation | Pydantic v2 (`BaseModel`, `BaseSettings`, `model_validator`) |
|
||||
| Settings management | `pydantic-settings` (`BaseSettings` class in `app/core/config.py`) |
|
||||
|
||||
---
|
||||
|
||||
## Database
|
||||
|
||||
| Item | Detail |
|
||||
|---|---|
|
||||
| Default database | SQLite |
|
||||
| Optional database | PostgreSQL (configured via `DB_TYPE` and related env vars) |
|
||||
| ORM | SQLAlchemy |
|
||||
| Migration tool | Alembic (`database/versions/`) |
|
||||
| PostgreSQL extras | `app/modules/postgresql/` module; setup guide at `docs/postgresql-setup.md` |
|
||||
|
||||
---
|
||||
|
||||
## Caching
|
||||
|
||||
| Item | Detail |
|
||||
|---|---|
|
||||
| File-based cache | `FileCache` / `AsyncFileCache` in `app/core/cache.py` |
|
||||
| Redis | Optional; `app/modules/redis/` module; used for distributed caching when configured |
|
||||
| In-process cache | Decorator helpers `fresh` / `async_fresh` on `FileCache` |
|
||||
|
||||
---
|
||||
|
||||
## LLM and AI Agent
|
||||
|
||||
| Item | Detail |
|
||||
|---|---|
|
||||
| Agent runtime | `app/agent/` — custom LLM agent orchestration |
|
||||
| LLM abstraction | LangChain-based with multi-provider support |
|
||||
| Supported providers | OpenAI-compatible APIs, Anthropic, and other configurable providers |
|
||||
| Configuration | `LLM_PROVIDER`, `LLM_MODEL`, `LLM_API_KEY`, `LLM_BASE_URL` in settings |
|
||||
| Enable flag | `AI_AGENT_ENABLE` |
|
||||
| MCP protocol | JSON-RPC 2.0 at `/api/v1/mcp`; see `docs/mcp-api.md` |
|
||||
|
||||
---
|
||||
|
||||
## Module Integrations
|
||||
|
||||
### Download Clients
|
||||
| Module | Directory |
|
||||
|---|---|
|
||||
| qBittorrent | `app/modules/qbittorrent/` |
|
||||
| Transmission | `app/modules/transmission/` |
|
||||
| rTorrent | `app/modules/rtorrent/` |
|
||||
|
||||
### Media Servers
|
||||
| Module | Directory |
|
||||
|---|---|
|
||||
| Emby | `app/modules/emby/` |
|
||||
| Jellyfin | `app/modules/jellyfin/` |
|
||||
| Plex | `app/modules/plex/` |
|
||||
| TrimMedia | `app/modules/trimemedia/` |
|
||||
| Zspace | `app/modules/zspace/` |
|
||||
| Ugreen | `app/modules/ugreen/` |
|
||||
|
||||
### Message Channels
|
||||
| Module | Directory |
|
||||
|---|---|
|
||||
| Telegram | `app/modules/telegram/` |
|
||||
| WeChat | `app/modules/wechat/` |
|
||||
| WeChatClawBot | `app/modules/wechatclawbot/` |
|
||||
| Feishu | `app/modules/feishu/` |
|
||||
| Slack | `app/modules/slack/` |
|
||||
| Discord | `app/modules/discord/` |
|
||||
| VoceChat | `app/modules/vocechat/` |
|
||||
| Synology Chat | `app/modules/synologychat/` |
|
||||
| WebPush | `app/modules/webpush/` |
|
||||
| QQBot | `app/modules/qqbot/` |
|
||||
|
||||
### Metadata Sources
|
||||
| Module | Directory |
|
||||
|---|---|
|
||||
| TMDB | `app/modules/themoviedb/` |
|
||||
| TheTVDB | `app/modules/thetvdb/` |
|
||||
| Douban | `app/modules/douban/` |
|
||||
| Bangumi | `app/modules/bangumi/` |
|
||||
| Fanart | `app/modules/fanart/` |
|
||||
|
||||
---
|
||||
|
||||
## Dependency Management
|
||||
|
||||
| Item | Detail |
|
||||
|---|---|
|
||||
| Source file | `requirements.in` — edit this to add or upgrade dependencies |
|
||||
| Lock file | `requirements.txt` — generated by `pip-compile`; never edit manually |
|
||||
| Tool | `pip-tools` (`pip-compile`, `pip-sync`) |
|
||||
| Install | `pip install -r requirements.txt` |
|
||||
|
||||
---
|
||||
|
||||
## Performance Extension
|
||||
|
||||
| Item | Detail |
|
||||
|---|---|
|
||||
| Rust extension | `moviepilot_rust` — optional compiled accelerator for core processing paths |
|
||||
| Build | Requires Rust `cargo`; built automatically by `moviepilot install deps` |
|
||||
| Skip flag | `MOVIEPILOT_SKIP_RUST_ACCEL=1` disables build (falls back to Python implementation) |
|
||||
| Toggle | Can be disabled/re-enabled at runtime via frontend Advanced Settings → Lab |
|
||||
|
||||
---
|
||||
|
||||
## Quality Tooling
|
||||
|
||||
| Tool | Purpose | Command |
|
||||
|---|---|---|
|
||||
| pytest | Test runner | `pytest tests/test_xxx.py` |
|
||||
| pylint | Static analysis | `pylint app/` |
|
||||
| safety | Dependency vulnerability scan | `safety check -r requirements.txt --policy-file=safety.policy.yml` |
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
| Method | Detail |
|
||||
|---|---|
|
||||
| Docker | Primary deployment; image bundles backend + frontend static files + resources |
|
||||
| Local CLI | `moviepilot` CLI for source-based install; see `docs/cli.md` |
|
||||
| Frontend | Vue/TypeScript SPA served from `public/`; source in `MoviePilot-Frontend` repo |
|
||||
| Frontend proxy | Local Node `service.js` proxies `/api` and `/cookiecloud` to the backend |
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
259
docs/rules/03-commands.md
Normal file
259
docs/rules/03-commands.md
Normal file
@@ -0,0 +1,259 @@
|
||||
# 03 — Commands
|
||||
|
||||
Only suggest or execute commands that appear in this document. Do not assume standard tool defaults, global flags, or operating-system-specific behavior unless explicitly listed here.
|
||||
|
||||
---
|
||||
|
||||
## Development Environment Setup
|
||||
|
||||
```bash
|
||||
# Create and activate virtual environment
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate # macOS / Linux
|
||||
.\venv\Scripts\activate # Windows
|
||||
|
||||
# Install pip-tools
|
||||
pip install pip-tools
|
||||
|
||||
# Install project dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependency Management
|
||||
|
||||
```bash
|
||||
# Compile requirements.txt from requirements.in (full recompile)
|
||||
pip-compile requirements.in
|
||||
|
||||
# Upgrade a single package without touching others
|
||||
pip-compile --upgrade-package <package-name> requirements.in
|
||||
|
||||
# Install from the generated lock file
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Always edit `requirements.in` to add or change dependencies.
|
||||
- Never edit `requirements.txt` manually — it is a generated lock file.
|
||||
- After any change to `requirements.in`, re-run `pip-compile requirements.in` and commit both files together.
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run a specific test file
|
||||
pytest tests/test_xxx.py
|
||||
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run tests with verbose output
|
||||
pytest -v tests/test_xxx.py
|
||||
|
||||
# Run a specific test function
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Static Analysis
|
||||
|
||||
```bash
|
||||
# Run pylint on the application package
|
||||
pylint app/
|
||||
|
||||
# Run pylint on a specific module
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Security Scan
|
||||
|
||||
```bash
|
||||
# Run safety check against the lock file
|
||||
safety check -r requirements.txt --policy-file=safety.policy.yml
|
||||
|
||||
# Save report to file
|
||||
safety check -r requirements.txt --policy-file=safety.policy.yml > safety_report.txt
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Run after every change to `requirements.txt`.
|
||||
- No new high-severity vulnerabilities may be introduced.
|
||||
|
||||
---
|
||||
|
||||
## Local CLI — Service Management
|
||||
|
||||
```bash
|
||||
moviepilot start
|
||||
moviepilot start --timeout 60
|
||||
moviepilot stop
|
||||
moviepilot stop --timeout 30 --force
|
||||
moviepilot restart
|
||||
moviepilot restart --start-timeout 60 --stop-timeout 30
|
||||
moviepilot status
|
||||
moviepilot version
|
||||
```
|
||||
|
||||
```bash
|
||||
moviepilot logs
|
||||
moviepilot logs --lines 100
|
||||
moviepilot logs --stdio
|
||||
moviepilot logs --frontend
|
||||
moviepilot logs --follow
|
||||
moviepilot logs --frontend --follow
|
||||
moviepilot logs --stdio --follow
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Local CLI — Installation and Setup
|
||||
|
||||
```bash
|
||||
# One-line bootstrap installer
|
||||
curl -fsSL https://raw.githubusercontent.com/jxxghp/MoviePilot/v2/scripts/bootstrap-local.sh | bash
|
||||
|
||||
# Install backend dependencies
|
||||
moviepilot install deps
|
||||
moviepilot install deps --python python3.11
|
||||
moviepilot install deps --venv /path/to/venv
|
||||
moviepilot install deps --recreate
|
||||
|
||||
# Install frontend release
|
||||
moviepilot install frontend
|
||||
moviepilot install frontend --version latest
|
||||
moviepilot install frontend --version v2.9.31
|
||||
|
||||
# Install resource files
|
||||
moviepilot install resources
|
||||
|
||||
# Initialize local config
|
||||
moviepilot init
|
||||
moviepilot init --wizard
|
||||
moviepilot init --force-token
|
||||
moviepilot init --superuser admin --superuser-password 'ChangeMe123!'
|
||||
|
||||
# All-in-one setup
|
||||
moviepilot setup
|
||||
moviepilot setup --wizard
|
||||
moviepilot setup --recreate
|
||||
moviepilot setup --superuser admin --superuser-password 'ChangeMe123!'
|
||||
|
||||
# Uninstall
|
||||
moviepilot uninstall
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Local CLI — Update
|
||||
|
||||
```bash
|
||||
moviepilot update backend
|
||||
moviepilot update backend --ref latest
|
||||
moviepilot update backend --ref v2.9.31
|
||||
|
||||
moviepilot update frontend
|
||||
moviepilot update frontend --frontend-version latest
|
||||
|
||||
moviepilot update all
|
||||
moviepilot update all --ref latest --frontend-version latest
|
||||
moviepilot update all --skip-resources
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Local CLI — Startup on Boot
|
||||
|
||||
```bash
|
||||
moviepilot startup status
|
||||
moviepilot startup enable
|
||||
moviepilot startup disable
|
||||
moviepilot startup enable --venv /path/to/venv
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Local CLI — Configuration
|
||||
|
||||
```bash
|
||||
moviepilot config path
|
||||
moviepilot config list
|
||||
moviepilot config list --show-secrets
|
||||
moviepilot config get PORT
|
||||
moviepilot config set PORT 3001
|
||||
moviepilot config keys
|
||||
moviepilot config keys DB_
|
||||
moviepilot config keys --show-current
|
||||
moviepilot config describe PORT
|
||||
moviepilot config describe API_TOKEN --show-secrets
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Local CLI — Tools and Scheduler
|
||||
|
||||
```bash
|
||||
# List all MCP tools
|
||||
moviepilot tool list
|
||||
|
||||
# Show tool parameters
|
||||
moviepilot tool show query_schedulers
|
||||
moviepilot tool show search_torrents
|
||||
|
||||
# Run a tool directly
|
||||
moviepilot tool run query_schedulers
|
||||
moviepilot tool run search_torrents media_type=movie tmdb_id=12345
|
||||
|
||||
# List scheduled tasks
|
||||
moviepilot scheduler list
|
||||
|
||||
# Immediately run a scheduled task
|
||||
moviepilot scheduler run subscribe_refresh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Local CLI — Agent
|
||||
|
||||
```bash
|
||||
moviepilot agent "Help me analyze the last search failure"
|
||||
moviepilot agent --user-id admin "Check the current downloader configuration"
|
||||
moviepilot agent --session cli-debug-1 "Why was the last transfer not triggered?"
|
||||
moviepilot agent --new-session "Summarize any obvious problems with the current system config"
|
||||
```
|
||||
|
||||
**Prerequisites:** `AI_AGENT_ENABLE` must be set to true, and LLM provider settings (`LLM_PROVIDER`, `LLM_MODEL`, `LLM_API_KEY`) must be configured.
|
||||
|
||||
---
|
||||
|
||||
## Local CLI — Help Discovery
|
||||
|
||||
```bash
|
||||
moviepilot --help
|
||||
moviepilot help
|
||||
moviepilot commands
|
||||
moviepilot help install
|
||||
moviepilot help init
|
||||
moviepilot help setup
|
||||
moviepilot help update
|
||||
moviepilot help agent
|
||||
moviepilot help config
|
||||
moviepilot help tool
|
||||
moviepilot help scheduler
|
||||
```
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
219
docs/rules/04-design-patterns.md
Normal file
219
docs/rules/04-design-patterns.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# 04 — Design Patterns
|
||||
|
||||
This document defines the structural patterns used across this codebase. When implementing complex features, you are required to use these patterns rather than inventing new abstractions.
|
||||
|
||||
---
|
||||
|
||||
## 1. Module Pattern (Pluggable Backends)
|
||||
|
||||
**When to use:** Adding a new downloader, media server, message channel, storage backend, or any other capability that requires lifecycle management, configuration switches, priority ordering, or independent testing.
|
||||
|
||||
**Base class:** `_ModuleBase` in `app/modules/__init__.py`
|
||||
|
||||
**Specialized base classes:**
|
||||
- `_DownloaderBase` — for download clients
|
||||
- `_MediaServerBase` — for media servers (implied by existing patterns)
|
||||
|
||||
**Required methods every module must implement:**
|
||||
|
||||
```python
|
||||
class ExampleModule(_ModuleBase, _DownloaderBase):
|
||||
|
||||
def init_module(self) -> None:
|
||||
"""模块初始化"""
|
||||
super().init_service(service_name=..., service_type=...)
|
||||
|
||||
def init_setting(self) -> Tuple[str, Union[str, bool]]:
|
||||
"""返回控制此模块开关的配置项名称和匹配值"""
|
||||
return "DOWNLOADER", "example"
|
||||
|
||||
@staticmethod
|
||||
def get_name() -> str:
|
||||
return "Example"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
return ModuleType.Downloader
|
||||
|
||||
@staticmethod
|
||||
def get_subtype() -> DownloaderType:
|
||||
return DownloaderType.Example
|
||||
|
||||
@staticmethod
|
||||
def get_priority() -> int:
|
||||
return 1
|
||||
|
||||
def test(self) -> Optional[Tuple[bool, str]]:
|
||||
"""测试模块连通性"""
|
||||
...
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
```
|
||||
|
||||
**Module directory convention:** `app/modules/<backend_name>/` containing at minimum `__init__.py` (the module class) and the implementation class.
|
||||
|
||||
**Module types** are defined in `app/schemas/types.py` as `ModuleType`, `DownloaderType`, `MediaServerType`, `MessageChannel`, `StorageSchema`, `OtherModulesType`. When adding a new category, update these enums.
|
||||
|
||||
---
|
||||
|
||||
## 2. Chain Orchestration Pattern
|
||||
|
||||
**When to use:** Adding a new business workflow that is shared across multiple entrypoints (API endpoint, CLI, agent, scheduler, webhook). Chains coordinate modules, helpers, databases, events, and caches.
|
||||
|
||||
**Base class:** `ChainBase` in `app/chain/__init__.py`
|
||||
|
||||
**Calling modules from a chain:**
|
||||
|
||||
```python
|
||||
# Preferred: call via run_module / async_run_module
|
||||
result = self.run_module("method_name", kwarg1=val1, kwarg2=val2)
|
||||
result = await self.async_run_module("method_name", kwarg1=val1)
|
||||
|
||||
# Only use ModuleManager directly when you need to enumerate modules,
|
||||
# inspect instances, or run health checks.
|
||||
```
|
||||
|
||||
**Chain-to-chain calls:** A chain may call another chain to reuse stable domain logic. Avoid introducing new circular dependencies between chains.
|
||||
|
||||
**File convention:** `app/chain/<domain>.py`, class name `<Domain>Chain` (e.g., `DownloadChain`, `SearchChain`, `SubscribeChain`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Event / Observer Pattern
|
||||
|
||||
**When to use:** Triggering cross-cutting reactions (e.g., notifying the media server after a transfer completes, reloading a module after config changes, dispatching user messages to message channels).
|
||||
|
||||
**Core classes:** `EventManager` (singleton instance `eventmanager`) and `Event` in `app/core/event.py`.
|
||||
|
||||
**Registering a handler:**
|
||||
|
||||
```python
|
||||
from app.core.event import eventmanager, Event
|
||||
from app.schemas.types import EventType
|
||||
|
||||
@eventmanager.register(EventType.TransferComplete)
|
||||
def on_transfer_complete(self, event: Event):
|
||||
event_data = event.event_data
|
||||
...
|
||||
```
|
||||
|
||||
**Sending an event:**
|
||||
|
||||
```python
|
||||
eventmanager.send_event(EventType.TransferComplete, data_dict)
|
||||
```
|
||||
|
||||
**Event types** are defined as `EventType` and `ChainEventType` enums in `app/schemas/types.py`. Add new event types there when extending the event system.
|
||||
|
||||
---
|
||||
|
||||
## 4. Repository (Oper) Pattern
|
||||
|
||||
**When to use:** All database reads and writes. Never issue SQLAlchemy queries directly from chain, module, or endpoint code.
|
||||
|
||||
**Convention:** Each SQLAlchemy model in `app/db/models/` has a corresponding `<Model>Oper` class in `app/db/<model>_oper.py`.
|
||||
|
||||
```
|
||||
app/db/models/subscribe.py → app/db/subscribe_oper.py (SubscribeOper)
|
||||
app/db/models/systemconfig.py → app/db/systemconfig_oper.py (SystemConfigOper)
|
||||
app/db/models/transferhistory.py → app/db/transferhistory_oper.py (TransferHistoryOper)
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```python
|
||||
from app.db.subscribe_oper import SubscribeOper
|
||||
|
||||
oper = SubscribeOper()
|
||||
subscribe = oper.get(sid=1)
|
||||
oper.add(Subscribe(name="Example", type="电影"))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Config Reload Pattern
|
||||
|
||||
**When to use:** A chain, module, or helper holds a long-lived object that must be rebuilt when specific configuration keys change (e.g., a downloader client reconnects when its host/port changes).
|
||||
|
||||
**Mixin:** `ConfigReloadMixin` in `app/utils/mixins.py`
|
||||
|
||||
**How it works:**
|
||||
1. Inherit `ConfigReloadMixin`.
|
||||
2. Define a `CONFIG_WATCH` class attribute as a set of config key names.
|
||||
3. Implement `on_config_changed()` — called automatically when any watched key changes.
|
||||
4. Optionally implement `get_reload_name()` to provide a descriptive name for log messages.
|
||||
|
||||
```python
|
||||
class MyChain(ChainBase, ConfigReloadMixin):
|
||||
|
||||
CONFIG_WATCH = {"DOWNLOADER", "QB_HOST", "QB_PORT"}
|
||||
|
||||
def on_config_changed(self):
|
||||
self.init_module()
|
||||
```
|
||||
|
||||
`_ModuleBase` already inherits `ConfigReloadMixin` and calls `init_module()` from `on_config_changed()` by default. Modules typically only need to declare `CONFIG_WATCH`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Singleton Pattern
|
||||
|
||||
**When to use:** Classes that must have exactly one instance shared application-wide (e.g., `EventManager`, `ModuleManager`, `PluginManager`).
|
||||
|
||||
**Implementation:** Inherit from `Singleton` in `app/utils/singleton.py`.
|
||||
|
||||
```python
|
||||
from app.utils.singleton import Singleton
|
||||
|
||||
class MyManager(metaclass=Singleton):
|
||||
...
|
||||
```
|
||||
|
||||
Do not introduce new singletons unless the class genuinely manages global shared state. Prefer dependency injection or parameter passing for everything else.
|
||||
|
||||
---
|
||||
|
||||
## 7. SystemConfig Pattern
|
||||
|
||||
**When to use:** Storing runtime business configuration that is user-editable, persistent across restarts, and not tied to a specific deployment environment.
|
||||
|
||||
**Enum:** `SystemConfigKey` in `app/schemas/types.py`
|
||||
|
||||
**Oper class:** `SystemConfigOper` in `app/db/systemconfig_oper.py`
|
||||
|
||||
```python
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.db.systemconfig_oper import SystemConfigOper
|
||||
|
||||
oper = SystemConfigOper()
|
||||
value = oper.get(SystemConfigKey.RssUrls)
|
||||
oper.set(SystemConfigKey.RssUrls, ["https://..."])
|
||||
```
|
||||
|
||||
**Rule:** Never use raw string literals as SystemConfig keys. Always add a new entry to the `SystemConfigKey` enum first.
|
||||
|
||||
---
|
||||
|
||||
## 8. UserConfig Pattern
|
||||
|
||||
**When to use:** Per-user settings that must survive across sessions but differ by user.
|
||||
|
||||
**Oper class:** `UserConfigOper` in `app/db/userconfig_oper.py`
|
||||
|
||||
Usage mirrors `SystemConfigOper` but scoped to a `user_id`.
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
| Anti-Pattern | Correct Alternative |
|
||||
|---|---|
|
||||
| `module -> chain` coupling | Move shared logic into `chain` or down into `helper` |
|
||||
| `module -> module` direct calls | Use `chain` to orchestrate cross-module workflows |
|
||||
| `helper -> chain` dependency | `helper` must remain a low-level utility; move orchestration to `chain` |
|
||||
| Raw SQLAlchemy queries in endpoints or chains | Use the corresponding `*_oper.py` class |
|
||||
| Raw string keys for SystemConfig | Define and use a `SystemConfigKey` enum entry |
|
||||
| HTTP requests via `requests` or `httpx` directly | Use `RequestUtils` from `app/utils/http.py` |
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
169
docs/rules/05-architecture.md
Normal file
169
docs/rules/05-architecture.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# 05 — Architecture and Modules
|
||||
|
||||
## Layer Overview
|
||||
|
||||
The application is structured as four distinct layers. Each layer has a defined responsibility, and dependency may only flow in permitted directions.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ Entrypoints │
|
||||
│ (API Endpoints / CLI / Agent / Scheduler / │
|
||||
│ Webhook / Message Interaction) │
|
||||
└────────────────────┬─────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ Chain Layer (app/chain/) │
|
||||
│ Business orchestration: search, download, │
|
||||
│ subscribe, transfer, message, recommend, etc. │
|
||||
└──────┬──────────────┬───────────────┬────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────────┐ ┌──────────┐ ┌────────────────┐
|
||||
│ Module │ │ Helper │ │ DB / Oper │
|
||||
│ Layer │ │ Layer │ │ Layer │
|
||||
│ (app/ │ │ (app/ │ │ (app/db/) │
|
||||
│ modules/) │ │ helper/)│ │ │
|
||||
└────────────┘ └──────────┘ └────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Layer Responsibilities and Boundaries
|
||||
|
||||
### Entrypoint Layer
|
||||
|
||||
**Directories:** `app/api/endpoints/`, `moviepilot` (CLI), `app/agent/`, scheduler callbacks, webhook handlers, message interactions.
|
||||
|
||||
**Responsibilities:**
|
||||
- HTTP concerns: authentication, parameter parsing, response model serialization, streaming adaptation, simple input validation.
|
||||
- Simple list, detail, toggle, settings read/write, and pure CRUD endpoints may call `app/db/` or a helper directly.
|
||||
- Any logic that coordinates multiple modules, triggers events, touches caches, or combines workflows must be moved into `chain`.
|
||||
|
||||
**Rules:**
|
||||
- Prefer adding new endpoints to an existing domain file. Create a new endpoint file only when introducing a new top-level resource domain.
|
||||
- After adding a new endpoint, register it in `app/api/apiv1.py`.
|
||||
- Endpoints must not contain business logic that belongs in `chain`.
|
||||
|
||||
---
|
||||
|
||||
### Chain Layer
|
||||
|
||||
**Directory:** `app/chain/`
|
||||
|
||||
**Responsibilities:**
|
||||
- Business orchestration shared by API, CLI, agent, scheduler, and other entrypoints.
|
||||
- Composes module capabilities, helpers, database access, events, and caches.
|
||||
- Focuses on use cases and workflows.
|
||||
|
||||
**Rules:**
|
||||
- Call module capabilities via `run_module()` or `async_run_module()`. Use `ModuleManager` directly only when enumerating, inspecting, or running health checks.
|
||||
- Do not hold low-level protocol details, HTTP request objects, or page-specific parameter assembly.
|
||||
- Before creating a new chain file, verify the workflow is genuinely reused across multiple entrypoints, or coordinates multiple modules. If it is short logic for a single endpoint, keep it in the endpoint.
|
||||
- Chain-to-chain calls are allowed when reusing stable domain logic. Avoid introducing new circular dependencies.
|
||||
|
||||
---
|
||||
|
||||
### Module Layer
|
||||
|
||||
**Directory:** `app/modules/`
|
||||
|
||||
**Responsibilities:**
|
||||
- Pluggable capability implementations: downloaders, media servers, message channels, metadata sources, storage backends, subtitle backends, filter backends, etc.
|
||||
- Manages lifecycle (init, stop), configuration switches, priority ordering, and independent testability.
|
||||
|
||||
**Module categories (defined in `app/schemas/types.py`):**
|
||||
|
||||
| Enum | Examples |
|
||||
|---|---|
|
||||
| `ModuleType.Downloader` | qBittorrent, Transmission, rTorrent |
|
||||
| `ModuleType.MediaServer` | Emby, Jellyfin, Plex, TrimMedia, Zspace, Ugreen |
|
||||
| `ModuleType.MessageChannel` | Telegram, WeChat, Feishu, Slack, Discord |
|
||||
| `ModuleType.MetaData` | TMDB, TheTVDB, Douban, Bangumi, Fanart |
|
||||
| `ModuleType.Indexer` | Site-specific torrent indexers |
|
||||
| `ModuleType.Storage` | Alist, rclone, u115, local storage |
|
||||
|
||||
**Rules:**
|
||||
- A module must focus on one backend or one capability. It returns domain result objects, not HTTP responses, and must not depend on FastAPI request objects or endpoint auth.
|
||||
- Do not add direct `module → module` coupling for new code. Cross-module orchestration must go through `chain`.
|
||||
- Do not expand the historical `module → chain` usage pattern. If a module needs shared business logic, move that logic into `chain` or down into `helper`.
|
||||
|
||||
---
|
||||
|
||||
### Helper Layer
|
||||
|
||||
**Directory:** `app/helper/`
|
||||
|
||||
**Responsibilities:**
|
||||
- Reusable low-level support: path handling, config aggregation, site index loading, protocol wrappers, rate limiting, cache utilities, page parsing, notification helpers.
|
||||
|
||||
**Rules:**
|
||||
- Add a new helper only when the logic is reused in multiple places, or it is clearly a standalone low-level concern.
|
||||
- If logic is used only by a single chain or module, keep it in the original file. Do not turn `helper` into a dumping ground.
|
||||
- If the code needs configuration switches, runtime loading, priorities, or multi-implementation dispatch, it is a `module`, not a `helper`.
|
||||
- `helper` must not contain full business workflows.
|
||||
|
||||
---
|
||||
|
||||
### DB / Oper Layer
|
||||
|
||||
**Directory:** `app/db/`
|
||||
|
||||
**Responsibilities:**
|
||||
- SQLAlchemy models under `app/db/models/`.
|
||||
- Data access wrappers (`*_oper.py`) that encapsulate all database queries.
|
||||
|
||||
**Rules:**
|
||||
- Never issue SQLAlchemy queries directly from chain, module, or endpoint code. Always use the corresponding `*_oper.py` class.
|
||||
- Any schema change requires a new Alembic migration under `database/versions/`.
|
||||
|
||||
---
|
||||
|
||||
## Permitted Call Directions
|
||||
|
||||
| Direction | Status |
|
||||
|---|---|
|
||||
| `endpoint / CLI / agent / scheduler → chain` | ✅ Preferred |
|
||||
| `endpoint / CLI / agent / scheduler → db / helper` | ✅ Allowed for simple CRUD and input normalization only |
|
||||
| `chain → chain` | ✅ Allowed when reusing stable, non-circular domain logic |
|
||||
| `chain → module` | ✅ Via `run_module()` / `async_run_module()` |
|
||||
| `chain → helper` | ✅ Allowed |
|
||||
| `chain → db` | ✅ Via `*_oper.py` classes |
|
||||
| `module → chain` | ⚠️ Exists in legacy code; do not expand in new code |
|
||||
| `module → module` | ❌ Forbidden in new code |
|
||||
| `helper → chain` | ❌ Forbidden |
|
||||
| `helper → endpoint` | ❌ Forbidden |
|
||||
|
||||
---
|
||||
|
||||
## Key File Locations
|
||||
|
||||
| Path | Purpose |
|
||||
|---|---|
|
||||
| `app/api/apiv1.py` | API router registration — register new endpoints here |
|
||||
| `app/core/config.py` | `ConfigModel` and `Settings` — all deployment/env-level config |
|
||||
| `app/schemas/types.py` | `SystemConfigKey`, `EventType`, `ModuleType`, and all shared enums |
|
||||
| `app/core/module.py` | `ModuleManager` — discovers and manages module instances |
|
||||
| `app/core/plugin.py` | `PluginManager` — discovers and manages plugin instances |
|
||||
| `app/core/event.py` | `EventManager` + `Event` — the application event bus |
|
||||
| `app/core/context.py` | `Context`, `MediaInfo`, `TorrentInfo` — shared domain context objects |
|
||||
| `app/main.py` | Application startup and FastAPI instance |
|
||||
| `database/versions/` | Alembic migration scripts |
|
||||
|
||||
---
|
||||
|
||||
## Where New Capabilities Go
|
||||
|
||||
| Scenario | Action |
|
||||
|---|---|
|
||||
| New business workflow shared by multiple entrypoints | `app/chain/` |
|
||||
| New downloader, media server, message channel, or storage backend | `app/modules/<backend>/` |
|
||||
| New public HTTP API endpoint | `app/api/endpoints/`, register in `app/api/apiv1.py` |
|
||||
| New low-level utility reused in multiple places | `app/helper/` |
|
||||
| New deployment/env/startup config (ports, paths, API keys) | `ConfigModel` in `app/core/config.py` |
|
||||
| New runtime business config, user-editable rule, or persistent system option | `SystemConfigKey` + `SystemConfigOper` |
|
||||
| Config change should reload a long-lived object | Add `CONFIG_WATCH` + `on_config_changed()` to the relevant class |
|
||||
| Few dozen lines of private logic in one chain or module | Private function in the same file; do not create a new helper |
|
||||
| New module category or subtype | Also update `app/schemas/types.py` |
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
121
docs/rules/06-code-styles.md
Normal file
121
docs/rules/06-code-styles.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# 06 — Code Standards and Style
|
||||
|
||||
## General Principles
|
||||
|
||||
- Preserve the style of the surrounding file. When in doubt, read neighboring code first.
|
||||
- Prefer the smallest correct change. Do not introduce a new abstraction layer without a clear payoff.
|
||||
- Do not add features, refactors, or abstractions beyond what the task requires.
|
||||
- Do not add error handling or validation for scenarios that cannot happen. Trust internal code and framework guarantees; only validate at system boundaries (user input, external API responses).
|
||||
|
||||
---
|
||||
|
||||
## Python Version and Typing
|
||||
|
||||
- Target: **Python 3.11+**. CI runs Python 3.12.
|
||||
- **Type annotations are required** on all public methods and function signatures.
|
||||
- Use `Optional[X]` for nullable types (do not use `X | None` — keep consistency with the existing codebase style).
|
||||
- Use `Union[X, Y]` for multi-type parameters.
|
||||
- Prefer `list[X]`, `dict[K, V]`, `tuple[X, Y]` built-in generics in new code (Python 3.9+); match the style of the surrounding file.
|
||||
- Use `pathlib.Path` for all file path operations. Never use raw string concatenation for paths.
|
||||
|
||||
---
|
||||
|
||||
## Pydantic Models
|
||||
|
||||
- All request body and response models must be defined as Pydantic `BaseModel` subclasses in `app/schemas/`.
|
||||
- Use `Field(...)` for required fields; use `Field(default=...)` or `Field(None)` for optional fields.
|
||||
- Do not define ad-hoc `dict` return types for API responses — define a schema class.
|
||||
- Settings and deployment configuration live in `ConfigModel` / `Settings` in `app/core/config.py` using `pydantic-settings`.
|
||||
- Use `model_validator` for cross-field validation logic.
|
||||
|
||||
---
|
||||
|
||||
## Async and Concurrency
|
||||
|
||||
- Prefer `async def` for I/O-bound operations (network requests, database queries, file operations).
|
||||
- Use `await` consistently; do not mix sync and async code paths in the same function without using `run_in_threadpool` from FastAPI or `asyncio.to_thread`.
|
||||
- For CPU-bound work that must not block the event loop, submit to `ThreadHelper` (see `app/helper/thread.py`).
|
||||
- Do not use bare `threading.Thread` in new code; use `ThreadHelper.submit()`.
|
||||
|
||||
---
|
||||
|
||||
## Imports
|
||||
|
||||
Order imports as follows, separated by blank lines:
|
||||
|
||||
1. Standard library (`import os`, `import json`, etc.)
|
||||
2. Third-party packages (`from fastapi import ...`, `from pydantic import ...`)
|
||||
3. Local application packages (`from app.chain import ...`, `from app.schemas import ...`)
|
||||
|
||||
Within each group, sort alphabetically. Do not use wildcard imports (`from module import *`) in application code.
|
||||
|
||||
---
|
||||
|
||||
## String Formatting
|
||||
|
||||
- Use **f-strings** for all string interpolation. Do not use `%` formatting or `.format()`.
|
||||
- For log messages, use `logger.info(f"...")` — do not use lazy `%s` format in logger calls (the project does not rely on lazy evaluation here).
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
- In **chain and module layers**: do not raise HTTP exceptions. Catch exceptions, log them, and return `None` or a domain-level error object so the caller can decide how to proceed.
|
||||
- In **endpoint layer**: use FastAPI's `HTTPException` or the project's standard response schemas for errors.
|
||||
- Never swallow exceptions silently. At minimum log the error with `logger.error(f"...: {str(err)}")`.
|
||||
- Do not use bare `except:` — always catch a specific exception type or at minimum `Exception`.
|
||||
|
||||
```python
|
||||
# Correct
|
||||
try:
|
||||
result = self.do_work()
|
||||
except Exception as err:
|
||||
logger.error(f"Failed to do work: {str(err)}")
|
||||
return None
|
||||
|
||||
# Wrong — swallowing silently
|
||||
try:
|
||||
result = self.do_work()
|
||||
except:
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logging
|
||||
|
||||
- Use `logger` from `app/log.py`. Do not import the standard library `logging` directly in application code.
|
||||
- Log levels:
|
||||
- `logger.debug(...)` — detailed diagnostic information, disabled by default.
|
||||
- `logger.info(...)` — normal operational events.
|
||||
- `logger.warning(...)` — unexpected but recoverable situations.
|
||||
- `logger.error(...)` — failures that affect functionality.
|
||||
- Keep log messages in Chinese unless the surrounding file consistently uses English.
|
||||
|
||||
---
|
||||
|
||||
## Constants and Magic Values
|
||||
|
||||
- Do not scatter raw string keys for `SystemConfig`. Add a `SystemConfigKey` enum entry and reference it.
|
||||
- Do not use magic numbers or magic strings inline. Define a named constant or enum value.
|
||||
|
||||
---
|
||||
|
||||
## File Organization
|
||||
|
||||
- One primary class per file is the norm for chains, modules, and helpers.
|
||||
- Private helper functions in the same file are preferable to extracting a new helper for single-use logic.
|
||||
- Keep files focused on one domain concern.
|
||||
|
||||
---
|
||||
|
||||
## What Not To Do
|
||||
|
||||
- Do not introduce new third-party libraries without updating `requirements.in` and running `pip-compile`.
|
||||
- Do not use `requests` or `httpx` directly for external HTTP calls — use `RequestUtils` from `app/utils/http.py`.
|
||||
- Do not issue raw SQLAlchemy queries from chains, modules, or endpoints — use the `*_oper.py` classes.
|
||||
- Do not add TODO or FIXME without context. Only keep one if it is genuinely deferred and cannot be addressed in the current task.
|
||||
- Do not add noisy markers like `# change starts here`, `# important`, or `# this is a fix`.
|
||||
- Do not write comments that restate what the code already clearly says.
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
102
docs/rules/07-naming-conventions.md
Normal file
102
docs/rules/07-naming-conventions.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# 07 — Naming Conventions
|
||||
|
||||
All new code must follow these conventions. Consistent naming is how the codebase communicates intent without comments.
|
||||
|
||||
---
|
||||
|
||||
## Files
|
||||
|
||||
| Context | Convention | Examples |
|
||||
|---|---|---|
|
||||
| Python source files | `snake_case.py` | `download_chain.py`, `qbittorrent.py` |
|
||||
| Module package directories | `snake_case/` | `qbittorrent/`, `synologychat/` |
|
||||
| Test files | `test_<domain>.py` | `test_download_chain.py`, `test_subscribe_endpoint.py` |
|
||||
| Alembic migrations | Auto-generated by Alembic; do not rename | `20240101_add_column.py` |
|
||||
| Skill directories | `<kebab-case>/` | `transfer-failed-retry/`, `moviepilot-cli/` |
|
||||
|
||||
---
|
||||
|
||||
## Classes
|
||||
|
||||
| Context | Convention | Examples |
|
||||
|---|---|---|
|
||||
| Chain classes | `<Domain>Chain` | `DownloadChain`, `SearchChain`, `SubscribeChain` |
|
||||
| Module classes | `<Backend>Module` | `QbittorrentModule`, `EmbyModule`, `TelegramModule` |
|
||||
| Oper (data access) classes | `<Model>Oper` | `SubscribeOper`, `SystemConfigOper`, `TransferHistoryOper` |
|
||||
| Helper classes | `<Domain>Helper` | `TorrentHelper`, `DirectoryHelper`, `MessageHelper` |
|
||||
| Pydantic schema models | `PascalCase`, noun-focused | `MediaInfo`, `TorrentInfo`, `DownloadingTorrent` |
|
||||
| SQLAlchemy model classes | `PascalCase`, singular noun | `Subscribe`, `TransferHistory`, `SystemConfig` |
|
||||
| Enum classes | `PascalCase` | `MediaType`, `EventType`, `ModuleType` |
|
||||
| Manager classes | `<Domain>Manager` | `ModuleManager`, `PluginManager`, `EventManager` |
|
||||
| General classes | `PascalCase` | `MetaInfo`, `Context`, `ChainBase` |
|
||||
|
||||
---
|
||||
|
||||
## Functions and Methods
|
||||
|
||||
| Context | Convention | Examples |
|
||||
|---|---|---|
|
||||
| All functions and methods | `snake_case` | `get_subscribe`, `run_module`, `on_config_changed` |
|
||||
| Private methods | `_snake_case` (leading underscore) | `_submit_download_added_task`, `_parse_result` |
|
||||
| Event handler methods | `on_<event_name>` or descriptive | `on_transfer_complete`, `handle_config_changed` |
|
||||
| Module interface methods | Match `_ModuleBase` contract | `init_module`, `init_setting`, `get_name`, `get_type`, `test`, `stop` |
|
||||
| Oper methods | Verb + noun | `get`, `add`, `update`, `delete`, `list` |
|
||||
|
||||
---
|
||||
|
||||
## Variables and Parameters
|
||||
|
||||
| Context | Convention | Examples |
|
||||
|---|---|---|
|
||||
| Local variables | `snake_case` | `torrent_info`, `media_type`, `download_dir` |
|
||||
| Instance attributes | `snake_case` | `self.download_history`, `self.config` |
|
||||
| Constants (module-level) | `UPPER_SNAKE_CASE` | `DEFAULT_EVENT_PRIORITY`, `MIN_EVENT_CONSUMER_THREADS` |
|
||||
| Private variables | `_snake_case` (leading underscore) | `_instance`, `_lock` |
|
||||
| Type variables | `PascalCase` with `TypeVar` | `T = TypeVar("T")` |
|
||||
|
||||
---
|
||||
|
||||
## Enums
|
||||
|
||||
| Context | Convention | Examples |
|
||||
|---|---|---|
|
||||
| Enum class name | `PascalCase` | `MediaType`, `TorrentStatus`, `EventType` |
|
||||
| Enum members | `PascalCase` (for complex enums) | `MediaType.MOVIE`, `EventType.TransferComplete` |
|
||||
| String enum values | Match the domain language | `MediaType.MOVIE = '电影'`, `TorrentStatus.TRANSFER = '可转移'` |
|
||||
| `SystemConfigKey` values | Match the config key as a string | `SystemConfigKey.RssUrls = "RssUrls"` |
|
||||
|
||||
---
|
||||
|
||||
## Configuration and Settings
|
||||
|
||||
| Context | Convention | Examples |
|
||||
|---|---|---|
|
||||
| `Settings` / `ConfigModel` fields | `UPPER_SNAKE_CASE` | `API_TOKEN`, `LLM_MODEL`, `QB_HOST` |
|
||||
| `SystemConfigKey` enum members | `PascalCase` | `SystemConfigKey.RssUrls`, `SystemConfigKey.SubscribeFilter` |
|
||||
| Environment variable names | `UPPER_SNAKE_CASE` | `AI_AGENT_ENABLE`, `DB_TYPE` |
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints and Routers
|
||||
|
||||
| Context | Convention | Examples |
|
||||
|---|---|---|
|
||||
| Endpoint function names | `snake_case`, verb-first | `get_subscribe_list`, `add_download`, `delete_history` |
|
||||
| URL path segments | `kebab-case` or `snake_case` matching existing patterns | `/api/v1/subscribe`, `/api/v1/transfer/history` |
|
||||
| Router tags | Match the resource domain name | `"subscribe"`, `"download"`, `"media"` |
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Wrong | Correct |
|
||||
|---|---|
|
||||
| `class downloadchain:` | `class DownloadChain:` |
|
||||
| `class QBModule:` | `class QbittorrentModule:` |
|
||||
| `def GetSubscribe():` | `def get_subscribe():` |
|
||||
| `TORRENT_info = ...` | `torrent_info = ...` |
|
||||
| `def handleConfigChanged():` | `def on_config_changed():` or `def handle_config_changed():` |
|
||||
| `SystemConfigOper().get("RssUrls")` | `SystemConfigOper().get(SystemConfigKey.RssUrls)` |
|
||||
| `class subscribe_oper:` | `class SubscribeOper:` |
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
140
docs/rules/08-comment-styles.md
Normal file
140
docs/rules/08-comment-styles.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# 08 — Comments and Documentation Style
|
||||
|
||||
## ⚠️ Mandatory Gate
|
||||
|
||||
All **public classes**, **public methods**, and **public functions** in this project must have Chinese docstrings. Code submitted without compliant docstrings on public interfaces will be **rejected at review**. No exceptions.
|
||||
|
||||
"Public" means anything not prefixed with `_`. This includes all methods on `ChainBase` subclasses, `_ModuleBase` subclasses, Pydantic schema classes, and endpoint functions.
|
||||
|
||||
---
|
||||
|
||||
## Docstring Format
|
||||
|
||||
### Single-line (for simple, obvious descriptions)
|
||||
|
||||
```python
|
||||
def get_name() -> str:
|
||||
"""获取模块名称"""
|
||||
return "Qbittorrent"
|
||||
```
|
||||
|
||||
### Multi-line (for methods with parameters, return values, or non-obvious behavior)
|
||||
|
||||
```python
|
||||
def download(
|
||||
self,
|
||||
context: Context,
|
||||
torrent: TorrentInfo,
|
||||
download_dir: Path,
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
添加下载任务到下载器。
|
||||
|
||||
:param context: 当前媒体上下文,包含识别结果和种子选择信息
|
||||
:param torrent: 要下载的种子信息
|
||||
:param download_dir: 目标保存目录
|
||||
:return: 成功时返回下载任务 ID,失败时返回 None
|
||||
"""
|
||||
...
|
||||
```
|
||||
|
||||
### Class docstrings
|
||||
|
||||
```python
|
||||
class DownloadChain(ChainBase):
|
||||
"""
|
||||
下载处理链,负责协调搜索结果的种子选择、下载器调度和下载后处理。
|
||||
"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Docstring Language Rule
|
||||
|
||||
- **Default:** Chinese.
|
||||
- **Exception:** If the surrounding file is entirely and consistently in English, match the local style.
|
||||
- Do not mix languages within a single docstring. Pick one and stay consistent for the whole file.
|
||||
|
||||
---
|
||||
|
||||
## Inline Comments
|
||||
|
||||
**Only add an inline or block comment when the WHY is non-obvious.** Good reasons to add a comment:
|
||||
|
||||
- A hidden external constraint (e.g., "this API returns stale data for up to 60 seconds after update")
|
||||
- A subtle invariant the code must maintain
|
||||
- A workaround for a specific third-party bug
|
||||
- Call ordering or initialization requirements that are not apparent from the code
|
||||
- Compatibility reasons with a specific client version or protocol
|
||||
|
||||
**Do not add a comment when:**
|
||||
|
||||
- The code already explains itself through well-named identifiers
|
||||
- The comment would just restate what the code does in words
|
||||
- The logic is straightforward branching or assignment
|
||||
|
||||
---
|
||||
|
||||
## Correct Examples
|
||||
|
||||
```python
|
||||
# qBittorrent API 在添加种子后立即查询时可能返回空,需要短暂等待
|
||||
time.sleep(0.5)
|
||||
result = self.client.get_torrent(hash_id)
|
||||
```
|
||||
|
||||
```python
|
||||
# 此处必须先检查 module 是否已初始化,否则多线程并发调用时 get_instances() 可能返回空列表
|
||||
if not self._initialized:
|
||||
self.init_module()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Incorrect Examples
|
||||
|
||||
```python
|
||||
# 获取订阅列表 ← 这只是在重述代码,不需要
|
||||
subscribes = SubscribeOper().list()
|
||||
|
||||
# 如果 result 为 None 则返回 ← 无意义
|
||||
if result is None:
|
||||
return None
|
||||
|
||||
# change starts here ← 噪音,禁止
|
||||
# fix: handle edge case ← 噪音,改成提交信息里写
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comment Placement
|
||||
|
||||
- Place block comments **above** the code they describe, not on the same line.
|
||||
- Use same-line end-of-line comments only for very short clarifications (e.g., unit of a constant).
|
||||
- For long explanations, prefer a block comment above the code rather than a multiline end-of-line comment.
|
||||
|
||||
```python
|
||||
# 优先使用已有的下载目录映射,避免重复计算路径
|
||||
effective_dir = self._resolve_download_dir(torrent) or download_dir
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stale Comment Rule
|
||||
|
||||
When modifying code, update or remove any comment that no longer accurately describes the implementation. A stale comment is worse than no comment — it actively misleads future readers.
|
||||
|
||||
---
|
||||
|
||||
## Prohibited Patterns
|
||||
|
||||
| Pattern | Why |
|
||||
|---|---|
|
||||
| `# change starts here` / `# change ends here` | Editorial noise; belongs in git history, not source |
|
||||
| `# TODO` without context or assignee | Accepted only when the deferral is genuinely unavoidable and the reason is documented |
|
||||
| `# FIXME` left in submitted code | Fix it now or document exactly why it cannot be fixed |
|
||||
| `# this is important` | Every line of code is important; this adds nothing |
|
||||
| Commented-out dead code | Delete it; git history preserves it |
|
||||
| Docstrings in English on new public interfaces | Violation of the mandatory Chinese docstring gate |
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
174
docs/rules/09-external-response.md
Normal file
174
docs/rules/09-external-response.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 09 — External APIs, Protocols, and Responses
|
||||
|
||||
## HTTP Client Conventions
|
||||
|
||||
**Rule:** All outbound HTTP requests must go through `RequestUtils` from `app/utils/http.py`. Do not use `requests`, `httpx`, or `aiohttp` directly.
|
||||
|
||||
`RequestUtils` handles:
|
||||
- Proxy configuration (from `settings.PROXY_*`)
|
||||
- Timeouts
|
||||
- SSL verification settings
|
||||
- User-Agent headers
|
||||
- Retry logic
|
||||
|
||||
```python
|
||||
from app.utils.http import RequestUtils
|
||||
|
||||
res = RequestUtils(
|
||||
ua=settings.USER_AGENT,
|
||||
proxies=settings.PROXY,
|
||||
timeout=30,
|
||||
).get_res(url="https://api.example.com/data")
|
||||
|
||||
if res and res.status_code == 200:
|
||||
data = res.json()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Response Format — REST API
|
||||
|
||||
All REST API responses use Pydantic schema models from `app/schemas/`. Do not return raw `dict` objects from endpoints.
|
||||
|
||||
### Standard Response Patterns
|
||||
|
||||
```python
|
||||
# Success with data
|
||||
from app.schemas.response import Response
|
||||
|
||||
return Response(success=True, message="", data=result)
|
||||
|
||||
# Success without data
|
||||
return Response(success=True, message="操作成功")
|
||||
|
||||
# Error
|
||||
return Response(success=False, message="错误原因描述")
|
||||
```
|
||||
|
||||
### List Responses
|
||||
|
||||
For paginated lists, follow the pattern of existing endpoint files. Check `app/api/endpoints/` for examples matching the resource domain.
|
||||
|
||||
### Error Responses (Endpoint Layer Only)
|
||||
|
||||
In endpoints, raise `HTTPException` for request-level errors:
|
||||
|
||||
```python
|
||||
from fastapi import HTTPException
|
||||
|
||||
raise HTTPException(status_code=404, detail="Resource not found")
|
||||
raise HTTPException(status_code=403, detail="Permission denied")
|
||||
```
|
||||
|
||||
Do not raise `HTTPException` in chain or module code. Chains and modules return `None` or domain-level error objects on failure; the endpoint translates that into an HTTP response.
|
||||
|
||||
---
|
||||
|
||||
## Error Handling by Layer
|
||||
|
||||
| Layer | On external API failure |
|
||||
|---|---|
|
||||
| Module | Log the error, return `None` or `(False, "error message")` tuple |
|
||||
| Chain | Log the error, return `None` or an appropriate domain object with failure indication |
|
||||
| Endpoint | Translate `None` or failure result into a `Response(success=False, ...)` or `HTTPException` |
|
||||
|
||||
```python
|
||||
# Module layer
|
||||
def test(self) -> Optional[Tuple[bool, str]]:
|
||||
"""测试模块连通性"""
|
||||
try:
|
||||
ok = self.client.ping()
|
||||
return (True, "连接成功") if ok else (False, "连接失败")
|
||||
except Exception as err:
|
||||
logger.error(f"测试连通性失败:{str(err)}")
|
||||
return (False, str(err))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Protocol
|
||||
|
||||
MoviePilot exposes an MCP (Model Context Protocol) interface for AI agent integration.
|
||||
|
||||
- **Transport:** HTTP, JSON-RPC 2.0
|
||||
- **Base path:** `/api/v1/mcp`
|
||||
- **Protocol versions supported:** `2025-11-25`, `2025-06-18`, `2024-11-05`
|
||||
|
||||
### Authentication
|
||||
|
||||
```
|
||||
Header: X-API-KEY: <api_key>
|
||||
Query: ?apikey=<api_key>
|
||||
```
|
||||
|
||||
### Supported Methods
|
||||
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `initialize` | Initialize session, negotiate protocol version and capabilities |
|
||||
| `notifications/initialized` | Client confirmation of initialization |
|
||||
| `tools/list` | List all available tools |
|
||||
| `tools/call` | Invoke a specific tool |
|
||||
| `ping` | Connection liveness check |
|
||||
|
||||
### Error Codes
|
||||
|
||||
| Code | Message | Meaning |
|
||||
|---|---|---|
|
||||
| -32700 | Parse error | Malformed JSON |
|
||||
| -32600 | Invalid Request | Invalid JSON-RPC request structure |
|
||||
| -32601 | Method not found | Unknown method |
|
||||
| -32602 | Invalid params | Parameter validation failure |
|
||||
| -32002 | Session not found | Session does not exist or has expired |
|
||||
| -32003 | Not initialized | Session has not completed initialization |
|
||||
| -32603 | Internal error | Server-side error |
|
||||
|
||||
### Tool Response Format
|
||||
|
||||
MCP tools return structured content. Errors must use the JSON-RPC error object format, not HTTP status codes.
|
||||
|
||||
---
|
||||
|
||||
## Notification and Messaging
|
||||
|
||||
Internal notifications use the `Notification` schema and the event system:
|
||||
|
||||
```python
|
||||
from app.schemas import Notification
|
||||
from app.schemas.types import NotificationType, MessageChannel
|
||||
from app.core.event import eventmanager
|
||||
from app.schemas.types import EventType
|
||||
|
||||
eventmanager.send_event(
|
||||
EventType.NoticeMessage,
|
||||
{
|
||||
"channel": MessageChannel.Telegram,
|
||||
"type": NotificationType.Download,
|
||||
"title": "下载成功",
|
||||
"text": f"{media_name} 已添加到下载队列",
|
||||
"image": poster_url,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Do not call message channel modules directly from chain code. Use the event bus to decouple senders from channels.
|
||||
|
||||
---
|
||||
|
||||
## Media Metadata API Conventions
|
||||
|
||||
When calling TMDB, TheTVDB, Douban, or Bangumi via the module layer:
|
||||
|
||||
- Always check the module return for `None` before using the result — modules return `None` when the backend is not configured or the request fails.
|
||||
- Cache responses using `FileCache` / `AsyncFileCache` where the result is stable and repeated requests would be expensive.
|
||||
- Return domain objects (`MediaInfo`, `TmdbEpisode`, `MediaPerson`, etc.) from modules, never raw API response dicts.
|
||||
|
||||
---
|
||||
|
||||
## Webhook Handling
|
||||
|
||||
Webhook payloads arrive at `app/api/endpoints/webhook.py` and are dispatched via `eventmanager.send_event(EventType.WebhookMessage, ...)`. Processing logic lives in the chain layer (`app/chain/webhook.py`).
|
||||
|
||||
Do not add webhook-specific business logic directly in the endpoint. The endpoint parses the payload and fires the event; the chain handles the response.
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
177
docs/rules/10-data-and-persistent.md
Normal file
177
docs/rules/10-data-and-persistent.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# 10 — Data and Persistent Management
|
||||
|
||||
## Database Models
|
||||
|
||||
**Location:** `app/db/models/`
|
||||
|
||||
Models are SQLAlchemy declarative classes. Each model maps to one database table.
|
||||
|
||||
| Model | Table Domain |
|
||||
|---|---|
|
||||
| `Subscribe` | Media subscriptions |
|
||||
| `SubscribeHistory` | Completed subscription records |
|
||||
| `TransferHistory` | File transfer history |
|
||||
| `DownloadHistory` / `DownloadFiles` | Download task history and file list |
|
||||
| `MediaServerItem` | Media server library item cache |
|
||||
| `SystemConfig` | Runtime key-value configuration store |
|
||||
| `UserConfig` | Per-user configuration store |
|
||||
| `User` | User accounts |
|
||||
| `Site` / `SiteIcon` / `SiteStatistic` / `SiteUserData` | Torrent site records and statistics |
|
||||
| `Message` | Message log |
|
||||
| `PluginData` | Plugin-persisted data |
|
||||
| `PassKey` | Passkey authentication records |
|
||||
| `Workflow` | Workflow definitions |
|
||||
|
||||
---
|
||||
|
||||
## Alembic Migrations
|
||||
|
||||
**Location:** `database/versions/`
|
||||
|
||||
**Rule:** Any change to a SQLAlchemy model schema (adding a column, renaming a column, changing a column type, adding a table, removing a table) **requires a new Alembic migration script**. Never update models without a corresponding migration.
|
||||
|
||||
**Generating a migration:**
|
||||
|
||||
```bash
|
||||
# Auto-generate from model diff
|
||||
alembic revision --autogenerate -m "describe the change"
|
||||
|
||||
# Create a blank migration for manual SQL
|
||||
alembic revision -m "describe the change"
|
||||
```
|
||||
|
||||
**Review the auto-generated migration before committing** — auto-generation can miss nullable changes, index modifications, or SQLite-incompatible operations.
|
||||
|
||||
---
|
||||
|
||||
## Data Access Layer (Oper Pattern)
|
||||
|
||||
**Location:** `app/db/`
|
||||
|
||||
Each model has a corresponding `*_oper.py` file containing the data access class. Do not write SQLAlchemy queries directly in chain, module, or endpoint code.
|
||||
|
||||
| Oper Class | File |
|
||||
|---|---|
|
||||
| `SubscribeOper` | `subscribe_oper.py` |
|
||||
| `SystemConfigOper` | `systemconfig_oper.py` |
|
||||
| `TransferHistoryOper` | `transferhistory_oper.py` |
|
||||
| `DownloadHistoryOper` | `downloadhistory_oper.py` |
|
||||
| `MediaServerOper` | `mediaserver_oper.py` |
|
||||
| `UserOper` | `user_oper.py` |
|
||||
| `UserConfigOper` | `userconfig_oper.py` |
|
||||
| `MessageOper` | `message_oper.py` |
|
||||
| `SiteOper` | `site_oper.py` |
|
||||
| `PluginDataOper` | `plugindata_oper.py` |
|
||||
| `WorkflowOper` | `workflow_oper.py` |
|
||||
|
||||
**Standard Oper method conventions:**
|
||||
|
||||
```python
|
||||
oper = SubscribeOper()
|
||||
subscribe = oper.get(sid=1) # Get by primary key or filter
|
||||
subscribes = oper.list() # List all
|
||||
oper.add(Subscribe(...)) # Insert
|
||||
oper.update(sid=1, name="New Name") # Update by key
|
||||
oper.delete(sid=1) # Delete by key
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SystemConfig — Runtime Configuration
|
||||
|
||||
**Purpose:** Runtime business configuration that is user-editable, persisted in the database, and survives application restarts.
|
||||
|
||||
**Enum:** `SystemConfigKey` in `app/schemas/types.py`
|
||||
|
||||
**Oper:** `SystemConfigOper` in `app/db/systemconfig_oper.py`
|
||||
|
||||
```python
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.db.systemconfig_oper import SystemConfigOper
|
||||
|
||||
oper = SystemConfigOper()
|
||||
|
||||
# Read
|
||||
rss_urls = oper.get(SystemConfigKey.RssUrls)
|
||||
|
||||
# Write
|
||||
oper.set(SystemConfigKey.RssUrls, ["https://example.com/rss"])
|
||||
```
|
||||
|
||||
**Rule:** Never use raw string literals as `SystemConfig` keys. Always define a new `SystemConfigKey` enum entry first. Raw string key lookups are not searchable and cannot be refactored safely.
|
||||
|
||||
---
|
||||
|
||||
## UserConfig — Per-User Configuration
|
||||
|
||||
**Purpose:** Settings that differ per user account. Uses `UserConfigOper`.
|
||||
|
||||
```python
|
||||
from app.db.userconfig_oper import UserConfigOper
|
||||
|
||||
oper = UserConfigOper()
|
||||
value = oper.get(user_id=1, key="notification_enabled")
|
||||
oper.set(user_id=1, key="notification_enabled", value=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Settings / Environment Configuration
|
||||
|
||||
**Purpose:** Deployment-level, environment-level, and startup-time configuration such as ports, paths, proxies, switches, API keys, and third-party service addresses.
|
||||
|
||||
**Location:** `ConfigModel` and `Settings` in `app/core/config.py`
|
||||
|
||||
These values are read from environment variables (or `.moviepilot.env`) at startup and are immutable at runtime. They are not stored in the database.
|
||||
|
||||
**Access:**
|
||||
|
||||
```python
|
||||
from app.core.config import settings
|
||||
|
||||
host = settings.QB_HOST
|
||||
port = settings.QB_PORT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Caching
|
||||
|
||||
### FileCache / AsyncFileCache
|
||||
|
||||
**Location:** `app/core/cache.py`
|
||||
|
||||
Used to cache expensive external API responses to disk. Cache entries have a configurable TTL.
|
||||
|
||||
```python
|
||||
from app.core.cache import FileCache, fresh
|
||||
|
||||
cache = FileCache(cache_name="tmdb", ttl=3600)
|
||||
|
||||
@fresh(cache=cache, key_func=lambda tmdb_id: f"movie_{tmdb_id}")
|
||||
def get_movie_detail(tmdb_id: int) -> dict:
|
||||
return self._tmdb_client.get_movie(tmdb_id)
|
||||
```
|
||||
|
||||
### Redis (Optional)
|
||||
|
||||
When `REDIS_HOST` is configured, `app/modules/redis/` provides a distributed cache backend. Prefer `FileCache` for single-node deployments.
|
||||
|
||||
---
|
||||
|
||||
## Data Lifecycle Rules
|
||||
|
||||
- **TransferHistory:** Records are inserted after every successful file transfer. Do not delete records without user confirmation.
|
||||
- **DownloadHistory:** Records are inserted when a download task is added. Linked `DownloadFiles` records track individual files within a torrent.
|
||||
- **SystemConfig:** Values may be read and written freely at runtime. Changes to watched config keys trigger `on_config_changed()` on registered classes via `ConfigReloadMixin`.
|
||||
- **MediaServerItem:** This is a cache of the remote media server library. It is refreshed on media server sync events and can be safely cleared and rebuilt.
|
||||
|
||||
---
|
||||
|
||||
## Sensitive Data Handling
|
||||
|
||||
- Never log database record contents that include personal data (user credentials, passkeys, API tokens).
|
||||
- `settings.API_TOKEN` and other secret fields must not be included in log output or API responses.
|
||||
- The `config list --show-secrets` flag exists specifically to gate secret visibility in the CLI.
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
139
docs/rules/11-quality-and-security.md
Normal file
139
docs/rules/11-quality-and-security.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# 11 — Code Quality and Security
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
### What to Run
|
||||
|
||||
```bash
|
||||
# Minimum: run tests directly related to the change
|
||||
pytest tests/test_<domain>.py
|
||||
|
||||
# If the change affects common modules, startup flow, CLI, or agent runtime
|
||||
pytest
|
||||
```
|
||||
|
||||
### When to Expand Scope
|
||||
|
||||
Run the full test suite when changing:
|
||||
- `app/core/` — config, event system, module manager, plugin manager
|
||||
- `app/chain/__init__.py` — chain base class
|
||||
- `app/modules/__init__.py` — module base class
|
||||
- `app/main.py` — application startup
|
||||
- The CLI entrypoint (`moviepilot`)
|
||||
- Agent runtime (`app/agent/`)
|
||||
- Any shared schema in `app/schemas/types.py`
|
||||
|
||||
### Honest Reporting
|
||||
|
||||
- If a task only changes documentation, state explicitly that tests were not run.
|
||||
- Do not claim "all tests pass" unless you ran them.
|
||||
- Do not describe unexecuted checks as completed.
|
||||
|
||||
### Writing New Tests
|
||||
|
||||
- When fixing a bug, prefer adding a test that reproduces it first.
|
||||
- When adding a feature, add at minimum the smallest useful test coverage.
|
||||
- Test files go in `tests/`, named `test_<domain>.py`.
|
||||
- Use the patterns established in adjacent test files (fixtures, mock patterns, assertion style).
|
||||
- Agent-related tests are under `tests/test_agent_*.py`. Integration-style tests may be in `tests/cases/` or `tests/manual/`.
|
||||
|
||||
---
|
||||
|
||||
## Static Analysis
|
||||
|
||||
```bash
|
||||
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.
|
||||
- Do not suppress pylint warnings with `# pylint: disable` without a documented reason.
|
||||
|
||||
---
|
||||
|
||||
## Dependency Security Scan
|
||||
|
||||
```bash
|
||||
safety check -r requirements.txt --policy-file=safety.policy.yml
|
||||
```
|
||||
|
||||
- Run after every change to `requirements.txt`.
|
||||
- No new high-severity vulnerabilities may be introduced.
|
||||
- If a vulnerability cannot be patched immediately, document it explicitly in the PR description.
|
||||
|
||||
---
|
||||
|
||||
## Authentication and Authorization
|
||||
|
||||
### API Authentication
|
||||
|
||||
All REST and MCP API endpoints require authentication. The project supports two mechanisms:
|
||||
|
||||
| Method | Format |
|
||||
|---|---|
|
||||
| Request header | `X-API-KEY: <api_key>` |
|
||||
| Query parameter | `?apikey=<api_key>` |
|
||||
|
||||
The `API_TOKEN` value in `settings` is the source of truth. It is set at initialization and never exposed in logs or API responses.
|
||||
|
||||
### Endpoint Authorization
|
||||
|
||||
- Use the existing FastAPI dependency functions (e.g., `get_current_user`, `get_current_active_superuser`) — check `app/api/endpoints/` for usage patterns.
|
||||
- Do not add manual token parsing inside endpoint functions. Always use the project's dependency injection.
|
||||
- Superuser-only operations must explicitly require the superuser dependency.
|
||||
|
||||
---
|
||||
|
||||
## Input Validation
|
||||
|
||||
- Validate user input at the **endpoint layer only**, using Pydantic models.
|
||||
- Do not duplicate validation logic in chain or module code. Trust that the endpoint has already validated what it passes down.
|
||||
- For external API responses, validate using Pydantic models or explicit `None` checks before accessing fields.
|
||||
|
||||
---
|
||||
|
||||
## Secrets Management
|
||||
|
||||
- Never hardcode secrets (API keys, passwords, tokens) in source code.
|
||||
- All secrets are configured via environment variables or `.moviepilot.env` and accessed through `settings`.
|
||||
- Never log or serialize `settings.API_TOKEN`, `settings.DB_PASSWORD`, or any field with `Secret` in its name.
|
||||
- Do not commit `.moviepilot.env`, `*.db`, or any file under `config/` — these are local runtime state.
|
||||
|
||||
---
|
||||
|
||||
## SQL Injection Prevention
|
||||
|
||||
- All database access goes through SQLAlchemy ORM via the `*_oper.py` classes. No raw SQL string construction.
|
||||
- If a raw SQL query is ever genuinely necessary, use SQLAlchemy's `text()` with parameterized binds — never string interpolation.
|
||||
|
||||
---
|
||||
|
||||
## XSS and Injection in Notifications
|
||||
|
||||
- When constructing notification messages that include user-provided data (media titles, filenames, usernames), treat those values as untrusted strings.
|
||||
- Do not render user data in HTML contexts without escaping. Notification channels that render HTML (e.g., Telegram with `parse_mode=HTML`) must escape user-controlled strings.
|
||||
|
||||
---
|
||||
|
||||
## File Path Security
|
||||
|
||||
- Use `pathlib.Path` for all file path operations.
|
||||
- Never construct file paths by concatenating user-provided strings.
|
||||
- When transferring files to a user-configured path, verify the destination is within an allowed base directory before writing.
|
||||
|
||||
---
|
||||
|
||||
## Pre-Submission Checklist
|
||||
|
||||
Before marking any task as complete:
|
||||
|
||||
- [ ] Related pytest tests pass
|
||||
- [ ] No new pylint error-level issues in `pylint app/`
|
||||
- [ ] If dependencies changed: `pip-compile requirements.in` was run and `safety check` passes
|
||||
- [ ] 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
|
||||
- [ ] If database schema changed: a new Alembic migration exists under `database/versions/`
|
||||
- [ ] No secrets are included in code, logs, or committed files
|
||||
- [ ] Public classes and methods have Chinese docstrings
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
123
docs/rules/12-collaboration-and-distribution.md
Normal file
123
docs/rules/12-collaboration-and-distribution.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# 12 — Collaboration, Versioning, Build, and Release
|
||||
|
||||
## Commit Conventions
|
||||
|
||||
This project uses **Conventional Commits**. The release workflow parses commit messages to categorize changelog entries. This is not stylistic — it is functional.
|
||||
|
||||
### Format
|
||||
|
||||
```
|
||||
<type>(<optional scope>): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer]
|
||||
```
|
||||
|
||||
### Commit Types
|
||||
|
||||
| Type | When to use |
|
||||
|---|---|
|
||||
| `feat` | A new feature visible to users |
|
||||
| `fix` | A bug fix |
|
||||
| `docs` | Documentation only changes |
|
||||
| `chore` | Maintenance, dependency updates, tooling changes |
|
||||
| `refactor` | Code restructuring without behavior change |
|
||||
| `test` | Adding or modifying tests |
|
||||
| `ci` | CI/CD pipeline changes |
|
||||
| `perf` | Performance improvements |
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
feat: support MiniMax audio provider
|
||||
fix: sign media server image proxy URLs
|
||||
docs: add MCP client configuration examples
|
||||
chore: upgrade pydantic to 2.9.0
|
||||
refactor: extract transfer path resolution into helper
|
||||
test: add subscribe endpoint validation tests
|
||||
ci: improve docker build cache
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- **Only create a commit when the user explicitly asks for one.**
|
||||
- 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.
|
||||
|
||||
---
|
||||
|
||||
## Branch Policy
|
||||
|
||||
- Do not casually create, rename, or delete branches without user instruction.
|
||||
- The main development branch is the project default — check `git branch` rather than assuming it is `main` or `master`.
|
||||
- Feature work lives on dedicated branches and is merged via pull request.
|
||||
- Do not force-push to shared branches.
|
||||
|
||||
---
|
||||
|
||||
## Version Numbers
|
||||
|
||||
- Do not casually change version numbers in `version.py` or related files.
|
||||
- Version changes are part of the release workflow and are only made when the task explicitly involves a release.
|
||||
- The `FRONTEND_VERSION` field in `version.py` controls which frontend release the CLI and Docker build will download. Only update it as part of a coordinated frontend release.
|
||||
|
||||
---
|
||||
|
||||
## Docker Build and Release
|
||||
|
||||
- The primary Docker image bundles the backend (Python app), frontend static files (from `public/`), and resource data.
|
||||
- Docker build and release are managed by CI. Do not manually trigger or alter the Docker release flow unless the task explicitly requires it.
|
||||
- If a Dockerfile change is needed, update `Dockerfile` and verify the build locally before submitting.
|
||||
|
||||
---
|
||||
|
||||
## CI/CD
|
||||
|
||||
- CI runs on every push and pull request. The pipeline typically includes:
|
||||
- Dependency installation
|
||||
- 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.
|
||||
|
||||
---
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
- 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
|
||||
- How the change was validated
|
||||
- Any known risks or compatibility impact
|
||||
- Migration steps if config or database schema changed
|
||||
- Tag the PR with the appropriate label (`bug`, `feature`, `docs`, `chore`).
|
||||
|
||||
---
|
||||
|
||||
## Dependency Release Process
|
||||
|
||||
When updating a dependency:
|
||||
|
||||
1. Update `requirements.in` with the new version constraint.
|
||||
2. Run `pip-compile requirements.in` to regenerate `requirements.txt`.
|
||||
3. Run `safety check -r requirements.txt --policy-file=safety.policy.yml`.
|
||||
4. Run the full test suite: `pytest`.
|
||||
5. Commit both `requirements.in` and `requirements.txt` together.
|
||||
|
||||
---
|
||||
|
||||
## Local CLI Release
|
||||
|
||||
The `moviepilot` CLI is the local-mode entrypoint. Its update path is:
|
||||
|
||||
```bash
|
||||
moviepilot update all # updates backend + frontend + resources
|
||||
moviepilot update backend # git pull + reinstall deps
|
||||
moviepilot update frontend
|
||||
```
|
||||
|
||||
Bootstrap installer changes live in `scripts/bootstrap-local.sh`. Only modify this script if the task explicitly involves the bootstrap flow.
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
107
docs/rules/README.md
Normal file
107
docs/rules/README.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Documentation Hub
|
||||
|
||||
This repository maintains a structured documentation library covering the full development lifecycle. All rule documents live in the `docs/rules/` directory. This index maps each file to its technical domain and intended reader.
|
||||
|
||||
---
|
||||
|
||||
## Technical Document Index
|
||||
|
||||
### Section I: Foundation and Environment
|
||||
|
||||
* **01 Project Overview**
|
||||
* File: `01-project-overview.md`
|
||||
* Scope: System goals, business domain, deployment models, and what is and is not in this repository.
|
||||
|
||||
* **02 Tech Stack**
|
||||
* File: `02-tech-stack.md`
|
||||
* Scope: Frameworks, languages, libraries, runtime environments, and third-party integrations.
|
||||
|
||||
* **03 Commands**
|
||||
* File: `03-commands.md`
|
||||
* Scope: CLI reference, development triggers, testing commands, linting, and dependency management.
|
||||
|
||||
### Section II: Architecture and Logic
|
||||
|
||||
* **04 Design Patterns**
|
||||
* File: `04-design-patterns.md`
|
||||
* Scope: Project-specific structural, creational, and behavioral patterns: Module, Chain, Event, Oper, Config Reload, Singleton.
|
||||
|
||||
* **05 Architecture and Modules**
|
||||
* File: `05-architecture.md`
|
||||
* Scope: Layer boundaries, dependency directions, module categories, and the canonical call graph.
|
||||
|
||||
* **09 External APIs, Protocols, and Responses**
|
||||
* File: `09-external-response.md`
|
||||
* Scope: HTTP client conventions, MCP protocol, standardized response formats, and error handling by layer.
|
||||
|
||||
* **10 Data and Persistent Management**
|
||||
* File: `10-data-and-persistent.md`
|
||||
* Scope: SQLAlchemy models, Alembic migrations, Oper access layer, SystemConfig, caching patterns.
|
||||
|
||||
### Section III: Implementation Standards
|
||||
|
||||
* **06 Code Standards and Style**
|
||||
* File: `06-code-styles.md`
|
||||
* Scope: Type annotations, Pydantic usage, async patterns, imports, formatting, and error handling rules.
|
||||
|
||||
* **07 Naming Conventions**
|
||||
* File: `07-naming-conventions.md`
|
||||
* Scope: Strict taxonomy for files, classes, functions, constants, and schema models.
|
||||
|
||||
* **08 Comments and Documentation Style**
|
||||
* File: `08-comment-styles.md`
|
||||
* Scope: Chinese docstring requirements, inline comment rules, and prohibited comment anti-patterns.
|
||||
|
||||
### Section IV: Quality and Governance
|
||||
|
||||
* **11 Code Quality and Security**
|
||||
* File: `11-quality-and-security.md`
|
||||
* Scope: Testing requirements, pylint gates, safety scans, authentication patterns, and input validation rules.
|
||||
|
||||
* **12 Collaboration, Versioning, Build, and Release**
|
||||
* File: `12-collaboration-and-distribution.md`
|
||||
* Scope: Conventional Commits, branch policy, release workflow, Docker build, and version management.
|
||||
|
||||
---
|
||||
|
||||
## Reader Persona Guidance
|
||||
|
||||
### Core Developers and Implementers
|
||||
|
||||
Developers actively writing or modifying features should follow this reading path:
|
||||
|
||||
1. **07 Naming Conventions** — establishes the lexicon for the feature.
|
||||
2. **06 Code Standards** — ensures linting and logic compliance.
|
||||
3. **04 Design Patterns** — identifies the correct structural approach.
|
||||
4. **03 Commands** — required for local execution and validation.
|
||||
|
||||
### System Architects and Reviewers
|
||||
|
||||
Personnel focused on system integrity and long-term maintenance:
|
||||
|
||||
1. **05 Architecture and Modules** — for verifying structural boundaries.
|
||||
2. **10 Data and Persistent Management** — for auditing data integrity and storage efficiency.
|
||||
3. **09 External APIs** — for reviewing integration security and protocol compliance.
|
||||
4. **11 Code Quality and Security** — for establishing the PR approval baseline.
|
||||
|
||||
### Operations and Release Engineers
|
||||
|
||||
Those managing the application lifecycle post-development:
|
||||
|
||||
1. **12 Collaboration and Versioning** — for release tags and branch management.
|
||||
2. **02 Tech Stack** — for environment provisioning and dependency management.
|
||||
3. **11 Code Quality and Security** — for verifying deployment-ready security posture.
|
||||
|
||||
---
|
||||
|
||||
## Document Interconnectivity
|
||||
|
||||
* **Architecture (05)** references **Code Standards (06)** for layer isolation and module boundary rules.
|
||||
* **Naming Conventions (07)** works in tandem with **Comment Styles (08)** to define overall code readability.
|
||||
* **External APIs (09)** relies on **Tech Stack (02)** for transport layer specifications and HTTP client selection.
|
||||
* **Data Management (10)** is governed by **Quality and Security (11)** for sensitive data handling requirements.
|
||||
* **Design Patterns (04)** is the implementation reference for decisions documented in **Architecture (05)**.
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2026-05-25*
|
||||
Reference in New Issue
Block a user