Skip to content

Commit 64a6f43

Browse files
committed
refactor: adopt native evolution contract
- replace legacy comparison gates with public-contract pytest coverage - isolate generated-project runtime and retain pairwise and deep-profile validation - remove migration fixtures, shims, and redundant CI dependencies
1 parent 3c4c461 commit 64a6f43

132 files changed

Lines changed: 3010 additions & 20520 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 2 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -5,125 +5,17 @@ on:
55
pull_request:
66

77
jobs:
8-
template-quality:
8+
test:
99
runs-on: ubuntu-latest
1010
steps:
1111
- uses: actions/checkout@v4
12-
with:
13-
fetch-depth: 0
1412
- uses: astral-sh/setup-uv@v6
1513
with:
1614
enable-cache: true
1715
- run: uv python install 3.14
1816
- run: uv sync --all-groups
19-
- name: Template static checks
20-
run: uv run python scripts/lint_template.py
21-
- name: Migration goldens are append-only
22-
if: github.event_name == 'pull_request'
23-
run: uv run python scripts/check_migration_goldens.py ${{ github.event.pull_request.base.sha }}
24-
- name: Migration goldens are append-only on push
25-
if: github.event_name == 'push' && github.event.before != '0000000000000000000000000000000000000000'
26-
run: uv run python scripts/check_migration_goldens.py ${{ github.event.before }}
17+
- run: uv run python scripts/lint_template.py
2718
- run: uv run ruff check .
2819
- run: uv run ruff format --check .
2920
- run: uv run ty check
3021
- run: uv run pytest
31-
32-
generated-projects:
33-
runs-on: ubuntu-latest
34-
services:
35-
postgres:
36-
image: postgres:17-alpine
37-
env:
38-
POSTGRES_DB: app
39-
POSTGRES_USER: postgres
40-
POSTGRES_PASSWORD: postgres
41-
ports:
42-
- 5432:5432
43-
options: >-
44-
--health-cmd "pg_isready -U postgres -d app"
45-
--health-interval 2s
46-
--health-timeout 3s
47-
--health-retries 15
48-
redis:
49-
image: redis:8-alpine
50-
ports:
51-
- 6379:6379
52-
options: >-
53-
--health-cmd "redis-cli ping"
54-
--health-interval 2s
55-
--health-timeout 3s
56-
--health-retries 15
57-
strategy:
58-
fail-fast: false
59-
matrix:
60-
include:
61-
- name: default
62-
data: '{}'
63-
migrate: false
64-
- name: postgresql-sqlalchemy-logfire
65-
data: '{"database":"postgresql","orm_type":"sqlalchemy","enable_logfire":true,"include_example_crud":true}'
66-
migrate: true
67-
- name: postgresql-sqlmodel
68-
data: '{"database":"postgresql","orm_type":"sqlmodel","enable_docker":false,"include_example_crud":true}'
69-
migrate: true
70-
- name: taskiq-redis-consumers
71-
data: '{"background_tasks":"taskiq","enable_redis":true,"enable_caching":true,"enable_rate_limiting":true,"rate_limit_storage":"redis","rate_limit_requests":2,"rate_limit_period":1}'
72-
migrate: false
73-
- name: pydantic-ai-logfire
74-
data: '{"ai_framework":"pydantic_ai","enable_logfire":true,"enable_docker":false}'
75-
migrate: false
76-
- name: full-nginx
77-
data: '{"database":"postgresql","orm_type":"sqlalchemy","background_tasks":"taskiq","ai_framework":"pydantic_ai","enable_logfire":true,"enable_redis":true,"enable_caching":true,"enable_rate_limiting":true,"rate_limit_storage":"redis","rate_limit_requests":2,"rate_limit_period":1,"enable_docker":true,"reverse_proxy":"nginx_external"}'
78-
migrate: true
79-
- name: docker-ci-off
80-
data: '{"enable_docker":false,"ci_type":"none","enable_rate_limiting":true,"rate_limit_storage":"memory","rate_limit_requests":2,"rate_limit_period":1}'
81-
migrate: false
82-
name: generated-${{ matrix.name }}
83-
env:
84-
POSTGRES_HOST: 127.0.0.1
85-
POSTGRES_PORT: 5432
86-
POSTGRES_USER: postgres
87-
POSTGRES_PASSWORD: postgres
88-
POSTGRES_DB: app
89-
REDIS_HOST: 127.0.0.1
90-
REDIS_PORT: 6379
91-
LOGFIRE_SEND_TO_LOGFIRE: 'false'
92-
steps:
93-
- uses: actions/checkout@v4
94-
- uses: astral-sh/setup-uv@v6
95-
with:
96-
enable-cache: true
97-
- run: uv python install 3.14
98-
- run: uv sync --all-groups
99-
- name: Copier render
100-
env:
101-
COPIER_DATA: ${{ matrix.data }}
102-
run: |
103-
uv run python -c "import json, os, pathlib; pathlib.Path('.ci-data.json').write_text(json.dumps(json.loads(os.environ['COPIER_DATA'])), encoding='utf-8')"
104-
uv run copier copy --defaults --data-file .ci-data.json . generated
105-
- name: Generated project uv sync
106-
working-directory: generated
107-
run: uv sync --all-groups
108-
- name: Apply database migrations
109-
if: matrix.migrate
110-
working-directory: generated
111-
run: uv run alembic upgrade head
112-
- name: Generated project quality
113-
working-directory: generated
114-
run: |
115-
uv run ruff check .
116-
uv run ruff format --check .
117-
uv run ty check
118-
uv run pytest
119-
- name: Runtime smoke
120-
working-directory: generated
121-
run: |
122-
uv run uvicorn app.main:app --host 127.0.0.1 --port 8000 &
123-
server_pid=$!
124-
trap 'kill "$server_pid"' EXIT
125-
for attempt in {1..30}; do
126-
curl --fail --silent http://127.0.0.1:8000/health/live && exit 0
127-
sleep 1
128-
done
129-
exit 1

AGENTS.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Project Overview
44

5-
本仓库提供唯一受支持的原生 Copier 模板,用于生成可组合的 FastAPI 后端服务。模板配置位于 `copier.yml`,模板树位于 `template/`不再维护 Cookiecutter、自定义生成 CLI 或 legacy 生成器
5+
本仓库提供唯一受支持的原生 Copier 模板,用于生成可组合的 FastAPI 后端服务。模板配置位于 `copier.yml`,模板树位于 `template/`不维护 Cookiecutter 或自定义生成 CLI。当前公开承诺以 `docs/product-contract.md` 为准
66

77
保留能力:API Key、可选 PostgreSQL(SQLAlchemy/SQLModel)、Taskiq、Redis cache/rate limit、Pydantic AI、Logfire、Docker、外部 Nginx 配置和 GitHub Actions。不要重新引入 frontend、用户/JWT、teams、billing、消息渠道、文件存储、Webhooks、RAG、Kubernetes、Helm、Traefik 或替代队列/telemetry/provider SDK。
88

@@ -17,22 +17,24 @@ Copier 直接读取 `copier.yml`,再以原生条件路径和 Jinja 条件渲
1717
| 路径 | 用途 |
1818
|---|---|
1919
| `copier.yml` | Copier 问题、默认值、choices、when 与 validator 的事实来源 |
20+
| `docs/product-contract.md` | Copier 输入、生成边界和生成项目公开行为的规范 |
2021
| `template/` | 唯一生成项目模板;条件目录和文件名使用自定义 `[% ... %]` Jinja delimiters |
2122
| `template/app/` | 生成 FastAPI runtime、可选能力与生命周期组合 |
2223
| `template/tests/` | 生成项目的行为测试 |
2324
| `template/AGENTS.md.jinja` | 根据所选能力生成的根级 agent guidance |
24-
| `template/.omp/RULES.md.jinja` | 根据所选能力生成的 omp rules |
25-
| `tests/test_template.py` | Copier public interface、生成树和实际 runtime 验收 |
25+
| `template/.omp/` | 根据所选能力生成的 omp rules、skills 与 commands |
26+
| `tests/test_*_contract.py` | 按公开契约领域组织的生成与运行验收 |
27+
| `tests/support/` | 隔离生成环境、进程与真实服务的共享测试支持层 |
2628
| `scripts/lint_template.py` | 模板和条件路径的 Jinja 语法静态检查 |
27-
| `.github/workflows/ci.yml` | 根模板质量检查与代表性生成矩阵 |
29+
| `.github/workflows/ci.yml` | 根模板质量检查与 pytest 验收入口 |
2830
| `docs/adr/` | 已接受的产品和架构边界 |
2931

3032
## Development Commands
3133

3234
```bash
3335
uv sync --all-groups
3436
uv run python scripts/lint_template.py
35-
uv run pytest tests/test_template.py -q
37+
uv run pytest
3638
uv run ruff check .
3739
uv run ruff format --check .
3840
uv run ty check
@@ -58,19 +60,21 @@ uv run uvicorn app.main:app
5860
## Code Conventions & Common Patterns
5961

6062
- Python 使用 4 空格、双引号、100 字符行宽;Ruff 负责格式与 import 排序,类型检查使用 `ty`
61-
- I/O、数据库、HTTP 和生命周期路径使用 `async`测试使用 AnyIO、HTTPX `AsyncClient` + `ASGITransport`
62-
- 依赖通过 FastAPI DI 或显式参数传递;每个测试结束后清理 `app.dependency_overrides`,禁止模块级请求状态
63+
- I/O、数据库、HTTP 和生命周期路径使用 `async`生成项目测试使用 AnyIO、HTTPX `AsyncClient` + `ASGITransport`
64+
- 根 pytest 不把生成应用导入自身进程;运行行为必须在生成项目自己的 `uv` 环境或进程中验证
6365
- 新增、修改或删除模板选项时,同步检查 `copier.yml`、条件路径、模板内容、依赖、环境变量、部署资产、guidance 和渲染测试。
6466
- Jinja 模板不是普通 Python。不要对整个 `template/` 自动修复;先渲染代表性项目,再在生成结果中运行 Ruff、ty 和 pytest。
6567
- 条件文件必须通过 Copier 原生路径条件消失,禁止恢复 cleanup hook、derived context 或兼容 alias。
66-
- Logfire 默认关闭;启用时先 `logfire.configure()`再按实际能力 instrument FastAPI、SQLAlchemy engine、Redis、Taskiq 和 Pydantic AI,且默认不采集模型内容
68+
- Logfire 仅在 Pydantic AI 启用时可用且默认启用;先 `logfire.configure()`再只 instrument Pydantic AI,默认不采集模型或 binary content
6769
- Docker 产物统一位于生成项目的 `deploy/`;Nginx 仅生成外部配置,不加入 compose service。
6870

6971
## Testing & QA
7072

71-
公共测试 seam 是 Copier 渲染接口和生成项目公开开发/runtime 接口。测试应验证生成树、依赖、配置、HTTP 行为、后台任务和真实数据库/Redis 交互,不断言无意义的模板源码细节。
73+
公共测试 seam 是 Copier 渲染接口、文档化开发接口和生成项目公开 runtime。测试按契约领域组织,验证生成边界、依赖、配置、HTTP/CLI 行为、后台任务和真实数据库/Redis 交互;不按历史 issue 分组,也不断言模板源码文本。
74+
75+
组合覆盖使用固定 pairwise answers 做快速边界检查,并对 minimal、default、两种 ORM、Agent+Logfire 和 Redis consumers+Taskiq 等代表剖面做深测。只允许 OpenAPI 等窄机器契约使用归一化快照,不保存完整生成树 golden。
7276

73-
修改时先运行最窄的相关测试和 `uv run ty check`。涉及模板条件时必须运行 `uv run python scripts/lint_template.py`,并至少渲染一个受影响配置。交付前运行完整 `uv run pytest`、Ruff check、Ruff format check 和 ty。需要 PostgreSQL/Redis 的验收使用 Docker 随机映射端口,结束后必须清理容器
77+
`uv run pytest` 是完整放行入口;开发时可用 marker 或测试路径筛选。涉及模板条件时还必须运行 `uv run python scripts/lint_template.py`。交付前运行完整 pytest、Ruff check、Ruff format check 和 ty。PostgreSQLRedis、Docker 与 Nginx 验收使用随机端口并在 teardown 中清理
7478

7579
不要提交生成项目、虚拟环境、缓存、coverage 产物、`.env`、凭据或本地数据库。
7680

CONTEXT.md

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,25 @@
44

55
## Language
66

7-
**减法迁移(Subtractive Migration**
8-
`legacy/` 对应实现为规范性基线,仅减去 V1 删除能力及其引用,并叠加已封闭列举的必要迁移变换与批准差异;不包含借迁移进行的重构、重命名或重新设计
9-
_Avoid_参考实现、prior art、重新实现、现代化改造
7+
**原生演进基线(Native Evolution Baseline**
8+
Copier 公开问题与生成边界、生成项目公开 API 和运行行为共同定义的产品契约;模板实现、历史迁移来源和输出快照都不是规范
9+
_Avoid_历史实现基线、内容等价门禁、当前测试即规范
1010

11-
**V1 能力边界(V1 Capability Boundary**
12-
V1 已确认的保留能力与删除能力集合;迁移实现不得以重新设计为由扩张或缩减该集合
13-
_Avoid_候选能力、待定范围、迁移建议
11+
**公开产品契约(Public Product Contract**
12+
调用方可观察并依赖的 Copier 输入与生成边界,以及生成项目的开发接口、HTTP API 和运行行为
13+
_Avoid_实现细节、文件内容快照、历史实现 contract
1414

15-
**默认生成剖面(Default Generation Profile**
16-
调用方不覆盖任何答案时生成的能力组合;保留能力的默认启用状态属于 legacy 行为,不因迁移到 Copier 而改变
17-
_Avoid_lean default、推荐组合、新默认值
15+
**契约保持切换(Contract-preserving Cutover**
16+
在不改变现有公开产品契约的前提下,将规范来源和验收架构一次性切换到原生演进基线;产品行为变更必须另行决策
17+
_Avoid_顺带重设计、迁移即改版、自由清理
1818

19-
**保留能力开关(Retained Capability Toggle**
20-
legacy 中用于启用或关闭某项 V1 保留能力的用户选择;迁移后继续表达同一选择,且默认值不变
21-
_Avoid_冗余选项、固定能力、实现细节
19+
**文档化开发接口(Documented Development Interface**
20+
生成项目文档明确承诺的文件、命令、环境变量、import 和扩展 seam;未文档化内部模块、符号及源码文本不属于兼容面
21+
_Avoid_全部生成实现、偶然可导入符号、源码快照
2222

23-
**对应实现(Corresponding Implementation)**
24-
`legacy/` 中承载同一保留能力、公开 contract 或运行行为的具体文件、符号与测试,是目标文件进入 V1 的可追溯来源。
25-
_Avoid_:灵感来源、相似示例、参考架构
26-
27-
**批准差异(Approved Deviation)**
28-
相对对应实现的显式例外,必须记录原因、最小影响范围和独立验收;未记录差异一律视为迁移回归。
29-
_Avoid_:顺手优化、合理调整、等价重构
30-
31-
**迁移等价性(Migration Equivalence)**
32-
目标生成树在扣除已声明的路径、条件、删除引用、兼容修改与批准差异后,与 legacy 对应实现的内容和行为一致。
33-
_Avoid_:大致一致、功能相似、测试能过
23+
**迁移兼容垫片(Migration Compatibility Shim)**
24+
仅为复现迁移前实现或绕过迁移期依赖差异而存在的版本限制、workaround 或兼容分支;不包括公开产品契约或模型接口的协议兼容性。
25+
_Avoid_:所有 compatibility、公开兼容承诺、Chat Completions-compatible
3426

3527
**后端服务(Backend Service)**
3628
通过 API 对外提供算法、智能体或其他业务能力的统一服务类型。

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copier FastAPI Forge
2+
3+
用于生成可组合 FastAPI 后端服务的原生 Copier 模板。公开 questions、能力生成边界和生成项目行为见 [`docs/product-contract.md`](docs/product-contract.md)
4+
5+
```bash
6+
uv sync --all-groups
7+
uv run copier copy --defaults . <output-dir>
8+
```
9+
10+
模板维护者使用以下命令执行完整质量检查:
11+
12+
```bash
13+
uv run python scripts/lint_template.py
14+
uv run ruff check .
15+
uv run ruff format --check .
16+
uv run ty check
17+
uv run pytest
18+
```

docs/adr/0012-use-subtractive-migration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
status: accepted
2+
status: superseded by ADR-0015
33
---
44

55
# 以 legacy 对应实现执行减法迁移

docs/adr/0013-revise-v1-migration-boundary.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
status: accepted
2+
status: superseded by ADR-0015
33
---
44

55
# 修订 V1 迁移边界与批准差异

docs/adr/0014-require-migration-equivalence-acceptance.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
status: accepted
2+
status: superseded by ADR-0015
33
---
44

55
# 以迁移等价性逐项验收 V1
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
status: accepted
3+
---
4+
5+
# 采用原生演进基线
6+
7+
V1 减法迁移已经完成,继续以历史模板文件、归一化 diff 和迁移内容摘要定义正确性会冻结实现细节,并使已经稳定的原生 Copier 模板无法独立演进。项目改以公开产品契约为唯一规范:Copier questions 与生成边界、文档化开发接口,以及生成项目的 HTTP、CLI 和运行行为共同定义正确性;源码文本、完整生成树快照、历史来源和当前测试实现都不是规范。
8+
9+
本次切换保持现有公开产品契约不变。先让按契约领域组织的新 pytest 验收与旧迁移门禁同时通过,再在同一变更中删除历史模板 fixture、trace、comparison、内容摘要、兼容垫片及无用依赖。测试保留固定 pairwise 配置和代表性深测剖面,主要使用语义断言,仅对 OpenAPI 等完整机器契约使用窄范围归一化快照;生成应用必须在自己的依赖环境中运行,GitHub Actions 只调用 pytest 验收入口。
10+
11+
未来可以显式修改公开产品契约,但必须在同一变更中更新产品契约文档和行为测试。breaking change 采用明确的 clean cutover;兼容期只有在单独说明范围、期限和删除条件时才能引入,不因历史行为默认保留 shim。`copier update` 不在本次契约内。
12+
13+
本 ADR 取代 ADR-0012、ADR-0013 和 ADR-0014。旧 ADR 保留迁移历史,但不再约束当前实现。

0 commit comments

Comments
 (0)