@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.
- package/README.md +72 -0
- package/dist/agents/reef-inspect-figma.md +77 -0
- package/dist/agents/reef-review-backend.md.tmpl +112 -0
- package/dist/agents/reef-review-frontend.md.tmpl +78 -0
- package/dist/agents/reef-review-infra.md +47 -0
- package/dist/agents/reef-review-security.md.tmpl +80 -0
- package/dist/agents/reef-scope-analysis.md +64 -0
- package/dist/build-registry.js +375 -0
- package/dist/cli.js +8581 -0
- package/dist/config-schema.json +133 -0
- package/dist/env-examples/context7.env-example +19 -0
- package/dist/env-examples/feishu-wiki.env-example +16 -0
- package/dist/env-examples/figma.env-example +16 -0
- package/dist/env-examples/github.env-example +20 -0
- package/dist/env-examples/jira.env-example +20 -0
- package/dist/hooks/mcp-hook.sh +77 -0
- package/dist/hooks/reef-auto-format.sh.tmpl +72 -0
- package/dist/hooks/reef-block-dangerous.sh +70 -0
- package/dist/hooks/reef-hooks.json +72 -0
- package/dist/hooks/reef-intent-detect.sh +129 -0
- package/dist/hooks/reef-protect-files.sh +55 -0
- package/dist/hooks/reef-run-tests.sh +84 -0
- package/dist/hooks/reef-scope-check.sh +386 -0
- package/dist/hooks/reef-scope-ci.sh +28 -0
- package/dist/hooks/reef-scope-gate.sh +115 -0
- package/dist/hooks/reef-scope-pre-commit.sh.tmpl +28 -0
- package/dist/hooks/reef-scope-setup.sh +204 -0
- package/dist/hooks/reef-scope-split.sh +203 -0
- package/dist/hooks/sweep-hooks.json +14 -0
- package/dist/hooks/sweep-mcp-hook.sh +77 -0
- package/dist/hooks/tide-hooks.json +14 -0
- package/dist/hooks/tide-session-preload.sh +17 -0
- package/dist/mcp/code-hosting/github.json +20 -0
- package/dist/mcp/design-tools/figma.json +19 -0
- package/dist/mcp/docs-reference/context7.json +28 -0
- package/dist/mcp/e2e-testing/playwright.json +13 -0
- package/dist/mcp/knowledge-base/feishu-wiki.json +19 -0
- package/dist/mcp/project-management/jira.json +27 -0
- package/dist/mcp-skills/deepflow-mcp-feishu-wiki-read/SKILL.md +65 -0
- package/dist/mcp-skills/deepflow-mcp-feishu-wiki-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepflow-mcp-figma-read/SKILL.md +98 -0
- package/dist/mcp-skills/deepflow-mcp-github-read/SKILL.md +62 -0
- package/dist/mcp-skills/deepflow-mcp-github-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepflow-mcp-jira-read/SKILL.md +80 -0
- package/dist/mcp-skills/deepflow-mcp-jira-write/SKILL.md +74 -0
- package/dist/mcp-skills/deepflow-mcp-playwright-read/SKILL.md +79 -0
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-read/SKILL.md +65 -0
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepstorm-mcp-figma-read/SKILL.md +98 -0
- package/dist/mcp-skills/deepstorm-mcp-github-read/SKILL.md +62 -0
- package/dist/mcp-skills/deepstorm-mcp-github-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepstorm-mcp-jira-read/SKILL.md +80 -0
- package/dist/mcp-skills/deepstorm-mcp-jira-write/SKILL.md +74 -0
- package/dist/mcp-skills/deepstorm-mcp-playwright-read/SKILL.md +79 -0
- package/dist/registry.json +818 -0
- package/dist/skills/atoll-ops/SKILL.md +46 -0
- package/dist/skills/reef-commit/SKILL.md +127 -0
- package/dist/skills/reef-gen-backend/SKILL.md.tmpl +87 -0
- package/dist/skills/reef-gen-backend/variants/java/steps.md +28 -0
- package/dist/skills/reef-gen-backend/variants/python/steps.md +70 -0
- package/dist/skills/reef-gen-frontend/SKILL.md.tmpl +83 -0
- package/dist/skills/reef-gen-frontend/variants/angular/steps.md +30 -0
- package/dist/skills/reef-harden/EXAMPLES.md +89 -0
- package/dist/skills/reef-harden/SKILL.md +136 -0
- package/dist/skills/reef-pr/SKILL.md +97 -0
- package/dist/skills/reef-review/SKILL.md.tmpl +107 -0
- package/dist/skills/reef-scope/SKILL.md +134 -0
- package/dist/skills/reef-start/SKILL.md.tmpl +562 -0
- package/dist/skills/reef-start/references/jira-start-subagent.md +60 -0
- package/dist/skills/reef-style-backend/SKILL.md.tmpl +134 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/chat-client.md +96 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/rag.md +94 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/structured-output.md +62 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/tool-calling.md +68 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/quick-reference.md +220 -0
- package/dist/skills/reef-style-backend/fragments/java/api-spec/quick-reference.md +148 -0
- package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/examples/database-migration.md +131 -0
- package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/quick-reference.md +103 -0
- package/dist/skills/reef-style-backend/fragments/java/dependency-management/quick-reference.md +119 -0
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/examples/error-code-enum.md +101 -0
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/quick-reference.md +181 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/controller.md +95 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md +121 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/infrastructure.md +179 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/service-entity.md +202 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/testing.md +107 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/quick-reference.md +83 -0
- package/dist/skills/reef-style-backend/fragments/java/orm/hibernate/quick-reference.md +150 -0
- package/dist/skills/reef-style-backend/fragments/java/security-redlines/quick-reference.md +197 -0
- package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/examples/user-repository-test.md +69 -0
- package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/quick-reference.md +101 -0
- package/dist/skills/reef-style-backend/fragments/java/test/junit5/examples/user-service-test.md +61 -0
- package/dist/skills/reef-style-backend/fragments/java/test/junit5/quick-reference.md +100 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/examples/user-controller-test.md +61 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/quick-reference.md +85 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/examples/user-service-integration-test.md +56 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/quick-reference.md +83 -0
- package/dist/skills/reef-style-backend/fragments/python/alembic-migration/quick-reference.md +77 -0
- package/dist/skills/reef-style-backend/fragments/python/api-spec/quick-reference.md +164 -0
- package/dist/skills/reef-style-backend/fragments/python/dependency-management/quick-reference.md +139 -0
- package/dist/skills/reef-style-backend/fragments/python/exception-handling/quick-reference.md +177 -0
- package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md +101 -0
- package/dist/skills/reef-style-backend/fragments/python/langchain/quick-reference.md +135 -0
- package/dist/skills/reef-style-backend/fragments/python/pytest-testing/quick-reference.md +111 -0
- package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md +83 -0
- package/dist/skills/reef-style-backend/fragments/python/security-redlines/quick-reference.md +207 -0
- package/dist/skills/reef-style-backend/fragments/python/sqlalchemy-orm/quick-reference.md +91 -0
- package/dist/skills/reef-style-backend/variants/java/examples/code-wrapping.md +227 -0
- package/dist/skills/reef-style-backend/variants/java/examples/contributor-pattern.md +97 -0
- package/dist/skills/reef-style-backend/variants/java/quick-reference.md +117 -0
- package/dist/skills/reef-style-backend/variants/python/examples/crud-router.md +71 -0
- package/dist/skills/reef-style-backend/variants/python/examples/pydantic-schema.md +45 -0
- package/dist/skills/reef-style-backend/variants/python/examples/pytest-fixture.md +60 -0
- package/dist/skills/reef-style-backend/variants/python/examples/sqlalchemy-model.md +31 -0
- package/dist/skills/reef-style-backend/variants/python/quick-reference.md +111 -0
- package/dist/skills/reef-style-frontend/SKILL.md.tmpl +70 -0
- package/dist/skills/reef-style-frontend/fragments/css/tailwind/quick-reference.md +79 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest/examples/testing.md +150 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest/quick-reference.md +141 -0
- package/dist/skills/reef-style-frontend/fragments/ts-config/strict/quick-reference.md +112 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/examples/ui-components.md +134 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/quick-reference.md +66 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/code-wrapping.md +252 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/component-types-pipes.md +56 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/entity-types.md +100 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/forms-layer.md +119 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/service-routing.md +95 -0
- package/dist/skills/reef-style-frontend/variants/angular/quick-reference.md +100 -0
- package/dist/skills/reef-testcase/SKILL.md +138 -0
- package/dist/skills/reef-testcase/references/coverage-dimensions.md +99 -0
- package/dist/skills/reef-testcase/references/test-case-template.md +63 -0
- package/dist/skills/sweep-init/SKILL.md +286 -0
- package/dist/skills/sweep-init/scripts/flow-selector.mjs +305 -0
- package/dist/skills/sweep-plan/SKILL.md.tmpl +312 -0
- package/dist/skills/sweep-plan/references/test-flow-template.md +46 -0
- package/dist/skills/sweep-run/SKILL.md +437 -0
- package/dist/skills/sweep-run/scripts/env-manager.mjs +208 -0
- package/dist/skills/sweep-run/scripts/flow-parser.mjs +329 -0
- package/dist/skills/sweep-run/scripts/flow-selector.mjs +483 -0
- package/dist/skills/sweep-run/scripts/mcp-manager.mjs +208 -0
- package/dist/skills/sweep-run/scripts/spec-compiler.mjs +303 -0
- package/dist/skills/tide-discuss/SKILL.md.tmpl +449 -0
- package/dist/skills/tide-discuss/references/checklists.md +88 -0
- package/dist/skills/tide-discuss/references/data-format.md +237 -0
- package/dist/skills/tide-discuss/references/prd-template.md +134 -0
- package/dist/skills/tide-discuss/references/publish-flow.md +167 -0
- package/dist/skills/tide-discuss/references/role-prompts.md +105 -0
- package/package.json +38 -0
package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# FastAPI 最佳实践
|
|
2
|
+
|
|
3
|
+
## 路由组织
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
# ✅ 好:按领域分组路由
|
|
7
|
+
# app/api/v1/users.py
|
|
8
|
+
router = APIRouter(prefix="/api/v1/users", tags=["users"])
|
|
9
|
+
|
|
10
|
+
@router.get("/", response_model=list[UserResponse])
|
|
11
|
+
async def list_users(
|
|
12
|
+
skip: int = Query(0, ge=0),
|
|
13
|
+
limit: int = Query(20, ge=1, le=100),
|
|
14
|
+
user_service: UserService = Depends(),
|
|
15
|
+
):
|
|
16
|
+
return await user_service.list_users(skip=skip, limit=limit)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**规范:**
|
|
20
|
+
- 每个模块一个 `APIRouter`,在 `app/api/v1/__init__.py` 中 `include_router`
|
|
21
|
+
- 路径用 kebab-case:`/api/v1/user-roles`,不用 `/api/v1/userRoles`
|
|
22
|
+
- 路径变量用 snake_case:`/api/v1/users/{user_id}`
|
|
23
|
+
- 列表接口**必须**有分页参数
|
|
24
|
+
|
|
25
|
+
## 依赖注入
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
# ✅ 好:用 Depends() 隐式注入
|
|
29
|
+
@app.get("/users/{user_id}")
|
|
30
|
+
async def get_user(
|
|
31
|
+
user_id: int,
|
|
32
|
+
user_service: UserService = Depends(),
|
|
33
|
+
):
|
|
34
|
+
return await user_service.get_user(user_id)
|
|
35
|
+
|
|
36
|
+
# ❌ 坏:在 Router 中直接实例化 Service
|
|
37
|
+
@app.get("/users/{user_id}")
|
|
38
|
+
async def get_user(user_id: int): # ← 没有依赖注入
|
|
39
|
+
service = UserService() # ← 手动实例化
|
|
40
|
+
return await service.get_user(user_id)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**规范:**
|
|
44
|
+
- Service 类使用 `__init__` 接收依赖,注册为 `Depends()`
|
|
45
|
+
- 不要用全局单例模式
|
|
46
|
+
- DAO / Repository 也通过 `Depends()` 注入到 Service
|
|
47
|
+
|
|
48
|
+
## Pydantic Schema
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
# ✅ 好:Request / Response Schema 分开
|
|
52
|
+
class CreateUserRequest(BaseModel):
|
|
53
|
+
name: str = Field(..., min_length=1, max_length=100)
|
|
54
|
+
email: EmailStr
|
|
55
|
+
role: UserRole = UserRole.USER
|
|
56
|
+
|
|
57
|
+
class UserResponse(BaseModel):
|
|
58
|
+
id: int
|
|
59
|
+
name: str
|
|
60
|
+
email: EmailStr
|
|
61
|
+
role: UserRole
|
|
62
|
+
created_at: datetime
|
|
63
|
+
|
|
64
|
+
model_config = ConfigDict(from_attributes=True) # ORM 模式
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**规范:**
|
|
68
|
+
- Request Schema 用 `BaseModel`,加输入约束
|
|
69
|
+
- Response Schema 用 `ConfigDict(from_attributes=True)` 以支持 ORM 序列化
|
|
70
|
+
- CRUD 接口用 `Create*Request` / `Update*Request` / `*Response` 命名
|
|
71
|
+
|
|
72
|
+
## 全局异常处理器
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
# ✅ 好:统一注册
|
|
76
|
+
from fastapi import FastAPI, Request
|
|
77
|
+
from fastapi.responses import JSONResponse
|
|
78
|
+
|
|
79
|
+
app = FastAPI()
|
|
80
|
+
|
|
81
|
+
@app.exception_handler(AppError)
|
|
82
|
+
async def app_error_handler(request: Request, exc: AppError):
|
|
83
|
+
return JSONResponse(
|
|
84
|
+
status_code=exc.status_code,
|
|
85
|
+
content={"code": exc.code, "message": exc.detail},
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 异步配置
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
# ✅ 好:FastAPI 原生异步
|
|
93
|
+
@app.get("/health")
|
|
94
|
+
async def health():
|
|
95
|
+
# 异步 HTTP 调用
|
|
96
|
+
async with httpx.AsyncClient() as client:
|
|
97
|
+
resp = await client.get("https://status.example.com")
|
|
98
|
+
return {"status": "ok"}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**红线:** `async def` handler 内部不得调同步的 `time.sleep()` / `requests.get()` / 同步 ORM 操作。需要调同步操作时用 `asyncio.to_thread()` 包裹。
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# LangChain 开发规范
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
LangChain 是 Python 生态中的 AI 集成框架,提供统一的 API 来调用 LLM、管理对话、构建 RAG 应用。
|
|
6
|
+
|
|
7
|
+
## 核心概念
|
|
8
|
+
|
|
9
|
+
### ChatModel
|
|
10
|
+
LangChain 的中央 API,用于与大语言模型交互。
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from langchain_openai import ChatOpenAI
|
|
14
|
+
from langchain_core.messages import SystemMessage, HumanMessage
|
|
15
|
+
|
|
16
|
+
# 初始化
|
|
17
|
+
llm = ChatOpenAI(model="gpt-4o", temperature=0)
|
|
18
|
+
|
|
19
|
+
# 基础调用
|
|
20
|
+
response = llm.invoke([
|
|
21
|
+
SystemMessage(content="你是一个专业的 Python 开发者助手"),
|
|
22
|
+
HumanMessage(content="解释 FastAPI 的依赖注入")
|
|
23
|
+
])
|
|
24
|
+
print(response.content)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**最佳实践:**
|
|
28
|
+
- `ChatOpenAI` / `ChatAnthropic` 在应用初始化时创建一次,通过依赖注入传递
|
|
29
|
+
- 不要在每个请求中重新创建 LLM 实例
|
|
30
|
+
- `temperature=0` 用于确定性结果,`temperature>0` 用于创意场景
|
|
31
|
+
- API Key 通过环境变量读取,不硬编码
|
|
32
|
+
|
|
33
|
+
### Prompt Template
|
|
34
|
+
|
|
35
|
+
结构化提示词模板:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
39
|
+
|
|
40
|
+
prompt = ChatPromptTemplate.from_messages([
|
|
41
|
+
("system", "你是一个{role},请用{language}回答"),
|
|
42
|
+
("human", "{question}"),
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
chain = prompt | llm
|
|
46
|
+
result = chain.invoke({"role": "Python专家", "language": "中文", "question": "什么是异步编程?"})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Tool / 函数调用
|
|
50
|
+
|
|
51
|
+
AI 模型调用外部函数的能力:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from langchain_core.tools import tool
|
|
55
|
+
|
|
56
|
+
@tool
|
|
57
|
+
def get_user_order(user_id: str) -> list[dict]:
|
|
58
|
+
"""获取用户订单信息"""
|
|
59
|
+
# 实际业务逻辑调用 Service 层
|
|
60
|
+
return order_service.get_orders(user_id)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# 绑定工具后调用
|
|
64
|
+
llm_with_tools = llm.bind_tools([get_user_order])
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**最佳实践:**
|
|
68
|
+
- `@tool` 的 docstring 会被模型理解,写清楚参数含义和返回值格式
|
|
69
|
+
- 工具函数内部调用 Service 层,不在工具内写业务逻辑
|
|
70
|
+
- `bind_tools([])` 传工具数组,不逐个 `.bind()` 调用
|
|
71
|
+
|
|
72
|
+
### Chain / LCEL
|
|
73
|
+
|
|
74
|
+
LangChain Expression Language — 声明式管道:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from langchain_core.output_parsers import StrOutputParser
|
|
78
|
+
|
|
79
|
+
# 链式组装
|
|
80
|
+
chain = prompt | llm | StrOutputParser()
|
|
81
|
+
|
|
82
|
+
# 执行
|
|
83
|
+
result = chain.invoke({"question": "Python 异步编程的最佳实践?"})
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### RAG(检索增强生成)
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from langchain_core.vectorstores import InMemoryVectorStore
|
|
90
|
+
from langchain_openai import OpenAIEmbeddings
|
|
91
|
+
|
|
92
|
+
# 创建向量存储
|
|
93
|
+
vector_store = InMemoryVectorStore.from_texts(
|
|
94
|
+
texts=["文档内容1", "文档内容2"],
|
|
95
|
+
embedding=OpenAIEmbeddings()
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# 检索
|
|
99
|
+
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
|
|
100
|
+
|
|
101
|
+
# RAG 链
|
|
102
|
+
from langchain.chains import create_retrieval_chain
|
|
103
|
+
from langchain.chains.combine_documents import create_stuff_documents_chain
|
|
104
|
+
|
|
105
|
+
combine_docs_chain = create_stuff_documents_chain(llm, prompt)
|
|
106
|
+
rag_chain = create_retrieval_chain(retriever, combine_docs_chain)
|
|
107
|
+
result = rag_chain.invoke({"question": "某个话题"})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 依赖管理
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# 基础安装
|
|
114
|
+
uv add langchain langchain-openai
|
|
115
|
+
|
|
116
|
+
# 按需安装其他 provider
|
|
117
|
+
uv add langchain-anthropic # Claude
|
|
118
|
+
uv add langchain-google-ai # Gemini
|
|
119
|
+
|
|
120
|
+
# 向量存储
|
|
121
|
+
uv add langchain-community
|
|
122
|
+
|
|
123
|
+
# RAG 相关
|
|
124
|
+
uv add chromadb tiktoken
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 常见坑
|
|
128
|
+
|
|
129
|
+
| 场景 | 问题 | 正确做法 |
|
|
130
|
+
|------|------|---------|
|
|
131
|
+
| API Key 管理 | 硬编码在代码中 | 从 `os.getenv("OPENAI_API_KEY")` 读取 |
|
|
132
|
+
| 每次请求新建 LLM | 性能差、连接池耗尽 | 应用启动时创建单例,通过 DI 注入 |
|
|
133
|
+
| 工具函数逻辑过重 | 工具内包含全部业务逻辑 | 工具只做参数解析 + 调用 Service |
|
|
134
|
+
| 忽略 `async` 支持 | LCEL 链中调同步 IO | 使用 `ainvoke()` 和 async retriever |
|
|
135
|
+
| prompt 不设 system message | 模型行为不可控 | 始终设置 system message 定义角色和约束 |
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# pytest 测试规范
|
|
2
|
+
|
|
3
|
+
## 目录结构
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
tests/
|
|
7
|
+
├── conftest.py # 全局 fixture
|
|
8
|
+
├── unit/ # 纯逻辑单元测试
|
|
9
|
+
│ ├── test_services/
|
|
10
|
+
│ └── test_schemas/
|
|
11
|
+
└── integration/ # 集成测试(DB / API)
|
|
12
|
+
├── test_api/
|
|
13
|
+
└── conftest.py # 集成测试专用 fixture
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**规范:**
|
|
17
|
+
- `tests/unit/` — 不依赖外部服务的纯逻辑测试
|
|
18
|
+
- `tests/integration/` — 依赖数据库、HTTP 调用的测试
|
|
19
|
+
- 测试文件以 `test_` 开头,函数以 `test_` 开头
|
|
20
|
+
|
|
21
|
+
## pytest fixture
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
# ✅ 好:conftest.py 中的共享 fixture
|
|
25
|
+
import pytest
|
|
26
|
+
import pytest_asyncio
|
|
27
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
28
|
+
|
|
29
|
+
@pytest_asyncio.fixture
|
|
30
|
+
async def db_session():
|
|
31
|
+
# 每个测试独立事务,测试后回滚
|
|
32
|
+
async with async_session() as session:
|
|
33
|
+
async with session.begin():
|
|
34
|
+
yield session
|
|
35
|
+
await session.rollback()
|
|
36
|
+
|
|
37
|
+
@pytest_asyncio.fixture
|
|
38
|
+
async def user_service(db_session: AsyncSession):
|
|
39
|
+
return UserService(db=db_session)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**规范:**
|
|
43
|
+
- 测试用 `pytest-asyncio` 支持异步 fixture 和 test
|
|
44
|
+
- fixture 只在 `conftest.py` 中定义,不在测试文件中
|
|
45
|
+
- fixture 范围默认 `function`,仅共享资源用 `session` / `module`
|
|
46
|
+
|
|
47
|
+
## 异步测试
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
# ✅ 好:pytest-asyncio 异步测试
|
|
51
|
+
import pytest
|
|
52
|
+
|
|
53
|
+
@pytest.mark.asyncio
|
|
54
|
+
async def test_create_user(user_service: UserService):
|
|
55
|
+
user = await user_service.create_user(name="Alice", email="alice@example.com")
|
|
56
|
+
assert user.id is not None
|
|
57
|
+
assert user.name == "Alice"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 集成测试标记
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import pytest
|
|
64
|
+
|
|
65
|
+
# ✅ 好:用 mark 区分测试类型
|
|
66
|
+
@pytest.mark.integration
|
|
67
|
+
@pytest.mark.asyncio
|
|
68
|
+
async def test_api_list_users(client: httpx.AsyncClient):
|
|
69
|
+
response = await client.get("/api/v1/users")
|
|
70
|
+
assert response.status_code == 200
|
|
71
|
+
data = response.json()
|
|
72
|
+
assert isinstance(data, list)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## HTTP 测试
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# ✅ 好:用 httpx AsyncClient 配合 FastAPI TestClient
|
|
79
|
+
from httpx import AsyncClient, ASGITransport
|
|
80
|
+
|
|
81
|
+
@pytest_asyncio.fixture
|
|
82
|
+
async def client():
|
|
83
|
+
transport = ASGITransport(app=app)
|
|
84
|
+
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|
85
|
+
yield ac
|
|
86
|
+
|
|
87
|
+
@pytest.mark.asyncio
|
|
88
|
+
async def test_get_user(client: AsyncClient):
|
|
89
|
+
response = await client.get("/api/v1/users/1")
|
|
90
|
+
assert response.status_code == 200
|
|
91
|
+
assert response.json()["id"] == 1
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 运行命令
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# 运行全部测试
|
|
98
|
+
python -m pytest
|
|
99
|
+
|
|
100
|
+
# 指定目录
|
|
101
|
+
python -m pytest tests/unit/
|
|
102
|
+
|
|
103
|
+
# 包含覆盖率
|
|
104
|
+
python -m pytest --cov=app --cov-report=term-missing
|
|
105
|
+
|
|
106
|
+
# 仅集成测试
|
|
107
|
+
python -m pytest -m integration
|
|
108
|
+
|
|
109
|
+
# 并行运行
|
|
110
|
+
python -m pytest -n auto
|
|
111
|
+
```
|
package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# ruff + mypy 工具链规范
|
|
2
|
+
|
|
3
|
+
## ruff 配置(pyproject.toml)
|
|
4
|
+
|
|
5
|
+
```toml
|
|
6
|
+
[tool.ruff]
|
|
7
|
+
target-version = "py310"
|
|
8
|
+
line-length = 100
|
|
9
|
+
|
|
10
|
+
[tool.ruff.lint]
|
|
11
|
+
select = [
|
|
12
|
+
"F", # Pyflakes — 检测未使用导入、未定义变量等
|
|
13
|
+
"E", # pycodestyle — PEP 8 风格错误
|
|
14
|
+
"W", # pycodestyle — 警告
|
|
15
|
+
"I", # isort — 导入排序
|
|
16
|
+
"N", # pep8-naming — 命名规范
|
|
17
|
+
"UP", # pyupgrade — 新版语法建议
|
|
18
|
+
"B", # flake8-bugbear — 常见 bug 模式
|
|
19
|
+
]
|
|
20
|
+
ignore = [
|
|
21
|
+
"E501", # 行长度交给 black,不在 ruff 中检查
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[tool.ruff.lint.per-file-ignores]
|
|
25
|
+
"tests/**" = ["N"] # 测试文件放宽命名规范
|
|
26
|
+
|
|
27
|
+
[tool.ruff.format]
|
|
28
|
+
quote-style = "double"
|
|
29
|
+
indent-style = "space"
|
|
30
|
+
line-ending = "lf"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**规范:**
|
|
34
|
+
- ruff 负责 lint(`ruff check`)和 format(`ruff format`)双重职责
|
|
35
|
+
- 启用 `F` + `E` + `W` + `I` + `N` + `UP` + `B` 规则组
|
|
36
|
+
- 行长度检查(`E501`)交给 black / ruff format,不在 lint 中重复
|
|
37
|
+
|
|
38
|
+
## mypy 配置(pyproject.toml)
|
|
39
|
+
|
|
40
|
+
```toml
|
|
41
|
+
[tool.mypy]
|
|
42
|
+
strict = true
|
|
43
|
+
python_version = "3.10"
|
|
44
|
+
check_untyped_defs = true
|
|
45
|
+
warn_unused_ignores = true
|
|
46
|
+
warn_redundant_casts = true
|
|
47
|
+
warn_return_any = true
|
|
48
|
+
no_implicit_optional = true
|
|
49
|
+
disallow_any_unimported = true
|
|
50
|
+
disallow_untyped_defs = true
|
|
51
|
+
disallow_untyped_calls = true
|
|
52
|
+
|
|
53
|
+
[[tool.mypy.overrides]]
|
|
54
|
+
module = [
|
|
55
|
+
"tests.*",
|
|
56
|
+
]
|
|
57
|
+
ignore_missing_imports = true
|
|
58
|
+
disallow_untyped_defs = false
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**规范:**
|
|
62
|
+
- 生产代码使用 `strict = true`
|
|
63
|
+
- 所有函数必须有类型注释
|
|
64
|
+
- 测试文件放宽类型要求
|
|
65
|
+
|
|
66
|
+
## 常用命令
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 快速 lint
|
|
70
|
+
ruff check .
|
|
71
|
+
|
|
72
|
+
# 自动修复
|
|
73
|
+
ruff check --fix .
|
|
74
|
+
|
|
75
|
+
# 格式化
|
|
76
|
+
ruff format
|
|
77
|
+
|
|
78
|
+
# 类型检查
|
|
79
|
+
mypy .
|
|
80
|
+
|
|
81
|
+
# 完整检查(推荐 CI 用)
|
|
82
|
+
ruff check . && ruff format --check && mypy .
|
|
83
|
+
```
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# 安全红线(Python / FastAPI)
|
|
2
|
+
|
|
3
|
+
## 🔴 红线速查
|
|
4
|
+
|
|
5
|
+
| 红线 | 禁止行为 | 正确做法 | 违反后果 |
|
|
6
|
+
|------|---------|---------|---------|
|
|
7
|
+
| 密码存储 | 明文存储密码 | `hashlib` / `bcrypt` / `passlib` 哈希后存储 | P0 — 安全漏洞 |
|
|
8
|
+
| SQL 拼接 | f-string / format 拼接 SQL | SQLAlchemy ORM 或 参数化查询 | P0 — SQL 注入 |
|
|
9
|
+
| 硬编码密钥 | 代码中硬编码 Secret / Token | 环境变量 `os.getenv()` 或 `pydantic-settings` | P0 — 凭据泄露 |
|
|
10
|
+
| 敏感信息打印 | `print()` 敏感数据 | `logging` + 脱敏 + 生产级别过滤 | P1 — 信息泄露 |
|
|
11
|
+
| 输入验证缺失 | 不验证用户输入 | Pydantic model + 严格约束 | P1 — 注入/越权 |
|
|
12
|
+
| 不安全的 CORS | `allow_origins=["*"]` | 明确指定允许源列表 | P1 — CORS 安全 |
|
|
13
|
+
| 调试接口 | 生产暴露 `/docs` / 调试路由 | 根据环境变量控制开启 | P1 — 信息泄露 |
|
|
14
|
+
|
|
15
|
+
## 🔴 红线详情
|
|
16
|
+
|
|
17
|
+
### 1. 密码存储 — 禁止明文 (P0)
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# ❌ 坏:明文存储
|
|
21
|
+
user.password = request.password
|
|
22
|
+
|
|
23
|
+
# ✅ 好:哈希后存储
|
|
24
|
+
from passlib.context import CryptContext
|
|
25
|
+
|
|
26
|
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
27
|
+
|
|
28
|
+
class UserService:
|
|
29
|
+
def create_user(self, request: CreateUserRequest) -> User:
|
|
30
|
+
hashed = pwd_context.hash(request.password)
|
|
31
|
+
user = User(email=request.email, hashed_password=hashed)
|
|
32
|
+
return await user_repository.save(user)
|
|
33
|
+
|
|
34
|
+
def verify_password(self, plain: str, hashed: str) -> bool:
|
|
35
|
+
return pwd_context.verify(plain, hashed)
|
|
36
|
+
|
|
37
|
+
# 验证密码
|
|
38
|
+
is_valid = pwd_context.verify(plain_password, user.hashed_password)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. SQL 拼接 — 禁止字符串拼接 (P0)
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
# ❌ 坏:f-string 拼接 — SQL 注入
|
|
45
|
+
query = f"SELECT * FROM users WHERE email = '{email}'"
|
|
46
|
+
result = await db.execute(query)
|
|
47
|
+
|
|
48
|
+
# ❌ 坏:format 拼接
|
|
49
|
+
query = "SELECT * FROM users WHERE email = '{}'".format(email)
|
|
50
|
+
result = await db.execute(query)
|
|
51
|
+
|
|
52
|
+
# ✅ 好:SQLAlchemy ORM(首选)
|
|
53
|
+
user = await db.query(User).filter(User.email == email).first()
|
|
54
|
+
|
|
55
|
+
# ✅ 好:参数化查询(原生 SQL 时)
|
|
56
|
+
from sqlalchemy import text
|
|
57
|
+
query = text("SELECT * FROM users WHERE email = :email")
|
|
58
|
+
result = await db.execute(query, {"email": email})
|
|
59
|
+
|
|
60
|
+
# ✅ 好:原始 SQL 用 bind 参数
|
|
61
|
+
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**红线规则:**
|
|
65
|
+
- 禁止在 SQL 中使用 `f"..."` 或 `"".format()` 拼接用户输入
|
|
66
|
+
- 优先使用 SQLAlchemy ORM 的查询构建器
|
|
67
|
+
- 必须使用原生 SQL 时,必须使用参数化查询(`:name` 或 `%s`)
|
|
68
|
+
|
|
69
|
+
### 3. 硬编码密钥 — 禁止代码中内联 (P0)
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# ❌ 坏:硬编码 Secret
|
|
73
|
+
SECRET_KEY = "sk-abc123..."
|
|
74
|
+
API_KEY = "my-secret-key-12345"
|
|
75
|
+
|
|
76
|
+
# ✅ 好:pydantic-settings 从环境变量读取
|
|
77
|
+
from pydantic_settings import BaseSettings
|
|
78
|
+
|
|
79
|
+
class Settings(BaseSettings):
|
|
80
|
+
secret_key: str
|
|
81
|
+
api_key: str
|
|
82
|
+
database_url: str
|
|
83
|
+
|
|
84
|
+
model_config = SettingsConfigDict(
|
|
85
|
+
env_file=".env",
|
|
86
|
+
env_file_encoding="utf-8",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
settings = Settings() # 从 .env 或环境变量加载
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# ❌ 坏:提交到 Git 的配置文件中有明文密钥
|
|
94
|
+
# config.py
|
|
95
|
+
SECRET_KEY = "my-secret-key" # ← 硬编码
|
|
96
|
+
|
|
97
|
+
# ✅ 好:.env 本地开发(不提交 Git)
|
|
98
|
+
SECRET_KEY=my-secret-key
|
|
99
|
+
API_KEY=sk-abc123...
|
|
100
|
+
DATABASE_URL=postgresql://localhost:5432/dev
|
|
101
|
+
|
|
102
|
+
# ✅ 好:CI/CD 中使用 CI Secrets 或 Vault
|
|
103
|
+
# GitHub Actions Secrets / GitLab CI Variables
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 4. 敏感脱敏 — 日志中禁止打印敏感信息 (P1)
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import logging
|
|
110
|
+
|
|
111
|
+
logger = logging.getLogger(__name__)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ❌ 坏:直接打印敏感信息
|
|
115
|
+
logger.info(f"User login: {email}, password: {password}") # ← 密码泄露
|
|
116
|
+
logger.info(f"Token: {token}") # ← Token 泄露
|
|
117
|
+
print(f"Auth token: {token}") # ← 禁止 print
|
|
118
|
+
|
|
119
|
+
# ✅ 好:脱敏后打印
|
|
120
|
+
def mask_email(email: str) -> str:
|
|
121
|
+
at = email.find("@")
|
|
122
|
+
if at <= 1:
|
|
123
|
+
return email
|
|
124
|
+
return email[0] + "***" + email[at:]
|
|
125
|
+
|
|
126
|
+
def mask_phone(phone: str) -> str:
|
|
127
|
+
if not phone or len(phone) < 7:
|
|
128
|
+
return phone
|
|
129
|
+
return phone[:3] + "****" + phone[-4:]
|
|
130
|
+
|
|
131
|
+
logger.info(f"User login: {mask_email(email)}")
|
|
132
|
+
logger.info(f"User [id={user_id}] logged in")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**红线规则:**
|
|
136
|
+
- 禁止打印密码、Token、完整手机号、完整身份证号
|
|
137
|
+
- 响应体中密码字段不输出(Pydantic `model_config` 排除或 response schema 中忽略)
|
|
138
|
+
- 禁止 `print()` — 必须使用 `logging` 模块
|
|
139
|
+
|
|
140
|
+
### 5. 输入验证红线 (P1)
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
# ✅ 好:Pydantic model 严格约束
|
|
144
|
+
from pydantic import BaseModel, Field, EmailStr, field_validator
|
|
145
|
+
|
|
146
|
+
class CreateUserRequest(BaseModel):
|
|
147
|
+
name: str = Field(..., min_length=1, max_length=100)
|
|
148
|
+
email: EmailStr
|
|
149
|
+
age: int = Field(..., ge=0, le=150)
|
|
150
|
+
role: UserRole = UserRole.USER
|
|
151
|
+
|
|
152
|
+
@field_validator("name")
|
|
153
|
+
@classmethod
|
|
154
|
+
def validate_name(cls, v: str) -> str:
|
|
155
|
+
if not v.strip():
|
|
156
|
+
raise ValueError("名称不能为纯空格")
|
|
157
|
+
return v.strip()
|
|
158
|
+
|
|
159
|
+
# ❌ 坏:不验证或用裸 dict
|
|
160
|
+
@app.post("/users")
|
|
161
|
+
async def create_user(name: str, email: str): # ← 无约束
|
|
162
|
+
...
|
|
163
|
+
|
|
164
|
+
# ✅ 好:Router 中必须使用 Pydantic model
|
|
165
|
+
@app.post("/users", response_model=ApiResponse[UserResponse])
|
|
166
|
+
async def create_user(request: CreateUserRequest, ...):
|
|
167
|
+
...
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 6. CORS 配置红线 (P1)
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# ❌ 坏:允许所有来源
|
|
174
|
+
app.add_middleware(
|
|
175
|
+
CORSMiddleware,
|
|
176
|
+
allow_origins=["*"], # ← 生产环境禁止
|
|
177
|
+
allow_credentials=True, # ← allow_origins=["*"] 时配合 allow_credentials=True 无效且不安全
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# ✅ 好:明确指定允许的来源
|
|
181
|
+
app.add_middleware(
|
|
182
|
+
CORSMiddleware,
|
|
183
|
+
allow_origins=[
|
|
184
|
+
"https://app.example.com",
|
|
185
|
+
"https://admin.example.com",
|
|
186
|
+
],
|
|
187
|
+
allow_credentials=True,
|
|
188
|
+
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
189
|
+
allow_headers=["Authorization", "Content-Type"],
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 7. 生产环境禁用调试接口 (P1)
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
# ✅ 好:根据环境控制
|
|
197
|
+
from app.core.config import settings
|
|
198
|
+
|
|
199
|
+
app = FastAPI(
|
|
200
|
+
title="My App",
|
|
201
|
+
docs_url="/api/docs" if settings.ENV != "production" else None,
|
|
202
|
+
redoc_url=None,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# ❌ 坏:生产环境也暴露
|
|
206
|
+
app = FastAPI(docs_url="/api/docs") # ← 生产环境暴露 API 文档
|
|
207
|
+
```
|