任何代码修改都必须在同一次提交里同步更新对应文档。
- 没有文档更新的代码改动 = 未完成,不得 commit。
- Commit message 末尾必须有"文档同步"段落。
- 完整的"代码路径 → 文档"映射表见 ../CLAUDE.md。
# 1. 单测必须 100% 通过
pytest tests/ -q
# 2. 自检:代码改动是否覆盖文档
git diff --stat | grep -E "\.(py|j2|yaml|json)$" # 列出代码改动
git diff --stat | grep -E "docs/.*\.md$" # 列出文档改动
# 两者都不应为空;纯重构也至少要在对应 module md 留改动痕迹
# 3. 检查新建/删除的文件
git status --short
# 新建 .py → 文档对应 module md 的"文件清单"表里加了行?
# 删除 .py → 文档表里那一行删了?
# 新建模块(新目录)→ 新建 docs/modules/<新>.md + 改 4 份顶层 md?按改动位置查"必须更新的文档"。完整版见 ../CLAUDE.md 的同步映射表。常用速查:
| 改了哪里 | 至少改这些 md |
|---|---|
tickbridge/sources/*.py |
modules/tickbridge.md + 如新增 fetcher 还要改 modules.md / CONTEXT.md / data_flow.md |
stocklens/pipeline/* |
pipeline.md;流程改动还要改 DATA_FLOW.md |
stocklens/services/scoring/* |
services.md + 如改公式还要改 ARCHITECTURE.md;建议同时加 tests/unit/test_*.py |
stocklens/storage/models.py |
storage.md(表清单)+ DATA_FLOW.md(写入阶段) |
stocklens/config/registry_fields.py |
config.md + .env.example |
stocklens/utils/concurrency.py |
concurrency.md(池清单)+ DATA_FLOW.md(并发资源) |
stocklens/notification/senders/* |
notification.md + README "推送渠道"清单 |
api/v1/endpoints/* |
api.md(端点表) |
bot/commands/* |
bot.md + README "Bot 集成" |
main.py(启动逻辑) |
main.md + 本文件"常用命令" |
templates/*.j2 |
notification.md |
strategies/*.yaml |
agent.md |
type: 一句话主题
具体改动列表:
- ...
文档同步:
- docs/modules/xxx.md: 更新 yyy 段落
- docs/MODULES.md: 修订文件清单(如适用)
- README.md: 更新 zzz(如适用)
type ∈ feat / fix / refactor / perf / docs / test / chore。
写完 commit message 再扫一遍:"文档同步"段落空的?回去补文档再 commit。
- 使用上面的 commit message 模板
- 描述简洁明了,中英文皆可
- 在
dev分支开发 - 别用
--no-verify跳过 hook
.env(敏感信息)data/、logs/、reports/、stocklens/reports/(运行时生成).venv//venv/(虚拟环境)static/(如果以后加回前端,构建产物不入库)
.env.example 是允许提交的。
# 激活虚拟环境(项目使用 .venv)
source .venv/bin/activate
# 一次性安装项目(editable 模式,需要 pip >= 23)
pip install -e .
# 启动 web 服务(无参;监听地址走 WEBUI_HOST / WEBUI_PORT,默认 0.0.0.0:8000)
python main.py
# 自定义端口启动
WEBUI_PORT=5678 python main.py
# 跑全部单测
pytest tests/ -q
# 详细输出
pytest tests/unit/ -v
# 跑独立子包测试
pytest tickbridge/tests/test_smoke.py tickbridge/tests/test_cache.py -q
pytest querybus/tests/ -q
pytest pulsefan/tests/ -q
# Lint + 格式化(ruff)
ruff check . # 检查
ruff check . --fix # 自动修复(仅安全的:unused imports / isort)
ruff format . # 格式化
# 安装 git hook(提交前自动跑 ruff + 通用 hygiene 检查)
pre-commit install # 一次性安装到本地 .git/hooks/
pre-commit run --all-files # 手动跑全仓
# 测试覆盖率(当前 19.0% / 516 测试,逐步往上;目标 35%)
pytest tests/ --cov # 终端汇总
pytest tests/ --cov --cov-report=html # 生成 htmlcov/index.html
# 重置管理员密码(auth 启用时)
stocklens-reset-password # 通过 entry_points 注册的 CLI启动时 DatabaseManager.__init__ 会自动跑 alembic upgrade head。本地手动用:
# 查看当前 DB 的 revision
alembic current
# 查看所有 revisions
alembic history
# 升级到最新(应用新 migration)
alembic upgrade head
# 降级到上一版(极少用;baseline 的 downgrade 是 no-op)
alembic downgrade -1改 schema 的标准流程(铁律 #3):
- 在
stocklens/storage/models.py修改 ORM 类(加字段 / 加表 / 改索引) - 跑自动生成:
alembic revision --autogenerate -m "add_xxx_column_to_yyy" - 检查生成的 migration 文件(
alembic/versions/<hash>_xxx.py):- SQLite +
render_as_batch=True会自动生成 batch 操作(CREATE NEW + COPY + RENAME 模式,绕开 SQLite 的 ALTER TABLE 限制) - 不要相信 autogenerate 100% 正确:某些情况下(比如改 server_default、加 CHECK constraint)需要手工调整
- 如果是数据迁移(不是纯结构),加
op.execute("UPDATE ...")步骤
- SQLite +
- 跑一次
alembic upgrade head在本地 DB 上确认 migration 工作 - 同次提交:
models.py+alembic/versions/<hash>_xxx.py+ 相关文档更新
baseline migration(49a950e262eb_baseline_existing_schema.py)锁定了 G2 接入时的 schema。它的 upgrade() 调 Base.metadata.create_all(idempotent),所以同一个 baseline 既能初始化新 DB 也能在已有 DB 上 stamp 一个 alembic_version 行。永远不要修改这个文件。
override DB URL(CI / 多环境):
ALEMBIC_DATABASE_URL="sqlite:////tmp/test.db" alembic upgrade head启动时也会读这个变量,所以 docker-compose 里可以独立配置。
项目带一份 production-grade Dockerfile + docker-compose.yml:
# 1. 准备配置(首次)
cp .env.example .env
# 编辑 .env:填 STOCK_LIST / LLM key / 推送 webhook
# 2. 一键构建并启动
docker compose up -d --build
# 3. 查看日志
docker compose logs -f stocklens
# 4. 停止
docker compose down
# 端口可在 .env 调整 HOST_PORT,CPU/内存上限通过
# STOCKLENS_CPU_LIMIT / STOCKLENS_MEM_LIMIT 调(默认 2 核 / 2 GiB)镜像设计要点:
- 二阶段构建:
builder(含 gcc/headers)解析依赖 →runtime(slim,只带 site-packages)。 - 非 root 用户:UID/GID=1000 跑应用;
./data./logs通过 bind mount 持久化。 - tini PID 1:信号转发,
docker compose down优雅关闭。 - 健康检查:每 30s 拉
/api/health,3 次失败标记 unhealthy。 .dockerignore严格剔除:.git/、data/、logs/、tests/、.venv/、docs/都不进镜像,最终镜像 ~250 MiB(基线 python:3.11-slim ≈ 130 MiB)。
业务代码里大量"主路径失败 → 退化到次路径"的 try/except(DB → API → cache、多源 fall-over、可选指标采集),统一用 stocklens.utils.fail_open.fail_open(tickbridge 端用 tickbridge.core.fail_open.fail_open):
from stocklens.utils.fail_open import fail_open
try:
weekly_df = fetcher.get_weekly_data(code)
except Exception as exc: # noqa: BLE001 — DB-only fallback below
fail_open(logger, "weekly_data fetch", exc)
weekly_df = None历史上散落的 logger.debug("silent except (%s) at %s:%d: %s", ...) 这种"留痕格式"已经全部迁移(45 处),现在都长得一样:[fail-open] <op>: <ExcType>: <msg> | <detail>,可以 grep "\[fail-open\]" 精准捞排查线索。默认级别是 WARNING(生产可见),需要 DEBUG 时显式传 level=logging.DEBUG。
| 变量 | 默认 | 说明 |
|---|---|---|
STOCKLENS_RATE_LIMIT_ENABLED |
true |
设 false 关闭所有限流 |
STOCKLENS_RATE_LIMIT_DEFAULT |
120/minute |
全局默认限制 |
STOCKLENS_RATE_LIMIT_ANALYZE |
30/minute |
/api/v1/analysis/analyze |
STOCKLENS_RATE_LIMIT_LOGIN |
10/minute |
/auth/login(如使用 slowapi 限流) |
STOCKLENS_RATE_LIMIT_STORAGE_URI |
memory:// |
多实例可换 redis://... |
⚠️ main.py已不再接受任何 CLI 分析参数。分析 / 回测 / 推送等业务一律通过 web UI 与 REST API 触发:POST /api/v1/analysis/analyze、POST /api/v1/backtest/...等(详见docs/modules/api.md)。
所有线程池可通过环境变量调节(避免 LLM/数据源 rate-limit):
# 全局降到 4 worker
export STOCKLENS_DEFAULT_WORKERS=4
# 仅限制搜索池
export STOCKLENS_SEARCH_WORKERS=2
# API 服务长任务/IO 任务分别配置
export API_LONG_POOL_WORKERS=4
export API_IO_POOL_WORKERS=16完整列表见 modules/concurrency.md。
所有环境变量见 .env.example(130+ 项,带中文注释)。最小运行配置:
STOCK_LIST=AAPL
GEMINI_API_KEY=your_key # 或任意一个 LLM key- 类型注解:所有公开函数必须有
- Docstring:Google 风格,中文注释
- 导入顺序:stdlib → third-party → local,各组间空行
- 日志:
logging.getLogger(__name__),不用print - 异常:
- 捕获尽量窄。优先用
stocklens.exceptions中的领域异常:DataSourceError/LLMError/StorageWriteError/SearchError/NotificationError/DSAError; - 最外层兜底用
Exception时必须带as exc并加logger.warning/exception/debug(...)留痕; - 绝不允许
except Exception: pass或except Exception: continue(无日志的静默吞噬); - 兜底
except Exception不再需要写# noqa: BLE001—— 2026-05 起 BLE001 已在.ruff.toml全仓 ignore(理由:BLE001 规则与 CLAUDE.md "outermost Exception fallback MUST log" 规定冲突,所有现存站点均已带 logger 调用); - 写库 + ORM 错误优先捕获
sqlalchemy.exc.IntegrityError/OperationalError,再 fallback 到StorageWriteError
- 捕获尽量窄。优先用
- 重型库(newspaper3k / fake_useragent / akshare 等)一律 lazy import
- 并发:所有
ThreadPoolExecutor通过stocklens.utils.concurrency.get_pool_workers(name)取 worker 数,不要硬编码os.cpu_count() - 跨层依赖:核心层 (
stocklens/) 禁止 importbot//api/;跨层共享类型放stocklens.contracts - 数据库写:批量保存用 SQLite
INSERT ... ON CONFLICT DO UPDATE(参考stock_repo.save_dataframe),不要循环 select-then-add
- 新建
stocklens/<新目录>/+__init__.py - 新建
docs/modules/<新>.md— 至少写文件清单、核心类、依赖关系、配置项、注意事项五段 - 在
docs/CONTEXT.md的目录映射图里加行 - 在
docs/MODULES.md速查表里加行 - 在
docs/ARCHITECTURE.md的"目录与模块映射"表里加行 - 在
docs/INDEX.md的快速导航表里加行 - 在
CLAUDE.md的"代码 → 文档同步映射"里加行 - 写至少一个 pytest 用例(
tests/unit/test_<新>.py) - 跑
pytest tests/ -q - 一次 commit 提交以上全部改动
少做任何一步,commit 都视为不完整。