331 lines
9.9 KiB
Markdown
331 lines
9.9 KiB
Markdown
|
|
---
|
|||
|
|
name: project-guidelines-example
|
|||
|
|
description: 「專案開發規範」範本,基於真實生產環境應用的示例。
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 專案規範技能(範例) (Project Guidelines Skill Example)
|
|||
|
|
|
|||
|
|
這是一個針對特定專案的技能範例。請以此為模板,為您自己的專案建立對應的規範。
|
|||
|
|
|
|||
|
|
本範例基於一個真實的生產環境應用:[Zenith](https://zenith.chat) — AI 驅動的客戶發現平台。
|
|||
|
|
|
|||
|
|
## 何時使用
|
|||
|
|
|
|||
|
|
在開發該規範所針對的特定專案時,請參考此技能。專案技能通常包含:
|
|||
|
|
- 架構總覽
|
|||
|
|
- 檔案結構
|
|||
|
|
- 程式碼模式 (Patterns)
|
|||
|
|
- 測試需求
|
|||
|
|
- 部署工作流
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 架構總覽 (Architecture Overview)
|
|||
|
|
|
|||
|
|
**技術棧 (Tech Stack):**
|
|||
|
|
- **前端**:Next.js 15 (App Router), TypeScript, React。
|
|||
|
|
- **後端**:FastAPI (Python), Pydantic 模型。
|
|||
|
|
- **資料庫**:Supabase (PostgreSQL)。
|
|||
|
|
- **AI**:Claude API(具備工具呼叫與結構化輸出能力)。
|
|||
|
|
- **部署**:Google Cloud Run。
|
|||
|
|
- **測試**:Playwright (E2E), pytest (後端), React Testing Library。
|
|||
|
|
|
|||
|
|
**服務架構圖:**
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 前端 │
|
|||
|
|
│ Next.js 15 + TypeScript + TailwindCSS │
|
|||
|
|
│ 部署位置:Vercel / Cloud Run │
|
|||
|
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 後端 │
|
|||
|
|
│ FastAPI + Python 3.11 + Pydantic │
|
|||
|
|
│ 部署位置:Cloud Run │
|
|||
|
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
┌───────────────┼───────────────┐
|
|||
|
|
▼ ▼ ▼
|
|||
|
|
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|||
|
|
│ Supabase │ │ Claude │ │ Redis │
|
|||
|
|
│ 資料庫 │ │ API │ │ 快取 │
|
|||
|
|
└──────────┘ └──────────┘ └──────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 檔案結構 (File Structure)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
project/
|
|||
|
|
├── frontend/ # 前端目錄
|
|||
|
|
│ └── src/
|
|||
|
|
│ ├── app/ # Next.js App Router 頁面
|
|||
|
|
│ │ ├── api/ # 前端 API 路由
|
|||
|
|
│ │ ├── (auth)/ # 身份驗證保護路由
|
|||
|
|
│ │ └── workspace/ # 主工作區應用
|
|||
|
|
│ ├── components/ # React 元件
|
|||
|
|
│ │ ├── ui/ # 基礎 UI 原子元件
|
|||
|
|
│ │ ├── forms/ # 表單相關元件
|
|||
|
|
│ │ └── layouts/ # 佈局元件
|
|||
|
|
│ ├── hooks/ # 自定義 React Hooks
|
|||
|
|
│ ├── lib/ # 工具函式庫
|
|||
|
|
│ ├── types/ # TypeScript 型別定義
|
|||
|
|
│ └── config/ # 配置項目
|
|||
|
|
│
|
|||
|
|
├── backend/ # 後端目錄
|
|||
|
|
│ ├── routers/ # FastAPI 路由處理器
|
|||
|
|
│ ├── models.py # Pydantic 資料模型
|
|||
|
|
│ ├── main.py # FastAPI 應用入口
|
|||
|
|
│ ├── auth_system.py # 身份驗證系統
|
|||
|
|
│ ├── database.py # 資料庫操作
|
|||
|
|
│ ├── services/ # 業務邏輯層
|
|||
|
|
│ └── tests/ # pytest 測試案例
|
|||
|
|
│
|
|||
|
|
├── deploy/ # 部署配置檔目錄
|
|||
|
|
├── docs/ # 專案文件目錄
|
|||
|
|
└── scripts/ # 工具指令腳本
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 程式碼模式 (Code Patterns)
|
|||
|
|
|
|||
|
|
### API 回應格式 (FastAPI)
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from pydantic import BaseModel
|
|||
|
|
from typing import Generic, TypeVar, Optional
|
|||
|
|
|
|||
|
|
T = TypeVar('T')
|
|||
|
|
|
|||
|
|
class ApiResponse(BaseModel, Generic[T]):
|
|||
|
|
success: bool
|
|||
|
|
data: Optional[T] = None
|
|||
|
|
error: Optional[str] = None
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def ok(cls, data: T) -> "ApiResponse[T]":
|
|||
|
|
return cls(success=True, data=data)
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def fail(cls, error: str) -> "ApiResponse[T]":
|
|||
|
|
return cls(success=False, error=error)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 前端 API 呼叫 (TypeScript)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
interface ApiResponse<T> {
|
|||
|
|
success: boolean
|
|||
|
|
data?: T
|
|||
|
|
error?: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function fetchApi<T>(
|
|||
|
|
endpoint: string,
|
|||
|
|
options?: RequestInit
|
|||
|
|
): Promise<ApiResponse<T>> {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(`/api${endpoint}`, {
|
|||
|
|
...options,
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
...options?.headers,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (!response.ok) {
|
|||
|
|
return { success: false, error: `HTTP ${response.status}` }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return await response.json()
|
|||
|
|
} catch (error) {
|
|||
|
|
return { success: false, error: String(error) }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Claude AI 整合(結構化輸出)
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from anthropic import Anthropic
|
|||
|
|
from pydantic import BaseModel
|
|||
|
|
|
|||
|
|
class AnalysisResult(BaseModel):
|
|||
|
|
summary: str
|
|||
|
|
key_points: list[str]
|
|||
|
|
confidence: float
|
|||
|
|
|
|||
|
|
async def analyze_with_claude(content: str) -> AnalysisResult:
|
|||
|
|
client = Anthropic()
|
|||
|
|
|
|||
|
|
response = client.messages.create(
|
|||
|
|
model="claude-sonnet-4-5-20250514",
|
|||
|
|
max_tokens=1024,
|
|||
|
|
messages=[{"role": "user", "content": content}],
|
|||
|
|
tools=[{
|
|||
|
|
"name": "provide_analysis",
|
|||
|
|
"description": "提供結構化分析內容",
|
|||
|
|
"input_schema": AnalysisResult.model_json_schema()
|
|||
|
|
}],
|
|||
|
|
tool_choice={"type": "tool", "name": "provide_analysis"}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 擷取工具呼叫結果
|
|||
|
|
tool_use = next(
|
|||
|
|
block for block in response.content
|
|||
|
|
if block.type == "tool_use"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return AnalysisResult(**tool_use.input)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### React 自定義 Hooks
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { useState, useCallback } from 'react'
|
|||
|
|
|
|||
|
|
interface UseApiState<T> {
|
|||
|
|
data: T | null
|
|||
|
|
loading: boolean
|
|||
|
|
error: string | null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function useApi<T>(
|
|||
|
|
fetchFn: () => Promise<ApiResponse<T>>
|
|||
|
|
) {
|
|||
|
|
const [state, setState] = useState<UseApiState<T>>({
|
|||
|
|
data: null,
|
|||
|
|
loading: false,
|
|||
|
|
error: null,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const execute = useCallback(async () => {
|
|||
|
|
setState(prev => ({ ...prev, loading: true, error: null }))
|
|||
|
|
|
|||
|
|
const result = await fetchFn()
|
|||
|
|
|
|||
|
|
if (result.success) {
|
|||
|
|
setState({ data: result.data!, loading: false, error: null })
|
|||
|
|
} else {
|
|||
|
|
setState({ data: null, loading: false, error: result.error! })
|
|||
|
|
}
|
|||
|
|
}, [fetchFn])
|
|||
|
|
|
|||
|
|
return { ...state, execute }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 測試需求 (Testing Requirements)
|
|||
|
|
|
|||
|
|
### 後端測試 (pytest)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 執行所有測試
|
|||
|
|
poetry run pytest tests/
|
|||
|
|
|
|||
|
|
# 執行測試並產出覆蓋率報告
|
|||
|
|
poetry run pytest tests/ --cov=. --cov-report=html
|
|||
|
|
|
|||
|
|
# 執行特定測試檔案
|
|||
|
|
poetry run pytest tests/test_auth.py -v
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**測試結構範例:**
|
|||
|
|
```python
|
|||
|
|
import pytest
|
|||
|
|
from httpx import AsyncClient
|
|||
|
|
from main import app
|
|||
|
|
|
|||
|
|
@pytest.fixture
|
|||
|
|
async def client():
|
|||
|
|
async with AsyncClient(app=app, base_url="http://test") as ac:
|
|||
|
|
yield ac
|
|||
|
|
|
|||
|
|
@pytest.mark.asyncio
|
|||
|
|
async def test_health_check(client: AsyncClient):
|
|||
|
|
response = await client.get("/health")
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
assert response.json()["status"] == "healthy"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 前端測試 (React Testing Library)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 執行測試
|
|||
|
|
npm run test
|
|||
|
|
|
|||
|
|
# 執行測試並產出覆蓋率報告
|
|||
|
|
npm run test -- --coverage
|
|||
|
|
|
|||
|
|
# 執行 E2E 端到端測試
|
|||
|
|
npm run test:e2e
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**測試結構範例:**
|
|||
|
|
```typescript
|
|||
|
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|||
|
|
import { WorkspacePanel } from './WorkspacePanel'
|
|||
|
|
|
|||
|
|
describe('WorkspacePanel 元件', () => {
|
|||
|
|
it('應正確渲染工作區', () => {
|
|||
|
|
render(<WorkspacePanel />)
|
|||
|
|
expect(screen.getByRole('main')).toBeInTheDocument()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('應能處理會話建立', async () => {
|
|||
|
|
render(<WorkspacePanel />)
|
|||
|
|
fireEvent.click(screen.getByText('New Session'))
|
|||
|
|
expect(await screen.findByText('Session created')).toBeInTheDocument()
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 部署工作流 (Deployment Workflow)
|
|||
|
|
|
|||
|
|
### 部署前檢查清單
|
|||
|
|
- [ ] 所有本地測試皆已通過。
|
|||
|
|
- [ ] 前端 `npm run build` 建置成功。
|
|||
|
|
- [ ] 後端 `poetry run pytest` 驗證通過。
|
|||
|
|
- [ ] 程式碼中無硬編碼的秘密資訊 (Secrets)。
|
|||
|
|
- [ ] 所有環境變數均已說明並記錄。
|
|||
|
|
- [ ] 資料庫遷移腳本已就緒。
|
|||
|
|
|
|||
|
|
### 部署指令
|
|||
|
|
```bash
|
|||
|
|
# 建置並部署前端
|
|||
|
|
cd frontend && npm run build
|
|||
|
|
gcloud run deploy frontend --source .
|
|||
|
|
|
|||
|
|
# 建置並部署後端
|
|||
|
|
cd backend
|
|||
|
|
gcloud run deploy backend --source .
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 核心規範 (Critical Rules)
|
|||
|
|
|
|||
|
|
1. **嚴禁在程式碼、註釋或文件中使用 Emoji**。
|
|||
|
|
2. **不可變性 (Immutability)**:嚴禁直接修改物件或陣列。
|
|||
|
|
3. **測試驅動開發 (TDD)**:實作功能前請先撰寫測試。
|
|||
|
|
4. **測試覆蓋率**:最低要求為 80%。
|
|||
|
|
5. **小巧的檔案風格**:單一檔案建議介於 200-400 行,上限 800 行。
|
|||
|
|
6. **生產環境禁止使用 console.log**。
|
|||
|
|
7. **完善的例外處理**:務必使用 try/catch 並妥善對應。
|
|||
|
|
8. **嚴格的輸入驗證**:後端使用 Pydantic,前端使用 Zod。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 相關技能
|
|||
|
|
- `coding-standards.md` — 通用的編碼最佳實踐。
|
|||
|
|
- `backend-patterns.md` — API 與資料庫設計模式。
|
|||
|
|
- `frontend-patterns.md` — React 與 Next.js 開發模式。
|
|||
|
|
- `tdd-workflow/` — 測試驅動開發方法論。
|