@deepstorm/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +72 -0
  2. package/dist/agents/reef-inspect-figma.md +77 -0
  3. package/dist/agents/reef-review-backend.md.tmpl +112 -0
  4. package/dist/agents/reef-review-frontend.md.tmpl +78 -0
  5. package/dist/agents/reef-review-infra.md +47 -0
  6. package/dist/agents/reef-review-security.md.tmpl +80 -0
  7. package/dist/agents/reef-scope-analysis.md +64 -0
  8. package/dist/build-registry.js +375 -0
  9. package/dist/cli.js +8581 -0
  10. package/dist/config-schema.json +133 -0
  11. package/dist/env-examples/context7.env-example +19 -0
  12. package/dist/env-examples/feishu-wiki.env-example +16 -0
  13. package/dist/env-examples/figma.env-example +16 -0
  14. package/dist/env-examples/github.env-example +20 -0
  15. package/dist/env-examples/jira.env-example +20 -0
  16. package/dist/hooks/mcp-hook.sh +77 -0
  17. package/dist/hooks/reef-auto-format.sh.tmpl +72 -0
  18. package/dist/hooks/reef-block-dangerous.sh +70 -0
  19. package/dist/hooks/reef-hooks.json +72 -0
  20. package/dist/hooks/reef-intent-detect.sh +129 -0
  21. package/dist/hooks/reef-protect-files.sh +55 -0
  22. package/dist/hooks/reef-run-tests.sh +84 -0
  23. package/dist/hooks/reef-scope-check.sh +386 -0
  24. package/dist/hooks/reef-scope-ci.sh +28 -0
  25. package/dist/hooks/reef-scope-gate.sh +115 -0
  26. package/dist/hooks/reef-scope-pre-commit.sh.tmpl +28 -0
  27. package/dist/hooks/reef-scope-setup.sh +204 -0
  28. package/dist/hooks/reef-scope-split.sh +203 -0
  29. package/dist/hooks/sweep-hooks.json +14 -0
  30. package/dist/hooks/sweep-mcp-hook.sh +77 -0
  31. package/dist/hooks/tide-hooks.json +14 -0
  32. package/dist/hooks/tide-session-preload.sh +17 -0
  33. package/dist/mcp/code-hosting/github.json +20 -0
  34. package/dist/mcp/design-tools/figma.json +19 -0
  35. package/dist/mcp/docs-reference/context7.json +28 -0
  36. package/dist/mcp/e2e-testing/playwright.json +13 -0
  37. package/dist/mcp/knowledge-base/feishu-wiki.json +19 -0
  38. package/dist/mcp/project-management/jira.json +27 -0
  39. package/dist/mcp-skills/deepflow-mcp-feishu-wiki-read/SKILL.md +65 -0
  40. package/dist/mcp-skills/deepflow-mcp-feishu-wiki-write/SKILL.md +63 -0
  41. package/dist/mcp-skills/deepflow-mcp-figma-read/SKILL.md +98 -0
  42. package/dist/mcp-skills/deepflow-mcp-github-read/SKILL.md +62 -0
  43. package/dist/mcp-skills/deepflow-mcp-github-write/SKILL.md +63 -0
  44. package/dist/mcp-skills/deepflow-mcp-jira-read/SKILL.md +80 -0
  45. package/dist/mcp-skills/deepflow-mcp-jira-write/SKILL.md +74 -0
  46. package/dist/mcp-skills/deepflow-mcp-playwright-read/SKILL.md +79 -0
  47. package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-read/SKILL.md +65 -0
  48. package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-write/SKILL.md +63 -0
  49. package/dist/mcp-skills/deepstorm-mcp-figma-read/SKILL.md +98 -0
  50. package/dist/mcp-skills/deepstorm-mcp-github-read/SKILL.md +62 -0
  51. package/dist/mcp-skills/deepstorm-mcp-github-write/SKILL.md +63 -0
  52. package/dist/mcp-skills/deepstorm-mcp-jira-read/SKILL.md +80 -0
  53. package/dist/mcp-skills/deepstorm-mcp-jira-write/SKILL.md +74 -0
  54. package/dist/mcp-skills/deepstorm-mcp-playwright-read/SKILL.md +79 -0
  55. package/dist/registry.json +818 -0
  56. package/dist/skills/atoll-ops/SKILL.md +46 -0
  57. package/dist/skills/reef-commit/SKILL.md +127 -0
  58. package/dist/skills/reef-gen-backend/SKILL.md.tmpl +87 -0
  59. package/dist/skills/reef-gen-backend/variants/java/steps.md +28 -0
  60. package/dist/skills/reef-gen-backend/variants/python/steps.md +70 -0
  61. package/dist/skills/reef-gen-frontend/SKILL.md.tmpl +83 -0
  62. package/dist/skills/reef-gen-frontend/variants/angular/steps.md +30 -0
  63. package/dist/skills/reef-harden/EXAMPLES.md +89 -0
  64. package/dist/skills/reef-harden/SKILL.md +136 -0
  65. package/dist/skills/reef-pr/SKILL.md +97 -0
  66. package/dist/skills/reef-review/SKILL.md.tmpl +107 -0
  67. package/dist/skills/reef-scope/SKILL.md +134 -0
  68. package/dist/skills/reef-start/SKILL.md.tmpl +562 -0
  69. package/dist/skills/reef-start/references/jira-start-subagent.md +60 -0
  70. package/dist/skills/reef-style-backend/SKILL.md.tmpl +134 -0
  71. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/chat-client.md +96 -0
  72. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/rag.md +94 -0
  73. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/structured-output.md +62 -0
  74. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/tool-calling.md +68 -0
  75. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/quick-reference.md +220 -0
  76. package/dist/skills/reef-style-backend/fragments/java/api-spec/quick-reference.md +148 -0
  77. package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/examples/database-migration.md +131 -0
  78. package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/quick-reference.md +103 -0
  79. package/dist/skills/reef-style-backend/fragments/java/dependency-management/quick-reference.md +119 -0
  80. package/dist/skills/reef-style-backend/fragments/java/exception-handling/examples/error-code-enum.md +101 -0
  81. package/dist/skills/reef-style-backend/fragments/java/exception-handling/quick-reference.md +181 -0
  82. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/controller.md +95 -0
  83. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md +121 -0
  84. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/infrastructure.md +179 -0
  85. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/service-entity.md +202 -0
  86. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/testing.md +107 -0
  87. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/quick-reference.md +83 -0
  88. package/dist/skills/reef-style-backend/fragments/java/orm/hibernate/quick-reference.md +150 -0
  89. package/dist/skills/reef-style-backend/fragments/java/security-redlines/quick-reference.md +197 -0
  90. package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/examples/user-repository-test.md +69 -0
  91. package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/quick-reference.md +101 -0
  92. package/dist/skills/reef-style-backend/fragments/java/test/junit5/examples/user-service-test.md +61 -0
  93. package/dist/skills/reef-style-backend/fragments/java/test/junit5/quick-reference.md +100 -0
  94. package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/examples/user-controller-test.md +61 -0
  95. package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/quick-reference.md +85 -0
  96. package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/examples/user-service-integration-test.md +56 -0
  97. package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/quick-reference.md +83 -0
  98. package/dist/skills/reef-style-backend/fragments/python/alembic-migration/quick-reference.md +77 -0
  99. package/dist/skills/reef-style-backend/fragments/python/api-spec/quick-reference.md +164 -0
  100. package/dist/skills/reef-style-backend/fragments/python/dependency-management/quick-reference.md +139 -0
  101. package/dist/skills/reef-style-backend/fragments/python/exception-handling/quick-reference.md +177 -0
  102. package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md +101 -0
  103. package/dist/skills/reef-style-backend/fragments/python/langchain/quick-reference.md +135 -0
  104. package/dist/skills/reef-style-backend/fragments/python/pytest-testing/quick-reference.md +111 -0
  105. package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md +83 -0
  106. package/dist/skills/reef-style-backend/fragments/python/security-redlines/quick-reference.md +207 -0
  107. package/dist/skills/reef-style-backend/fragments/python/sqlalchemy-orm/quick-reference.md +91 -0
  108. package/dist/skills/reef-style-backend/variants/java/examples/code-wrapping.md +227 -0
  109. package/dist/skills/reef-style-backend/variants/java/examples/contributor-pattern.md +97 -0
  110. package/dist/skills/reef-style-backend/variants/java/quick-reference.md +117 -0
  111. package/dist/skills/reef-style-backend/variants/python/examples/crud-router.md +71 -0
  112. package/dist/skills/reef-style-backend/variants/python/examples/pydantic-schema.md +45 -0
  113. package/dist/skills/reef-style-backend/variants/python/examples/pytest-fixture.md +60 -0
  114. package/dist/skills/reef-style-backend/variants/python/examples/sqlalchemy-model.md +31 -0
  115. package/dist/skills/reef-style-backend/variants/python/quick-reference.md +111 -0
  116. package/dist/skills/reef-style-frontend/SKILL.md.tmpl +70 -0
  117. package/dist/skills/reef-style-frontend/fragments/css/tailwind/quick-reference.md +79 -0
  118. package/dist/skills/reef-style-frontend/fragments/test/vitest/examples/testing.md +150 -0
  119. package/dist/skills/reef-style-frontend/fragments/test/vitest/quick-reference.md +141 -0
  120. package/dist/skills/reef-style-frontend/fragments/ts-config/strict/quick-reference.md +112 -0
  121. package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/examples/ui-components.md +134 -0
  122. package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/quick-reference.md +66 -0
  123. package/dist/skills/reef-style-frontend/variants/angular/examples/code-wrapping.md +252 -0
  124. package/dist/skills/reef-style-frontend/variants/angular/examples/component-types-pipes.md +56 -0
  125. package/dist/skills/reef-style-frontend/variants/angular/examples/entity-types.md +100 -0
  126. package/dist/skills/reef-style-frontend/variants/angular/examples/forms-layer.md +119 -0
  127. package/dist/skills/reef-style-frontend/variants/angular/examples/service-routing.md +95 -0
  128. package/dist/skills/reef-style-frontend/variants/angular/quick-reference.md +100 -0
  129. package/dist/skills/reef-testcase/SKILL.md +138 -0
  130. package/dist/skills/reef-testcase/references/coverage-dimensions.md +99 -0
  131. package/dist/skills/reef-testcase/references/test-case-template.md +63 -0
  132. package/dist/skills/sweep-init/SKILL.md +286 -0
  133. package/dist/skills/sweep-init/scripts/flow-selector.mjs +305 -0
  134. package/dist/skills/sweep-plan/SKILL.md.tmpl +312 -0
  135. package/dist/skills/sweep-plan/references/test-flow-template.md +46 -0
  136. package/dist/skills/sweep-run/SKILL.md +437 -0
  137. package/dist/skills/sweep-run/scripts/env-manager.mjs +208 -0
  138. package/dist/skills/sweep-run/scripts/flow-parser.mjs +329 -0
  139. package/dist/skills/sweep-run/scripts/flow-selector.mjs +483 -0
  140. package/dist/skills/sweep-run/scripts/mcp-manager.mjs +208 -0
  141. package/dist/skills/sweep-run/scripts/spec-compiler.mjs +303 -0
  142. package/dist/skills/tide-discuss/SKILL.md.tmpl +449 -0
  143. package/dist/skills/tide-discuss/references/checklists.md +88 -0
  144. package/dist/skills/tide-discuss/references/data-format.md +237 -0
  145. package/dist/skills/tide-discuss/references/prd-template.md +134 -0
  146. package/dist/skills/tide-discuss/references/publish-flow.md +167 -0
  147. package/dist/skills/tide-discuss/references/role-prompts.md +105 -0
  148. package/package.json +38 -0
@@ -0,0 +1,56 @@
1
+ ```java
2
+ @SpringBootTest
3
+ @Transactional
4
+ @ActiveProfiles("test")
5
+ @Testcontainers
6
+ class UserServiceIntegrationTest {
7
+
8
+ @Container @ServiceConnection
9
+ static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
10
+
11
+ @Autowired private UserService userService;
12
+
13
+ // ──── 读 ────
14
+
15
+ @Test @Sql("/user-service-test-data.sql")
16
+ void should_returnAllUsers() {
17
+ assertThat(userService.findAll())
18
+ .extracting(UserDTO::email)
19
+ .containsExactlyInAnyOrder("alice@test.com", "bob@test.com");
20
+ }
21
+
22
+ @Test
23
+ void should_throw_when_userNotFound() {
24
+ assertThatThrownBy(() -> userService.findById(999L))
25
+ .isInstanceOf(NotFoundException.class);
26
+ }
27
+
28
+ // ──── 写 ────
29
+
30
+ @Test
31
+ void should_createUser() {
32
+ UserDTO result = userService.create(new CreateUserRequest("new@test.com", "New User"));
33
+ assertThat(result.id()).isNotNull();
34
+ assertThat(result.email()).isEqualTo("new@test.com");
35
+ }
36
+
37
+ @Test
38
+ void should_throw_when_duplicateEmail() {
39
+ userService.create(new CreateUserRequest("dup@test.com", "First"));
40
+ assertThatThrownBy(() ->
41
+ userService.create(new CreateUserRequest("dup@test.com", "Second")))
42
+ .isInstanceOf(AlreadyExistsException.class);
43
+ }
44
+
45
+ // ──── 事务 ────
46
+
47
+ @Test
48
+ void should_rollback_on_failure() {
49
+ userService.create(new CreateUserRequest("a@test.com", "A"));
50
+ assertThatThrownBy(() ->
51
+ userService.create(new CreateUserRequest("a@test.com", "A Duplicate")))
52
+ .isInstanceOf(AlreadyExistsException.class);
53
+ // @Transactional 自动回滚,数据库只有一条 a@test.com
54
+ }
55
+ }
56
+ ```
@@ -0,0 +1,83 @@
1
+ # Spring Service 测试规范
2
+
3
+ `@SpringBootTest` + `@Transactional`:加载完整上下文,每个测试结束后事务回滚,互不影响。
4
+
5
+ ## 基础结构
6
+
7
+ ```java
8
+ @SpringBootTest
9
+ @Transactional
10
+ @ActiveProfiles("test")
11
+ class UserServiceTest {
12
+ @Autowired private UserService userService;
13
+ @Autowired private TestEntityManager entityManager; // 直接操作数据库准备数据
14
+ }
15
+ ```
16
+
17
+ > **⚠️ 何时用 `@SpringBootTest` vs `@ExtendWith(MockitoExtension.class)`**
18
+ > - **数据库交互**(Repository / 事务)→ `@SpringBootTest`
19
+ > - **纯业务逻辑**(计算、校验、映射)→ `MockitoExtension`(更快)
20
+
21
+ ## 测试模式
22
+
23
+ ### 读操作
24
+
25
+ ```java
26
+ @Test
27
+ void should_returnUser_when_findById() {
28
+ User user = entityManager.persistAndFlush(new User("test@test.com", "Test"));
29
+ UserDTO result = userService.findById(user.getId());
30
+ assertThat(result).extracting(UserDTO::id, UserDTO::email)
31
+ .containsExactly(user.getId(), "test@test.com");
32
+ }
33
+ ```
34
+
35
+ ### 写操作
36
+
37
+ ```java
38
+ @Test
39
+ void should_createUser() {
40
+ UserDTO result = userService.create(new CreateUserRequest("new@test.com", "New"));
41
+ assertThat(result.id()).isNotNull();
42
+ // 验证数据库真实写入
43
+ User saved = entityManager.find(User.class, result.id());
44
+ assertThat(saved).isNotNull();
45
+ }
46
+ ```
47
+
48
+ ### 异常路径
49
+
50
+ ```java
51
+ @Test
52
+ void should_throw_when_duplicateEmail() {
53
+ entityManager.persistAndFlush(new User("dup@test.com", "Existing"));
54
+ assertThatThrownBy(() -> userService.create(
55
+ new CreateUserRequest("dup@test.com", "Duplicate")))
56
+ .isInstanceOf(AlreadyExistsException.class);
57
+ }
58
+ ```
59
+
60
+ ### 事务回滚
61
+
62
+ ```java
63
+ @Test
64
+ void should_rollback_on_failure() {
65
+ userService.create(new CreateUserRequest("a@test.com", "A"));
66
+ assertThatThrownBy(() ->
67
+ userService.create(new CreateUserRequest("a@test.com", "A2")))
68
+ .isInstanceOf(AlreadyExistsException.class);
69
+ // @Transactional 自动回滚,数据库只有一条 a@test.com
70
+ }
71
+ ```
72
+
73
+ ## 关键规则
74
+
75
+ | 场景 | 做法 |
76
+ |------|------|
77
+ | 需要数据库交互 | `@SpringBootTest` + `TestEntityManager` |
78
+ | 纯业务逻辑 | `@ExtendWith(MockitoExtension.class)` |
79
+ | 测试隔离 | `@Transactional`(自动回滚) |
80
+ | 测试配置 | `@ActiveProfiles("test")` |
81
+ | 数据准备 | `entityManager.persistAndFlush()` 或 `@Sql` |
82
+
83
+ > **注意:** `@Transactional` 不回滚 `@PostConstruct` 和异步操作(`@Async`)中的操作。
@@ -0,0 +1,77 @@
1
+ # Alembic 迁移实践
2
+
3
+ ## 初始化
4
+
5
+ ```bash
6
+ # 初始化 Alembic
7
+ alembic init -t async migrations
8
+
9
+ # 配置 async 引擎(migrations/env.py 中)
10
+ # target_metadata = Base.metadata
11
+ ```
12
+
13
+ ## 创建迁移
14
+
15
+ ```bash
16
+ # ✅ 自动生成迁移(推荐)
17
+ alembic revision --autogenerate -m "add user table"
18
+
19
+ # ⚠️ 检查自动生成的内容是否正确后再提交
20
+ # 自动生成不能 100% 检测到所有变更(如列重命名)
21
+
22
+ # ✅ 手动创建空迁移(特殊场景)
23
+ alembic revision -m "migrate data"
24
+ ```
25
+
26
+ ## 应用迁移
27
+
28
+ ```bash
29
+ # 升级到最新
30
+ alembic upgrade head
31
+
32
+ # 回滚一个版本
33
+ alembic downgrade -1
34
+
35
+ # 查看历史
36
+ alembic history
37
+
38
+ # 查看当前版本
39
+ alembic current
40
+ ```
41
+
42
+ ## 迁移文件规范
43
+
44
+ ```python
45
+ """add user table
46
+
47
+ Revision ID: abc123
48
+ Revises:
49
+ Create Date: 2026-06-25 10:00:00
50
+ """
51
+ from alembic import op
52
+ import sqlalchemy as sa
53
+
54
+ # revision identifiers
55
+ revision = "abc123"
56
+ down_revision = None
57
+
58
+ def upgrade() -> None:
59
+ op.create_table(
60
+ "users",
61
+ sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
62
+ sa.Column("name", sa.String(length=100), nullable=False),
63
+ sa.Column("email", sa.String(length=255), nullable=False),
64
+ sa.Column("created_at", sa.DateTime(), server_default=sa.func.now()),
65
+ sa.PrimaryKeyConstraint("id"),
66
+ sa.UniqueConstraint("email"),
67
+ )
68
+
69
+ def downgrade() -> None:
70
+ op.drop_table("users")
71
+ ```
72
+
73
+ **规范:**
74
+ - 每个迁移文件只做**一件事**(一个表或一个字段变更)
75
+ - `upgrade()` 和 `downgrade()` 必须互逆
76
+ - `down_revision` 必须正确指向前一个迁移
77
+ - **禁止**在生产环境直接执行未经 review 的自动生成迁移
@@ -0,0 +1,164 @@
1
+ # API 规范(Python / FastAPI)
2
+
3
+ ## 速查
4
+
5
+ | 场景 | 决策 |
6
+ | --- | --- |
7
+ | 资源路径 | 英文复数 kebab-case:`/api/v1/user-roles`,不用 camelCase |
8
+ | 路径变量 | snake_case:`/api/v1/users/{user_id}` |
9
+ | 自定义动作 | 遵循 REST 语义;无法用标准 CRUD 时用 `@router.post("/{id}:action")` |
10
+ | 查询参数 | snake_case:`?status=active&page=1&page_size=20` |
11
+ | 统一响应体 | `response_model` + `ApiResponse[T]` / `PageResponse[T]` 包装 |
12
+ | 版本策略 | URL path 前缀 `/api/v1/` |
13
+ | 错误响应 | 统一 JSON:`{"code": "USER_001", "message": "...", "detail": {}}` |
14
+ | OpenAPI | FastAPI 自动生成,配合 `summary` / `description` / `tags` |
15
+ | 分页 | `page`(1-based) + `page_size`(默认 20,最大 100) |
16
+
17
+ ## 核心规范
18
+
19
+ ### 资源命名
20
+
21
+ - **资源用复数**:`/users`、`/orders`
22
+ - **嵌套资源**:`/apps/{app_id}/users/{user_id}`
23
+ - **路径 kebab-case**:/api/v1/user-roles,不是 `/api/v1/userRoles`
24
+ - **路径参数 snake_case**:`{user_id}`,不是 `{userId}`
25
+ - **查询参数 snake_case**:`created_before`、`sort_by`
26
+
27
+ ### 统一响应体
28
+
29
+ ```python
30
+ from pydantic import BaseModel
31
+ from typing import Generic, TypeVar, List, Optional
32
+
33
+ T = TypeVar("T")
34
+
35
+
36
+ # ✅ 单资源响应
37
+ class ApiResponse(BaseModel, Generic[T]):
38
+ success: bool
39
+ data: Optional[T] = None
40
+ message: Optional[str] = None
41
+
42
+ @staticmethod
43
+ def success(data: T) -> "ApiResponse[T]":
44
+ return ApiResponse(success=True, data=data)
45
+
46
+ @staticmethod
47
+ def message(msg: str) -> "ApiResponse[T]":
48
+ return ApiResponse(success=True, message=msg)
49
+
50
+
51
+ # ✅ 分页响应
52
+ class PageResponse(BaseModel, Generic[T]):
53
+ items: List[T]
54
+ page: int
55
+ page_size: int
56
+ total: int
57
+ total_pages: int
58
+
59
+
60
+ # ✅ 错误响应
61
+ class ErrorResponse(BaseModel):
62
+ code: str
63
+ message: str
64
+ detail: dict = {}
65
+ ```
66
+
67
+ ```python
68
+ # ✅ 好:统一响应
69
+ from fastapi import APIRouter
70
+
71
+ router = APIRouter(prefix="/api/v1/users", tags=["users"])
72
+
73
+ @router.get("/{user_id}", response_model=ApiResponse[UserResponse])
74
+ async def get_user(user_id: int, user_service: UserService = Depends()):
75
+ user = await user_service.get_user(user_id)
76
+ return ApiResponse.success(user)
77
+
78
+ @router.get("", response_model=PageResponse[UserResponse])
79
+ async def list_users(
80
+ page: int = Query(1, ge=1),
81
+ page_size: int = Query(20, ge=1, le=100),
82
+ user_service: UserService = Depends(),
83
+ ):
84
+ return await user_service.list_users(page=page, page_size=page_size)
85
+
86
+ # ❌ 坏:直接返回实体
87
+ @router.get("/{user_id}")
88
+ async def get_user(user_id: int):
89
+ return await user_service.get_user(user_id) # ← 未包装,格式不一致
90
+ ```
91
+
92
+ ### 版本策略
93
+
94
+ ```python
95
+ # ✅ 好:按版本分组
96
+ # app/api/v1/users.py
97
+ router_v1 = APIRouter(prefix="/api/v1/users", tags=["users-v1"])
98
+
99
+ # app/api/v2/users.py
100
+ router_v2 = APIRouter(prefix="/api/v2/users", tags=["users-v2"])
101
+ ```
102
+
103
+ - **非破坏性变更**(新增字段、新增可选参数):停留在当前版本
104
+ - **破坏性变更**(重命名字段、删除字段):创建 `/api/v2/...`
105
+ - **废弃端点**:`@router.get("/...", deprecated=True)`
106
+
107
+ ### 分页约定
108
+
109
+ ```python
110
+ # ✅ 好:标准分页
111
+ class PaginationParams:
112
+ """分页参数依赖注入"""
113
+ def __init__(
114
+ self,
115
+ page: int = Query(1, ge=1, description="页码(从1开始)"),
116
+ page_size: int = Query(20, ge=1, le=100, description="每页数量"),
117
+ ):
118
+ self.page = page
119
+ self.page_size = page_size
120
+ self.offset = (page - 1) * page_size
121
+
122
+ @router.get("", response_model=PageResponse[UserResponse])
123
+ async def list_users(
124
+ pagination: PaginationParams = Depends(),
125
+ user_service: UserService = Depends(),
126
+ ):
127
+ items, total = await user_service.list_users(
128
+ skip=pagination.offset, limit=pagination.page_size
129
+ )
130
+ return PageResponse(
131
+ items=items, page=pagination.page,
132
+ page_size=pagination.page_size, total=total,
133
+ total_pages=-(-total // pagination.page_size), # ceil division
134
+ )
135
+ ```
136
+
137
+ ### OpenAPI 文档
138
+
139
+ FastAPI 自动基于 Pydantic model + 路由生成 OpenAPI。额外配置:
140
+
141
+ ```python
142
+ # ✅ 好:在路由上添加接口描述
143
+ @router.get(
144
+ "/{user_id}",
145
+ summary="获取用户详情",
146
+ description="根据用户 ID 获取完整的用户信息",
147
+ response_model=ApiResponse[UserResponse],
148
+ responses={
149
+ 404: {"description": "用户不存在", "model": ErrorResponse},
150
+ 403: {"description": "无权限", "model": ErrorResponse},
151
+ },
152
+ )
153
+ async def get_user(user_id: int, ...):
154
+ ...
155
+
156
+ # ✅ 好:全局 OpenAPI 元信息
157
+ app = FastAPI(
158
+ title="My App API",
159
+ description="用户管理系统 API",
160
+ version="1.0.0",
161
+ docs_url="/api/docs" if settings.ENV != "production" else None,
162
+ redoc_url=None,
163
+ )
164
+ ```
@@ -0,0 +1,139 @@
1
+ # 依赖管理规范(Python)
2
+
3
+ ## 速查
4
+
5
+ | 场景 | 决策 |
6
+ | --- | --- |
7
+ | 包管理器 | `uv`(统一管理和运行) |
8
+ | 声明依赖 | `uv add {package}` 或直接在 `pyproject.toml` 中声明 |
9
+ | 版本声明 | 在 `pyproject.toml` 中指定主版本范围:`>=1.0,<2.0` |
10
+ | 禁止通配符版本 | 禁止 `*` 或 `>=0.0.0` |
11
+ | 禁止硬编码 | 禁止 `pip install package==1.0.0`(不可复现) |
12
+ | 依赖分组 | `[project.dependencies]`(运行)+ `[project.optional-dependencies]`(dev) |
13
+ | 锁定文件 | `uv.lock` — 提交到 Git |
14
+ | CVE 检查 | `uv audit` 扫描已知漏洞 |
15
+ | 版本升级 | 手动升级后运行 `uv lock` 更新锁定文件 |
16
+
17
+ ## 核心规范
18
+
19
+ ### pyproject.toml 结构
20
+
21
+ ```toml
22
+ [project]
23
+ name = "my-app"
24
+ version = "0.1.0"
25
+ requires-python = ">=3.10"
26
+
27
+ dependencies = [
28
+ "fastapi>=0.115.0,<1.0.0",
29
+ "sqlalchemy>=2.0,<3.0",
30
+ "alembic>=1.14,<2.0",
31
+ "pydantic>=2.10,<3.0",
32
+ "pydantic-settings>=2.7,<3.0",
33
+ "httpx>=0.28,<1.0",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest>=8.0,<9.0",
39
+ "pytest-asyncio>=0.24,<1.0",
40
+ "ruff>=0.8,<1.0",
41
+ "mypy>=1.13,<2.0",
42
+ "httpx>=0.28,<1.0",
43
+ ]
44
+
45
+ [tool.ruff]
46
+ # ruff 配置...
47
+
48
+ [tool.mypy]
49
+ # mypy 配置...
50
+ ```
51
+
52
+ **规范:**
53
+ - 运行依赖在 `[project.dependencies]` 中声明
54
+ - 开发/测试依赖在 `[project.optional-dependencies]` 中分组
55
+ - 每组使用语义化版本范围:`>=min,<max`
56
+ - 同一生态的库保持版本一致(如 FastAPI + Pydantic 需兼容)
57
+
58
+ ### 禁止通配符版本
59
+
60
+ ```toml
61
+ # ❌ 坏:通配符或不限版本
62
+ dependencies = [
63
+ "fastapi=*",
64
+ "sqlalchemy>=0.0.0",
65
+ ]
66
+
67
+ # ✅ 好:限制主版本范围
68
+ dependencies = [
69
+ "fastapi>=0.115.0,<1.0.0",
70
+ "sqlalchemy>=2.0,<3.0",
71
+ ]
72
+ ```
73
+
74
+ ### uv 命令速查
75
+
76
+ ```bash
77
+ # 添加依赖
78
+ uv add fastapi
79
+
80
+ # 添加开发依赖
81
+ uv add --dev pytest pytest-asyncio
82
+
83
+ # 移除依赖
84
+ uv remove fastapi
85
+
86
+ # 更新锁文件(升级所有兼容版本)
87
+ uv lock
88
+
89
+ # 升级特定包
90
+ uv lock --upgrade-package fastapi
91
+
92
+ # 安全审计
93
+ uv audit
94
+
95
+ # 查看依赖树
96
+ uv tree
97
+ ```
98
+
99
+ ```bash
100
+ # ❌ 坏:直接 pip install(绕过 uv.lock)
101
+ pip install fastapi==0.115.0
102
+
103
+ # ✅ 好:用 uv 管理所有依赖
104
+ uv add fastapi
105
+ ```
106
+
107
+ ### 版本升级规则
108
+
109
+ | 升级类型 | 操作 | 验证 |
110
+ |---------|------|------|
111
+ | Major 升级 | 手动升级,独立 PR | 需检查 breaking changes + API 兼容性 |
112
+ | Minor 升级 | `uv lock --upgrade-package {pkg}` | 运行完整测试套件 |
113
+ | Patch 升级 | `uv lock --upgrade-package {pkg}` | 运行测试 + lint |
114
+
115
+ ### 安全审计
116
+
117
+ ```bash
118
+ # 扫描 CVE
119
+ uv audit
120
+
121
+ # 输出示例:
122
+ # Found 2 vulnerabilities:
123
+ # - urllib3<2.2.3 (CVE-2024-37891, medium)
124
+ # - requests<2.32.0 (CVE-2024-35195, medium)
125
+ ```
126
+
127
+ **规范:**
128
+ - 每次 CI 中运行 `uv audit`
129
+ - 紧急 CVE(CVSS >= 7.0):1 天内升级并验证
130
+ - 无法立即升级时,加备注和缓解措施文档
131
+
132
+ ### 已知 CVE 常见的 Python 包
133
+
134
+ | 包 | 受影响版本 | 最低安全版本 |
135
+ |---|----------|------------|
136
+ | urllib3 | < 2.2.3 | >= 2.2.3 |
137
+ | requests | < 2.32.0 | >= 2.32.0 |
138
+ | Jinja2 | < 3.1.5 | >= 3.1.5 |
139
+ | cryptography | < 43.0.1 | >= 43.0.1 |
@@ -0,0 +1,177 @@
1
+ # 异常处理规范(Python / FastAPI)
2
+
3
+ ## 速查
4
+
5
+ | 场景 | 决策 |
6
+ | --- | --- |
7
+ | 业务异常 | 继承 `AppError(HTTPException)`,含 `code` + `status_code` + `detail` |
8
+ | 全局捕获 | `@app.exception_handler(AppError)` 统一处理 |
9
+ | 错误响应 | `{"code": "USER_001", "message": "...", "detail": {}}` |
10
+ | 错误码 | `{MODULE}_{NNN}` 枚举 |
11
+ | 参数校验失败 | FastAPI 自动处理 `RequestValidationError` |
12
+ | 未知异常 | 兜底 500 — `GENERIC_001`,记录完整 traceback |
13
+ | Service 层异常 | 抛出 `AppError` 子类,不在 Router 中 try-catch |
14
+
15
+ ## 核心规范
16
+
17
+ ### 业务异常继承层次
18
+
19
+ ```
20
+ HTTPException (fastapi)
21
+ └── AppError ← 抽象基类
22
+ ├── NotFoundError (404)
23
+ ├── ConflictError (409)
24
+ ├── ValidationError (400)
25
+ ├── PermissionError (403)
26
+ └── FailedPreconditionError (400)
27
+ ```
28
+
29
+ ```python
30
+ from fastapi import HTTPException
31
+ from starlette.status import *
32
+
33
+ class AppError(HTTPException):
34
+ """业务异常基类。所有业务异常必须继承此类。"""
35
+ def __init__(self, status_code: int, code: str, message: str, detail: dict | None = None):
36
+ super().__init__(status_code=status_code, detail={
37
+ "code": code,
38
+ "message": message,
39
+ "detail": detail or {},
40
+ })
41
+
42
+ # 子类
43
+ class NotFoundError(AppError):
44
+ def __init__(self, code: str, resource_type: str, resource_id: any):
45
+ super().__init__(
46
+ status_code=HTTP_404_NOT_FOUND,
47
+ code=code,
48
+ message=f"{resource_type} not found: {resource_id}",
49
+ detail={"resource_type": resource_type, "resource_id": resource_id},
50
+ )
51
+
52
+ class ConflictError(AppError):
53
+ def __init__(self, code: str, message: str):
54
+ super().__init__(status_code=HTTP_409_CONFLICT, code=code, message=message)
55
+
56
+ class ValidationError(AppError):
57
+ def __init__(self, code: str, message: str, errors: list[dict] | None = None):
58
+ super().__init__(
59
+ status_code=HTTP_400_BAD_REQUEST,
60
+ code=code,
61
+ message=message,
62
+ detail={"errors": errors} if errors else {},
63
+ )
64
+
65
+ class PermissionDeniedError(AppError):
66
+ def __init__(self, code: str = "AUTH_201", message: str = "无权限访问"):
67
+ super().__init__(status_code=HTTP_403_FORBIDDEN, code=code, message=message)
68
+ ```
69
+
70
+ ### 错误码枚举
71
+
72
+ ```python
73
+ from enum import Enum
74
+
75
+ class ErrorCode(str, Enum):
76
+ # User — 001~099 输入验证, 100~199 资源状态, 200~299 权限
77
+ USER_001 = "USER_001" # 用户名已存在
78
+ USER_002 = "USER_002" # 邮箱格式无效
79
+ USER_100 = "USER_100" # 用户不存在
80
+ USER_101 = "USER_101" # 用户已禁用
81
+ USER_200 = "USER_200" # 无权操作该用户
82
+
83
+ # Order
84
+ ORDER_001 = "ORDER_001" # 订单金额无效
85
+ ORDER_100 = "ORDER_100" # 订单不存在
86
+ ORDER_101 = "ORDER_101" # 订单状态不允许操作
87
+
88
+ # Auth
89
+ AUTH_001 = "AUTH_001" # Token 已过期
90
+ AUTH_002 = "AUTH_002" # Token 无效
91
+ AUTH_201 = "AUTH_201" # 无权限访问
92
+
93
+ # Generic
94
+ GENERIC_001 = "GENERIC_001" # 系统内部错误
95
+ GENERIC_404 = "GENERIC_404" # 接口不存在
96
+ ```
97
+
98
+ ### 全局异常处理器
99
+
100
+ ```python
101
+ from fastapi import FastAPI, Request
102
+ from fastapi.responses import JSONResponse
103
+ from fastapi.exceptions import RequestValidationError
104
+
105
+ app = FastAPI()
106
+
107
+
108
+ # ✅ 业务异常统一处理
109
+ @app.exception_handler(AppError)
110
+ async def app_error_handler(request: Request, exc: AppError):
111
+ return JSONResponse(
112
+ status_code=exc.status_code,
113
+ content=exc.detail,
114
+ )
115
+
116
+
117
+ # ✅ FastAPI 参数校验失败(Pydantic 校验)
118
+ @app.exception_handler(RequestValidationError)
119
+ async def validation_error_handler(request: Request, exc: RequestValidationError):
120
+ errors = [
121
+ {"field": err["loc"][-1], "message": err["msg"]}
122
+ for err in exc.errors()
123
+ ]
124
+ return JSONResponse(
125
+ status_code=422,
126
+ content={
127
+ "code": "VALIDATION_001",
128
+ "message": "参数校验失败",
129
+ "detail": {"errors": errors},
130
+ },
131
+ )
132
+
133
+
134
+ # ✅ 兜底:未捕获异常 → 500
135
+ @app.exception_handler(Exception)
136
+ async def unexpected_error_handler(request: Request, exc: Exception):
137
+ logger.error("Unexpected error", exc_info=True) # 完整 traceback 只打日志
138
+ return JSONResponse(
139
+ status_code=500,
140
+ content={
141
+ "code": "GENERIC_001",
142
+ "message": "系统内部错误",
143
+ "detail": {},
144
+ },
145
+ )
146
+ ```
147
+
148
+ **规范:**
149
+ - 不要在 Router 函数中写 try-except 包裹业务逻辑
150
+ - 不要吞异常后返回 `None` 或空字典
151
+ - 兜底 handler 必须 `logger.error(..., exc_info=True)`
152
+ - `RequestValidationError` 的 detail 中返回具体字段错误信息
153
+
154
+ ### 异常使用示例
155
+
156
+ ```python
157
+ # Service 层 — 直接抛异常
158
+ class UserService:
159
+ async def get_user(self, user_id: int) -> UserResponse:
160
+ user = await user_repository.get_by_id(user_id)
161
+ if user is None:
162
+ raise NotFoundError(ErrorCode.USER_100, "User", user_id)
163
+ return UserResponse.from_orm(user)
164
+
165
+ async def create_user(self, request: CreateUserRequest) -> UserResponse:
166
+ existing = await user_repository.get_by_email(request.email)
167
+ if existing:
168
+ raise ConflictError(ErrorCode.USER_001, "邮箱已注册")
169
+ user = await user_repository.create(request)
170
+ return UserResponse.from_orm(user)
171
+
172
+ # Router 层 — 不需要 try-catch
173
+ @router.post("", response_model=ApiResponse[UserResponse])
174
+ async def create_user(request: CreateUserRequest, user_service: UserService = Depends()):
175
+ user = await user_service.create_user(request)
176
+ return ApiResponse.success(user)
177
+ ```