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/` — 測試驅動開發方法論。
|