mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 21:53:28 +08:00
Compare commits
23 Commits
fix/stop-h
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
656cf4c94a | ||
|
|
0220202a61 | ||
|
|
5a2c9f5558 | ||
|
|
7ff2f0748e | ||
|
|
3f6a14acde | ||
|
|
d6c7f8fb0a | ||
|
|
7253d0ca98 | ||
|
|
118e57e14b | ||
|
|
a4d4b1d756 | ||
|
|
c90566f9be | ||
|
|
b9a01d3c32 | ||
|
|
fab80c99b7 | ||
|
|
8846210ca2 | ||
|
|
cff28efb34 | ||
|
|
b575f2e3eb | ||
|
|
0f065af311 | ||
|
|
ded5d826a4 | ||
|
|
ae272da28d | ||
|
|
c39aa22c5a | ||
|
|
7483d646e4 | ||
|
|
432a45274e | ||
|
|
866d9ebb53 | ||
|
|
1d0f64a14d |
@@ -23,7 +23,7 @@ Backend architecture patterns and best practices for scalable server-side applic
|
|||||||
### RESTful API Structure
|
### RESTful API Structure
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ Resource-based URLs
|
// PASS: Resource-based URLs
|
||||||
GET /api/markets # List resources
|
GET /api/markets # List resources
|
||||||
GET /api/markets/:id # Get single resource
|
GET /api/markets/:id # Get single resource
|
||||||
POST /api/markets # Create resource
|
POST /api/markets # Create resource
|
||||||
@@ -31,7 +31,7 @@ PUT /api/markets/:id # Replace resource
|
|||||||
PATCH /api/markets/:id # Update resource
|
PATCH /api/markets/:id # Update resource
|
||||||
DELETE /api/markets/:id # Delete resource
|
DELETE /api/markets/:id # Delete resource
|
||||||
|
|
||||||
// ✅ Query parameters for filtering, sorting, pagination
|
// PASS: Query parameters for filtering, sorting, pagination
|
||||||
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ export default withAuth(async (req, res) => {
|
|||||||
### Query Optimization
|
### Query Optimization
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Select only needed columns
|
// PASS: GOOD: Select only needed columns
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('id, name, status, volume')
|
.select('id, name, status, volume')
|
||||||
@@ -139,7 +139,7 @@ const { data } = await supabase
|
|||||||
.order('volume', { ascending: false })
|
.order('volume', { ascending: false })
|
||||||
.limit(10)
|
.limit(10)
|
||||||
|
|
||||||
// ❌ BAD: Select everything
|
// FAIL: BAD: Select everything
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('*')
|
.select('*')
|
||||||
@@ -148,13 +148,13 @@ const { data } = await supabase
|
|||||||
### N+1 Query Prevention
|
### N+1 Query Prevention
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: N+1 query problem
|
// FAIL: BAD: N+1 query problem
|
||||||
const markets = await getMarkets()
|
const markets = await getMarkets()
|
||||||
for (const market of markets) {
|
for (const market of markets) {
|
||||||
market.creator = await getUser(market.creator_id) // N queries
|
market.creator = await getUser(market.creator_id) // N queries
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ GOOD: Batch fetch
|
// PASS: GOOD: Batch fetch
|
||||||
const markets = await getMarkets()
|
const markets = await getMarkets()
|
||||||
const creatorIds = markets.map(m => m.creator_id)
|
const creatorIds = markets.map(m => m.creator_id)
|
||||||
const creators = await getUsers(creatorIds) // 1 query
|
const creators = await getUsers(creatorIds) // 1 query
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ Universal coding standards applicable across all projects.
|
|||||||
### Variable Naming
|
### Variable Naming
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Descriptive names
|
// PASS: GOOD: Descriptive names
|
||||||
const marketSearchQuery = 'election'
|
const marketSearchQuery = 'election'
|
||||||
const isUserAuthenticated = true
|
const isUserAuthenticated = true
|
||||||
const totalRevenue = 1000
|
const totalRevenue = 1000
|
||||||
|
|
||||||
// ❌ BAD: Unclear names
|
// FAIL: BAD: Unclear names
|
||||||
const q = 'election'
|
const q = 'election'
|
||||||
const flag = true
|
const flag = true
|
||||||
const x = 1000
|
const x = 1000
|
||||||
@@ -62,12 +62,12 @@ const x = 1000
|
|||||||
### Function Naming
|
### Function Naming
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Verb-noun pattern
|
// PASS: GOOD: Verb-noun pattern
|
||||||
async function fetchMarketData(marketId: string) { }
|
async function fetchMarketData(marketId: string) { }
|
||||||
function calculateSimilarity(a: number[], b: number[]) { }
|
function calculateSimilarity(a: number[], b: number[]) { }
|
||||||
function isValidEmail(email: string): boolean { }
|
function isValidEmail(email: string): boolean { }
|
||||||
|
|
||||||
// ❌ BAD: Unclear or noun-only
|
// FAIL: BAD: Unclear or noun-only
|
||||||
async function market(id: string) { }
|
async function market(id: string) { }
|
||||||
function similarity(a, b) { }
|
function similarity(a, b) { }
|
||||||
function email(e) { }
|
function email(e) { }
|
||||||
@@ -76,7 +76,7 @@ function email(e) { }
|
|||||||
### Immutability Pattern (CRITICAL)
|
### Immutability Pattern (CRITICAL)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ ALWAYS use spread operator
|
// PASS: ALWAYS use spread operator
|
||||||
const updatedUser = {
|
const updatedUser = {
|
||||||
...user,
|
...user,
|
||||||
name: 'New Name'
|
name: 'New Name'
|
||||||
@@ -84,7 +84,7 @@ const updatedUser = {
|
|||||||
|
|
||||||
const updatedArray = [...items, newItem]
|
const updatedArray = [...items, newItem]
|
||||||
|
|
||||||
// ❌ NEVER mutate directly
|
// FAIL: NEVER mutate directly
|
||||||
user.name = 'New Name' // BAD
|
user.name = 'New Name' // BAD
|
||||||
items.push(newItem) // BAD
|
items.push(newItem) // BAD
|
||||||
```
|
```
|
||||||
@@ -92,7 +92,7 @@ items.push(newItem) // BAD
|
|||||||
### Error Handling
|
### Error Handling
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Comprehensive error handling
|
// PASS: GOOD: Comprehensive error handling
|
||||||
async function fetchData(url: string) {
|
async function fetchData(url: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
@@ -108,7 +108,7 @@ async function fetchData(url: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ BAD: No error handling
|
// FAIL: BAD: No error handling
|
||||||
async function fetchData(url) {
|
async function fetchData(url) {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
return response.json()
|
return response.json()
|
||||||
@@ -118,14 +118,14 @@ async function fetchData(url) {
|
|||||||
### Async/Await Best Practices
|
### Async/Await Best Practices
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Parallel execution when possible
|
// PASS: GOOD: Parallel execution when possible
|
||||||
const [users, markets, stats] = await Promise.all([
|
const [users, markets, stats] = await Promise.all([
|
||||||
fetchUsers(),
|
fetchUsers(),
|
||||||
fetchMarkets(),
|
fetchMarkets(),
|
||||||
fetchStats()
|
fetchStats()
|
||||||
])
|
])
|
||||||
|
|
||||||
// ❌ BAD: Sequential when unnecessary
|
// FAIL: BAD: Sequential when unnecessary
|
||||||
const users = await fetchUsers()
|
const users = await fetchUsers()
|
||||||
const markets = await fetchMarkets()
|
const markets = await fetchMarkets()
|
||||||
const stats = await fetchStats()
|
const stats = await fetchStats()
|
||||||
@@ -134,7 +134,7 @@ const stats = await fetchStats()
|
|||||||
### Type Safety
|
### Type Safety
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Proper types
|
// PASS: GOOD: Proper types
|
||||||
interface Market {
|
interface Market {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@@ -146,7 +146,7 @@ function getMarket(id: string): Promise<Market> {
|
|||||||
// Implementation
|
// Implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ BAD: Using 'any'
|
// FAIL: BAD: Using 'any'
|
||||||
function getMarket(id: any): Promise<any> {
|
function getMarket(id: any): Promise<any> {
|
||||||
// Implementation
|
// Implementation
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ function getMarket(id: any): Promise<any> {
|
|||||||
### Component Structure
|
### Component Structure
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Functional component with types
|
// PASS: GOOD: Functional component with types
|
||||||
interface ButtonProps {
|
interface ButtonProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
@@ -182,7 +182,7 @@ export function Button({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ BAD: No types, unclear structure
|
// FAIL: BAD: No types, unclear structure
|
||||||
export function Button(props) {
|
export function Button(props) {
|
||||||
return <button onClick={props.onClick}>{props.children}</button>
|
return <button onClick={props.onClick}>{props.children}</button>
|
||||||
}
|
}
|
||||||
@@ -191,7 +191,7 @@ export function Button(props) {
|
|||||||
### Custom Hooks
|
### Custom Hooks
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Reusable custom hook
|
// PASS: GOOD: Reusable custom hook
|
||||||
export function useDebounce<T>(value: T, delay: number): T {
|
export function useDebounce<T>(value: T, delay: number): T {
|
||||||
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
||||||
|
|
||||||
@@ -213,25 +213,25 @@ const debouncedQuery = useDebounce(searchQuery, 500)
|
|||||||
### State Management
|
### State Management
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Proper state updates
|
// PASS: GOOD: Proper state updates
|
||||||
const [count, setCount] = useState(0)
|
const [count, setCount] = useState(0)
|
||||||
|
|
||||||
// Functional update for state based on previous state
|
// Functional update for state based on previous state
|
||||||
setCount(prev => prev + 1)
|
setCount(prev => prev + 1)
|
||||||
|
|
||||||
// ❌ BAD: Direct state reference
|
// FAIL: BAD: Direct state reference
|
||||||
setCount(count + 1) // Can be stale in async scenarios
|
setCount(count + 1) // Can be stale in async scenarios
|
||||||
```
|
```
|
||||||
|
|
||||||
### Conditional Rendering
|
### Conditional Rendering
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Clear conditional rendering
|
// PASS: GOOD: Clear conditional rendering
|
||||||
{isLoading && <Spinner />}
|
{isLoading && <Spinner />}
|
||||||
{error && <ErrorMessage error={error} />}
|
{error && <ErrorMessage error={error} />}
|
||||||
{data && <DataDisplay data={data} />}
|
{data && <DataDisplay data={data} />}
|
||||||
|
|
||||||
// ❌ BAD: Ternary hell
|
// FAIL: BAD: Ternary hell
|
||||||
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -254,7 +254,7 @@ GET /api/markets?status=active&limit=10&offset=0
|
|||||||
### Response Format
|
### Response Format
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Consistent response structure
|
// PASS: GOOD: Consistent response structure
|
||||||
interface ApiResponse<T> {
|
interface ApiResponse<T> {
|
||||||
success: boolean
|
success: boolean
|
||||||
data?: T
|
data?: T
|
||||||
@@ -285,7 +285,7 @@ return NextResponse.json({
|
|||||||
```typescript
|
```typescript
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
// ✅ GOOD: Schema validation
|
// PASS: GOOD: Schema validation
|
||||||
const CreateMarketSchema = z.object({
|
const CreateMarketSchema = z.object({
|
||||||
name: z.string().min(1).max(200),
|
name: z.string().min(1).max(200),
|
||||||
description: z.string().min(1).max(2000),
|
description: z.string().min(1).max(2000),
|
||||||
@@ -348,14 +348,14 @@ types/market.types.ts # camelCase with .types suffix
|
|||||||
### When to Comment
|
### When to Comment
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Explain WHY, not WHAT
|
// PASS: GOOD: Explain WHY, not WHAT
|
||||||
// Use exponential backoff to avoid overwhelming the API during outages
|
// Use exponential backoff to avoid overwhelming the API during outages
|
||||||
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
||||||
|
|
||||||
// Deliberately using mutation here for performance with large arrays
|
// Deliberately using mutation here for performance with large arrays
|
||||||
items.push(newItem)
|
items.push(newItem)
|
||||||
|
|
||||||
// ❌ BAD: Stating the obvious
|
// FAIL: BAD: Stating the obvious
|
||||||
// Increment counter by 1
|
// Increment counter by 1
|
||||||
count++
|
count++
|
||||||
|
|
||||||
@@ -395,12 +395,12 @@ export async function searchMarkets(
|
|||||||
```typescript
|
```typescript
|
||||||
import { useMemo, useCallback } from 'react'
|
import { useMemo, useCallback } from 'react'
|
||||||
|
|
||||||
// ✅ GOOD: Memoize expensive computations
|
// PASS: GOOD: Memoize expensive computations
|
||||||
const sortedMarkets = useMemo(() => {
|
const sortedMarkets = useMemo(() => {
|
||||||
return markets.sort((a, b) => b.volume - a.volume)
|
return markets.sort((a, b) => b.volume - a.volume)
|
||||||
}, [markets])
|
}, [markets])
|
||||||
|
|
||||||
// ✅ GOOD: Memoize callbacks
|
// PASS: GOOD: Memoize callbacks
|
||||||
const handleSearch = useCallback((query: string) => {
|
const handleSearch = useCallback((query: string) => {
|
||||||
setSearchQuery(query)
|
setSearchQuery(query)
|
||||||
}, [])
|
}, [])
|
||||||
@@ -411,7 +411,7 @@ const handleSearch = useCallback((query: string) => {
|
|||||||
```typescript
|
```typescript
|
||||||
import { lazy, Suspense } from 'react'
|
import { lazy, Suspense } from 'react'
|
||||||
|
|
||||||
// ✅ GOOD: Lazy load heavy components
|
// PASS: GOOD: Lazy load heavy components
|
||||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
@@ -426,13 +426,13 @@ export function Dashboard() {
|
|||||||
### Database Queries
|
### Database Queries
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Select only needed columns
|
// PASS: GOOD: Select only needed columns
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('id, name, status')
|
.select('id, name, status')
|
||||||
.limit(10)
|
.limit(10)
|
||||||
|
|
||||||
// ❌ BAD: Select everything
|
// FAIL: BAD: Select everything
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('*')
|
.select('*')
|
||||||
@@ -459,12 +459,12 @@ test('calculates similarity correctly', () => {
|
|||||||
### Test Naming
|
### Test Naming
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Descriptive test names
|
// PASS: GOOD: Descriptive test names
|
||||||
test('returns empty array when no markets match query', () => { })
|
test('returns empty array when no markets match query', () => { })
|
||||||
test('throws error when OpenAI API key is missing', () => { })
|
test('throws error when OpenAI API key is missing', () => { })
|
||||||
test('falls back to substring search when Redis unavailable', () => { })
|
test('falls back to substring search when Redis unavailable', () => { })
|
||||||
|
|
||||||
// ❌ BAD: Vague test names
|
// FAIL: BAD: Vague test names
|
||||||
test('works', () => { })
|
test('works', () => { })
|
||||||
test('test search', () => { })
|
test('test search', () => { })
|
||||||
```
|
```
|
||||||
@@ -475,12 +475,12 @@ Watch for these anti-patterns:
|
|||||||
|
|
||||||
### 1. Long Functions
|
### 1. Long Functions
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: Function > 50 lines
|
// FAIL: BAD: Function > 50 lines
|
||||||
function processMarketData() {
|
function processMarketData() {
|
||||||
// 100 lines of code
|
// 100 lines of code
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ GOOD: Split into smaller functions
|
// PASS: GOOD: Split into smaller functions
|
||||||
function processMarketData() {
|
function processMarketData() {
|
||||||
const validated = validateData()
|
const validated = validateData()
|
||||||
const transformed = transformData(validated)
|
const transformed = transformData(validated)
|
||||||
@@ -490,7 +490,7 @@ function processMarketData() {
|
|||||||
|
|
||||||
### 2. Deep Nesting
|
### 2. Deep Nesting
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: 5+ levels of nesting
|
// FAIL: BAD: 5+ levels of nesting
|
||||||
if (user) {
|
if (user) {
|
||||||
if (user.isAdmin) {
|
if (user.isAdmin) {
|
||||||
if (market) {
|
if (market) {
|
||||||
@@ -503,7 +503,7 @@ if (user) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ GOOD: Early returns
|
// PASS: GOOD: Early returns
|
||||||
if (!user) return
|
if (!user) return
|
||||||
if (!user.isAdmin) return
|
if (!user.isAdmin) return
|
||||||
if (!market) return
|
if (!market) return
|
||||||
@@ -515,11 +515,11 @@ if (!hasPermission) return
|
|||||||
|
|
||||||
### 3. Magic Numbers
|
### 3. Magic Numbers
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: Unexplained numbers
|
// FAIL: BAD: Unexplained numbers
|
||||||
if (retryCount > 3) { }
|
if (retryCount > 3) { }
|
||||||
setTimeout(callback, 500)
|
setTimeout(callback, 500)
|
||||||
|
|
||||||
// ✅ GOOD: Named constants
|
// PASS: GOOD: Named constants
|
||||||
const MAX_RETRIES = 3
|
const MAX_RETRIES = 3
|
||||||
const DEBOUNCE_DELAY_MS = 500
|
const DEBOUNCE_DELAY_MS = 500
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Modern frontend patterns for React, Next.js, and performant user interfaces.
|
|||||||
### Composition Over Inheritance
|
### Composition Over Inheritance
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Component composition
|
// PASS: GOOD: Component composition
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
variant?: 'default' | 'outlined'
|
variant?: 'default' | 'outlined'
|
||||||
@@ -294,17 +294,17 @@ export function useMarkets() {
|
|||||||
### Memoization
|
### Memoization
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ useMemo for expensive computations
|
// PASS: useMemo for expensive computations
|
||||||
const sortedMarkets = useMemo(() => {
|
const sortedMarkets = useMemo(() => {
|
||||||
return markets.sort((a, b) => b.volume - a.volume)
|
return markets.sort((a, b) => b.volume - a.volume)
|
||||||
}, [markets])
|
}, [markets])
|
||||||
|
|
||||||
// ✅ useCallback for functions passed to children
|
// PASS: useCallback for functions passed to children
|
||||||
const handleSearch = useCallback((query: string) => {
|
const handleSearch = useCallback((query: string) => {
|
||||||
setSearchQuery(query)
|
setSearchQuery(query)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// ✅ React.memo for pure components
|
// PASS: React.memo for pure components
|
||||||
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||||
return (
|
return (
|
||||||
<div className="market-card">
|
<div className="market-card">
|
||||||
@@ -320,7 +320,7 @@ export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
|||||||
```typescript
|
```typescript
|
||||||
import { lazy, Suspense } from 'react'
|
import { lazy, Suspense } from 'react'
|
||||||
|
|
||||||
// ✅ Lazy load heavy components
|
// PASS: Lazy load heavy components
|
||||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||||
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
||||||
|
|
||||||
@@ -515,7 +515,7 @@ export class ErrorBoundary extends React.Component<
|
|||||||
```typescript
|
```typescript
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
|
|
||||||
// ✅ List animations
|
// PASS: List animations
|
||||||
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@@ -534,7 +534,7 @@ export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Modal animations
|
// PASS: Modal animations
|
||||||
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ This skill ensures all code follows security best practices and identifies poten
|
|||||||
|
|
||||||
### 1. Secrets Management
|
### 1. Secrets Management
|
||||||
|
|
||||||
#### ❌ NEVER Do This
|
#### FAIL: NEVER Do This
|
||||||
```typescript
|
```typescript
|
||||||
const apiKey = "sk-proj-xxxxx" // Hardcoded secret
|
const apiKey = "sk-proj-xxxxx" // Hardcoded secret
|
||||||
const dbPassword = "password123" // In source code
|
const dbPassword = "password123" // In source code
|
||||||
```
|
```
|
||||||
|
|
||||||
#### ✅ ALWAYS Do This
|
#### PASS: ALWAYS Do This
|
||||||
```typescript
|
```typescript
|
||||||
const apiKey = process.env.OPENAI_API_KEY
|
const apiKey = process.env.OPENAI_API_KEY
|
||||||
const dbUrl = process.env.DATABASE_URL
|
const dbUrl = process.env.DATABASE_URL
|
||||||
@@ -108,14 +108,14 @@ function validateFileUpload(file: File) {
|
|||||||
|
|
||||||
### 3. SQL Injection Prevention
|
### 3. SQL Injection Prevention
|
||||||
|
|
||||||
#### ❌ NEVER Concatenate SQL
|
#### FAIL: NEVER Concatenate SQL
|
||||||
```typescript
|
```typescript
|
||||||
// DANGEROUS - SQL Injection vulnerability
|
// DANGEROUS - SQL Injection vulnerability
|
||||||
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
|
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
|
||||||
await db.query(query)
|
await db.query(query)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### ✅ ALWAYS Use Parameterized Queries
|
#### PASS: ALWAYS Use Parameterized Queries
|
||||||
```typescript
|
```typescript
|
||||||
// Safe - parameterized query
|
// Safe - parameterized query
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
@@ -140,10 +140,10 @@ await db.query(
|
|||||||
|
|
||||||
#### JWT Token Handling
|
#### JWT Token Handling
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ WRONG: localStorage (vulnerable to XSS)
|
// FAIL: WRONG: localStorage (vulnerable to XSS)
|
||||||
localStorage.setItem('token', token)
|
localStorage.setItem('token', token)
|
||||||
|
|
||||||
// ✅ CORRECT: httpOnly cookies
|
// PASS: CORRECT: httpOnly cookies
|
||||||
res.setHeader('Set-Cookie',
|
res.setHeader('Set-Cookie',
|
||||||
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
||||||
```
|
```
|
||||||
@@ -300,18 +300,18 @@ app.use('/api/search', searchLimiter)
|
|||||||
|
|
||||||
#### Logging
|
#### Logging
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ WRONG: Logging sensitive data
|
// FAIL: WRONG: Logging sensitive data
|
||||||
console.log('User login:', { email, password })
|
console.log('User login:', { email, password })
|
||||||
console.log('Payment:', { cardNumber, cvv })
|
console.log('Payment:', { cardNumber, cvv })
|
||||||
|
|
||||||
// ✅ CORRECT: Redact sensitive data
|
// PASS: CORRECT: Redact sensitive data
|
||||||
console.log('User login:', { email, userId })
|
console.log('User login:', { email, userId })
|
||||||
console.log('Payment:', { last4: card.last4, userId })
|
console.log('Payment:', { last4: card.last4, userId })
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Error Messages
|
#### Error Messages
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ WRONG: Exposing internal details
|
// FAIL: WRONG: Exposing internal details
|
||||||
catch (error) {
|
catch (error) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: error.message, stack: error.stack },
|
{ error: error.message, stack: error.stack },
|
||||||
@@ -319,7 +319,7 @@ catch (error) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ CORRECT: Generic error messages
|
// PASS: CORRECT: Generic error messages
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('Internal error:', error)
|
console.error('Internal error:', error)
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -314,39 +314,39 @@ npm run test:coverage
|
|||||||
|
|
||||||
## Common Testing Mistakes to Avoid
|
## Common Testing Mistakes to Avoid
|
||||||
|
|
||||||
### ❌ WRONG: Testing Implementation Details
|
### FAIL: WRONG: Testing Implementation Details
|
||||||
```typescript
|
```typescript
|
||||||
// Don't test internal state
|
// Don't test internal state
|
||||||
expect(component.state.count).toBe(5)
|
expect(component.state.count).toBe(5)
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ CORRECT: Test User-Visible Behavior
|
### PASS: CORRECT: Test User-Visible Behavior
|
||||||
```typescript
|
```typescript
|
||||||
// Test what users see
|
// Test what users see
|
||||||
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
||||||
```
|
```
|
||||||
|
|
||||||
### ❌ WRONG: Brittle Selectors
|
### FAIL: WRONG: Brittle Selectors
|
||||||
```typescript
|
```typescript
|
||||||
// Breaks easily
|
// Breaks easily
|
||||||
await page.click('.css-class-xyz')
|
await page.click('.css-class-xyz')
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ CORRECT: Semantic Selectors
|
### PASS: CORRECT: Semantic Selectors
|
||||||
```typescript
|
```typescript
|
||||||
// Resilient to changes
|
// Resilient to changes
|
||||||
await page.click('button:has-text("Submit")')
|
await page.click('button:has-text("Submit")')
|
||||||
await page.click('[data-testid="submit-button"]')
|
await page.click('[data-testid="submit-button"]')
|
||||||
```
|
```
|
||||||
|
|
||||||
### ❌ WRONG: No Test Isolation
|
### FAIL: WRONG: No Test Isolation
|
||||||
```typescript
|
```typescript
|
||||||
// Tests depend on each other
|
// Tests depend on each other
|
||||||
test('creates user', () => { /* ... */ })
|
test('creates user', () => { /* ... */ })
|
||||||
test('updates same user', () => { /* depends on previous test */ })
|
test('updates same user', () => { /* depends on previous test */ })
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ CORRECT: Independent Tests
|
### PASS: CORRECT: Independent Tests
|
||||||
```typescript
|
```typescript
|
||||||
// Each test sets up its own data
|
// Each test sets up its own data
|
||||||
test('creates user', () => {
|
test('creates user', () => {
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ Assume the validator is hostile and literal.
|
|||||||
|
|
||||||
## The `hooks` Field: DO NOT ADD
|
## The `hooks` Field: DO NOT ADD
|
||||||
|
|
||||||
> ⚠️ **CRITICAL:** Do NOT add a `"hooks"` field to `plugin.json`. This is enforced by a regression test.
|
> WARNING: **CRITICAL:** Do NOT add a `"hooks"` field to `plugin.json`. This is enforced by a regression test.
|
||||||
|
|
||||||
### Why This Matters
|
### Why This Matters
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -198,6 +198,10 @@ jobs:
|
|||||||
run: node scripts/ci/catalog.js --text
|
run: node scripts/ci/catalog.js --text
|
||||||
continue-on-error: false
|
continue-on-error: false
|
||||||
|
|
||||||
|
- name: Check unicode safety
|
||||||
|
run: node scripts/ci/check-unicode-safety.js
|
||||||
|
continue-on-error: false
|
||||||
|
|
||||||
security:
|
security:
|
||||||
name: Security Scan
|
name: Security Scan
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
3
.github/workflows/reusable-validate.yml
vendored
3
.github/workflows/reusable-validate.yml
vendored
@@ -44,3 +44,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Validate rules
|
- name: Validate rules
|
||||||
run: node scripts/ci/validate-rules.js
|
run: node scripts/ci/validate-rules.js
|
||||||
|
|
||||||
|
- name: Check unicode safety
|
||||||
|
run: node scripts/ci/check-unicode-safety.js
|
||||||
|
|||||||
@@ -597,7 +597,7 @@ For more detailed information, see the `docs/` directory:
|
|||||||
|
|
||||||
## Contributers
|
## Contributers
|
||||||
|
|
||||||
- Himanshu Sharma [@ihimanss](https://github.com/ihimanss)
|
- Himanshu Sharma [@ihimanss](https://github.com/ihimanss)
|
||||||
- Sungmin Hong [@aws-hsungmin](https://github.com/aws-hsungmin)
|
- Sungmin Hong [@aws-hsungmin](https://github.com/aws-hsungmin)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ detect_pm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PM=$(detect_pm)
|
PM=$(detect_pm)
|
||||||
echo "📦 Package manager: $PM"
|
echo "Package manager: $PM"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ── Helper: run a check ─────────────────────────────────────
|
# ── Helper: run a check ─────────────────────────────────────
|
||||||
|
|||||||
@@ -62,10 +62,10 @@ Choose model tier based on task complexity:
|
|||||||
|
|
||||||
- **Haiku**: Classification, boilerplate transforms, narrow edits
|
- **Haiku**: Classification, boilerplate transforms, narrow edits
|
||||||
- Example: Rename variable, add type annotation, format code
|
- Example: Rename variable, add type annotation, format code
|
||||||
|
|
||||||
- **Sonnet**: Implementation and refactors
|
- **Sonnet**: Implementation and refactors
|
||||||
- Example: Implement feature, refactor module, write tests
|
- Example: Implement feature, refactor module, write tests
|
||||||
|
|
||||||
- **Opus**: Architecture, root-cause analysis, multi-file invariants
|
- **Opus**: Architecture, root-cause analysis, multi-file invariants
|
||||||
- Example: Design system, debug complex issue, review architecture
|
- Example: Design system, debug complex issue, review architecture
|
||||||
|
|
||||||
@@ -75,10 +75,10 @@ Choose model tier based on task complexity:
|
|||||||
|
|
||||||
- **Continue session** for closely-coupled units
|
- **Continue session** for closely-coupled units
|
||||||
- Example: Implementing related functions in same module
|
- Example: Implementing related functions in same module
|
||||||
|
|
||||||
- **Start fresh session** after major phase transitions
|
- **Start fresh session** after major phase transitions
|
||||||
- Example: Moving from implementation to testing
|
- Example: Moving from implementation to testing
|
||||||
|
|
||||||
- **Compact after milestone completion**, not during active debugging
|
- **Compact after milestone completion**, not during active debugging
|
||||||
- Example: After feature complete, before starting next feature
|
- Example: After feature complete, before starting next feature
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ Backend architecture patterns and best practices for scalable server-side applic
|
|||||||
### RESTful API Structure
|
### RESTful API Structure
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ Resource-based URLs
|
// PASS: Resource-based URLs
|
||||||
GET /api/markets # List resources
|
GET /api/markets # List resources
|
||||||
GET /api/markets/:id # Get single resource
|
GET /api/markets/:id # Get single resource
|
||||||
POST /api/markets # Create resource
|
POST /api/markets # Create resource
|
||||||
@@ -33,7 +33,7 @@ PUT /api/markets/:id # Replace resource
|
|||||||
PATCH /api/markets/:id # Update resource
|
PATCH /api/markets/:id # Update resource
|
||||||
DELETE /api/markets/:id # Delete resource
|
DELETE /api/markets/:id # Delete resource
|
||||||
|
|
||||||
// ✅ Query parameters for filtering, sorting, pagination
|
// PASS: Query parameters for filtering, sorting, pagination
|
||||||
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ export default withAuth(async (req, res) => {
|
|||||||
### Query Optimization
|
### Query Optimization
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Select only needed columns
|
// PASS: GOOD: Select only needed columns
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('id, name, status, volume')
|
.select('id, name, status, volume')
|
||||||
@@ -141,7 +141,7 @@ const { data } = await supabase
|
|||||||
.order('volume', { ascending: false })
|
.order('volume', { ascending: false })
|
||||||
.limit(10)
|
.limit(10)
|
||||||
|
|
||||||
// ❌ BAD: Select everything
|
// FAIL: BAD: Select everything
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('*')
|
.select('*')
|
||||||
@@ -150,13 +150,13 @@ const { data } = await supabase
|
|||||||
### N+1 Query Prevention
|
### N+1 Query Prevention
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: N+1 query problem
|
// FAIL: BAD: N+1 query problem
|
||||||
const markets = await getMarkets()
|
const markets = await getMarkets()
|
||||||
for (const market of markets) {
|
for (const market of markets) {
|
||||||
market.creator = await getUser(market.creator_id) // N queries
|
market.creator = await getUser(market.creator_id) // N queries
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ GOOD: Batch fetch
|
// PASS: GOOD: Batch fetch
|
||||||
const markets = await getMarkets()
|
const markets = await getMarkets()
|
||||||
const creatorIds = markets.map(m => m.creator_id)
|
const creatorIds = markets.map(m => m.creator_id)
|
||||||
const creators = await getUsers(creatorIds) // 1 query
|
const creators = await getUsers(creatorIds) // 1 query
|
||||||
|
|||||||
@@ -50,12 +50,12 @@ Universal coding standards applicable across all projects.
|
|||||||
### Variable Naming
|
### Variable Naming
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Descriptive names
|
// PASS: GOOD: Descriptive names
|
||||||
const marketSearchQuery = 'election'
|
const marketSearchQuery = 'election'
|
||||||
const isUserAuthenticated = true
|
const isUserAuthenticated = true
|
||||||
const totalRevenue = 1000
|
const totalRevenue = 1000
|
||||||
|
|
||||||
// ❌ BAD: Unclear names
|
// FAIL: BAD: Unclear names
|
||||||
const q = 'election'
|
const q = 'election'
|
||||||
const flag = true
|
const flag = true
|
||||||
const x = 1000
|
const x = 1000
|
||||||
@@ -64,12 +64,12 @@ const x = 1000
|
|||||||
### Function Naming
|
### Function Naming
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Verb-noun pattern
|
// PASS: GOOD: Verb-noun pattern
|
||||||
async function fetchMarketData(marketId: string) { }
|
async function fetchMarketData(marketId: string) { }
|
||||||
function calculateSimilarity(a: number[], b: number[]) { }
|
function calculateSimilarity(a: number[], b: number[]) { }
|
||||||
function isValidEmail(email: string): boolean { }
|
function isValidEmail(email: string): boolean { }
|
||||||
|
|
||||||
// ❌ BAD: Unclear or noun-only
|
// FAIL: BAD: Unclear or noun-only
|
||||||
async function market(id: string) { }
|
async function market(id: string) { }
|
||||||
function similarity(a, b) { }
|
function similarity(a, b) { }
|
||||||
function email(e) { }
|
function email(e) { }
|
||||||
@@ -78,7 +78,7 @@ function email(e) { }
|
|||||||
### Immutability Pattern (CRITICAL)
|
### Immutability Pattern (CRITICAL)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ ALWAYS use spread operator
|
// PASS: ALWAYS use spread operator
|
||||||
const updatedUser = {
|
const updatedUser = {
|
||||||
...user,
|
...user,
|
||||||
name: 'New Name'
|
name: 'New Name'
|
||||||
@@ -86,7 +86,7 @@ const updatedUser = {
|
|||||||
|
|
||||||
const updatedArray = [...items, newItem]
|
const updatedArray = [...items, newItem]
|
||||||
|
|
||||||
// ❌ NEVER mutate directly
|
// FAIL: NEVER mutate directly
|
||||||
user.name = 'New Name' // BAD
|
user.name = 'New Name' // BAD
|
||||||
items.push(newItem) // BAD
|
items.push(newItem) // BAD
|
||||||
```
|
```
|
||||||
@@ -94,7 +94,7 @@ items.push(newItem) // BAD
|
|||||||
### Error Handling
|
### Error Handling
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Comprehensive error handling
|
// PASS: GOOD: Comprehensive error handling
|
||||||
async function fetchData(url: string) {
|
async function fetchData(url: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
@@ -110,7 +110,7 @@ async function fetchData(url: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ BAD: No error handling
|
// FAIL: BAD: No error handling
|
||||||
async function fetchData(url) {
|
async function fetchData(url) {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
return response.json()
|
return response.json()
|
||||||
@@ -120,14 +120,14 @@ async function fetchData(url) {
|
|||||||
### Async/Await Best Practices
|
### Async/Await Best Practices
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Parallel execution when possible
|
// PASS: GOOD: Parallel execution when possible
|
||||||
const [users, markets, stats] = await Promise.all([
|
const [users, markets, stats] = await Promise.all([
|
||||||
fetchUsers(),
|
fetchUsers(),
|
||||||
fetchMarkets(),
|
fetchMarkets(),
|
||||||
fetchStats()
|
fetchStats()
|
||||||
])
|
])
|
||||||
|
|
||||||
// ❌ BAD: Sequential when unnecessary
|
// FAIL: BAD: Sequential when unnecessary
|
||||||
const users = await fetchUsers()
|
const users = await fetchUsers()
|
||||||
const markets = await fetchMarkets()
|
const markets = await fetchMarkets()
|
||||||
const stats = await fetchStats()
|
const stats = await fetchStats()
|
||||||
@@ -136,7 +136,7 @@ const stats = await fetchStats()
|
|||||||
### Type Safety
|
### Type Safety
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Proper types
|
// PASS: GOOD: Proper types
|
||||||
interface Market {
|
interface Market {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@@ -148,7 +148,7 @@ function getMarket(id: string): Promise<Market> {
|
|||||||
// Implementation
|
// Implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ BAD: Using 'any'
|
// FAIL: BAD: Using 'any'
|
||||||
function getMarket(id: any): Promise<any> {
|
function getMarket(id: any): Promise<any> {
|
||||||
// Implementation
|
// Implementation
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,7 @@ function getMarket(id: any): Promise<any> {
|
|||||||
### Component Structure
|
### Component Structure
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Functional component with types
|
// PASS: GOOD: Functional component with types
|
||||||
interface ButtonProps {
|
interface ButtonProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
@@ -184,7 +184,7 @@ export function Button({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ BAD: No types, unclear structure
|
// FAIL: BAD: No types, unclear structure
|
||||||
export function Button(props) {
|
export function Button(props) {
|
||||||
return <button onClick={props.onClick}>{props.children}</button>
|
return <button onClick={props.onClick}>{props.children}</button>
|
||||||
}
|
}
|
||||||
@@ -193,7 +193,7 @@ export function Button(props) {
|
|||||||
### Custom Hooks
|
### Custom Hooks
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Reusable custom hook
|
// PASS: GOOD: Reusable custom hook
|
||||||
export function useDebounce<T>(value: T, delay: number): T {
|
export function useDebounce<T>(value: T, delay: number): T {
|
||||||
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
||||||
|
|
||||||
@@ -215,25 +215,25 @@ const debouncedQuery = useDebounce(searchQuery, 500)
|
|||||||
### State Management
|
### State Management
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Proper state updates
|
// PASS: GOOD: Proper state updates
|
||||||
const [count, setCount] = useState(0)
|
const [count, setCount] = useState(0)
|
||||||
|
|
||||||
// Functional update for state based on previous state
|
// Functional update for state based on previous state
|
||||||
setCount(prev => prev + 1)
|
setCount(prev => prev + 1)
|
||||||
|
|
||||||
// ❌ BAD: Direct state reference
|
// FAIL: BAD: Direct state reference
|
||||||
setCount(count + 1) // Can be stale in async scenarios
|
setCount(count + 1) // Can be stale in async scenarios
|
||||||
```
|
```
|
||||||
|
|
||||||
### Conditional Rendering
|
### Conditional Rendering
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Clear conditional rendering
|
// PASS: GOOD: Clear conditional rendering
|
||||||
{isLoading && <Spinner />}
|
{isLoading && <Spinner />}
|
||||||
{error && <ErrorMessage error={error} />}
|
{error && <ErrorMessage error={error} />}
|
||||||
{data && <DataDisplay data={data} />}
|
{data && <DataDisplay data={data} />}
|
||||||
|
|
||||||
// ❌ BAD: Ternary hell
|
// FAIL: BAD: Ternary hell
|
||||||
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -256,7 +256,7 @@ GET /api/markets?status=active&limit=10&offset=0
|
|||||||
### Response Format
|
### Response Format
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Consistent response structure
|
// PASS: GOOD: Consistent response structure
|
||||||
interface ApiResponse<T> {
|
interface ApiResponse<T> {
|
||||||
success: boolean
|
success: boolean
|
||||||
data?: T
|
data?: T
|
||||||
@@ -287,7 +287,7 @@ return NextResponse.json({
|
|||||||
```typescript
|
```typescript
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
// ✅ GOOD: Schema validation
|
// PASS: GOOD: Schema validation
|
||||||
const CreateMarketSchema = z.object({
|
const CreateMarketSchema = z.object({
|
||||||
name: z.string().min(1).max(200),
|
name: z.string().min(1).max(200),
|
||||||
description: z.string().min(1).max(2000),
|
description: z.string().min(1).max(2000),
|
||||||
@@ -350,14 +350,14 @@ types/market.types.ts # camelCase with .types suffix
|
|||||||
### When to Comment
|
### When to Comment
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Explain WHY, not WHAT
|
// PASS: GOOD: Explain WHY, not WHAT
|
||||||
// Use exponential backoff to avoid overwhelming the API during outages
|
// Use exponential backoff to avoid overwhelming the API during outages
|
||||||
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
||||||
|
|
||||||
// Deliberately using mutation here for performance with large arrays
|
// Deliberately using mutation here for performance with large arrays
|
||||||
items.push(newItem)
|
items.push(newItem)
|
||||||
|
|
||||||
// ❌ BAD: Stating the obvious
|
// FAIL: BAD: Stating the obvious
|
||||||
// Increment counter by 1
|
// Increment counter by 1
|
||||||
count++
|
count++
|
||||||
|
|
||||||
@@ -397,12 +397,12 @@ export async function searchMarkets(
|
|||||||
```typescript
|
```typescript
|
||||||
import { useMemo, useCallback } from 'react'
|
import { useMemo, useCallback } from 'react'
|
||||||
|
|
||||||
// ✅ GOOD: Memoize expensive computations
|
// PASS: GOOD: Memoize expensive computations
|
||||||
const sortedMarkets = useMemo(() => {
|
const sortedMarkets = useMemo(() => {
|
||||||
return markets.sort((a, b) => b.volume - a.volume)
|
return markets.sort((a, b) => b.volume - a.volume)
|
||||||
}, [markets])
|
}, [markets])
|
||||||
|
|
||||||
// ✅ GOOD: Memoize callbacks
|
// PASS: GOOD: Memoize callbacks
|
||||||
const handleSearch = useCallback((query: string) => {
|
const handleSearch = useCallback((query: string) => {
|
||||||
setSearchQuery(query)
|
setSearchQuery(query)
|
||||||
}, [])
|
}, [])
|
||||||
@@ -413,7 +413,7 @@ const handleSearch = useCallback((query: string) => {
|
|||||||
```typescript
|
```typescript
|
||||||
import { lazy, Suspense } from 'react'
|
import { lazy, Suspense } from 'react'
|
||||||
|
|
||||||
// ✅ GOOD: Lazy load heavy components
|
// PASS: GOOD: Lazy load heavy components
|
||||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
@@ -428,13 +428,13 @@ export function Dashboard() {
|
|||||||
### Database Queries
|
### Database Queries
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Select only needed columns
|
// PASS: GOOD: Select only needed columns
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('id, name, status')
|
.select('id, name, status')
|
||||||
.limit(10)
|
.limit(10)
|
||||||
|
|
||||||
// ❌ BAD: Select everything
|
// FAIL: BAD: Select everything
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('*')
|
.select('*')
|
||||||
@@ -461,12 +461,12 @@ test('calculates similarity correctly', () => {
|
|||||||
### Test Naming
|
### Test Naming
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Descriptive test names
|
// PASS: GOOD: Descriptive test names
|
||||||
test('returns empty array when no markets match query', () => { })
|
test('returns empty array when no markets match query', () => { })
|
||||||
test('throws error when OpenAI API key is missing', () => { })
|
test('throws error when OpenAI API key is missing', () => { })
|
||||||
test('falls back to substring search when Redis unavailable', () => { })
|
test('falls back to substring search when Redis unavailable', () => { })
|
||||||
|
|
||||||
// ❌ BAD: Vague test names
|
// FAIL: BAD: Vague test names
|
||||||
test('works', () => { })
|
test('works', () => { })
|
||||||
test('test search', () => { })
|
test('test search', () => { })
|
||||||
```
|
```
|
||||||
@@ -477,12 +477,12 @@ Watch for these anti-patterns:
|
|||||||
|
|
||||||
### 1. Long Functions
|
### 1. Long Functions
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: Function > 50 lines
|
// FAIL: BAD: Function > 50 lines
|
||||||
function processMarketData() {
|
function processMarketData() {
|
||||||
// 100 lines of code
|
// 100 lines of code
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ GOOD: Split into smaller functions
|
// PASS: GOOD: Split into smaller functions
|
||||||
function processMarketData() {
|
function processMarketData() {
|
||||||
const validated = validateData()
|
const validated = validateData()
|
||||||
const transformed = transformData(validated)
|
const transformed = transformData(validated)
|
||||||
@@ -492,7 +492,7 @@ function processMarketData() {
|
|||||||
|
|
||||||
### 2. Deep Nesting
|
### 2. Deep Nesting
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: 5+ levels of nesting
|
// FAIL: BAD: 5+ levels of nesting
|
||||||
if (user) {
|
if (user) {
|
||||||
if (user.isAdmin) {
|
if (user.isAdmin) {
|
||||||
if (market) {
|
if (market) {
|
||||||
@@ -505,7 +505,7 @@ if (user) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ GOOD: Early returns
|
// PASS: GOOD: Early returns
|
||||||
if (!user) return
|
if (!user) return
|
||||||
if (!user.isAdmin) return
|
if (!user.isAdmin) return
|
||||||
if (!market) return
|
if (!market) return
|
||||||
@@ -517,11 +517,11 @@ if (!hasPermission) return
|
|||||||
|
|
||||||
### 3. Magic Numbers
|
### 3. Magic Numbers
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: Unexplained numbers
|
// FAIL: BAD: Unexplained numbers
|
||||||
if (retryCount > 3) { }
|
if (retryCount > 3) { }
|
||||||
setTimeout(callback, 500)
|
setTimeout(callback, 500)
|
||||||
|
|
||||||
// ✅ GOOD: Named constants
|
// PASS: GOOD: Named constants
|
||||||
const MAX_RETRIES = 3
|
const MAX_RETRIES = 3
|
||||||
const DEBOUNCE_DELAY_MS = 500
|
const DEBOUNCE_DELAY_MS = 500
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ Modern frontend patterns for React, Next.js, and performant user interfaces.
|
|||||||
### Composition Over Inheritance
|
### Composition Over Inheritance
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Component composition
|
// PASS: GOOD: Component composition
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
variant?: 'default' | 'outlined'
|
variant?: 'default' | 'outlined'
|
||||||
@@ -296,17 +296,17 @@ export function useMarkets() {
|
|||||||
### Memoization
|
### Memoization
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ useMemo for expensive computations
|
// PASS: useMemo for expensive computations
|
||||||
const sortedMarkets = useMemo(() => {
|
const sortedMarkets = useMemo(() => {
|
||||||
return markets.sort((a, b) => b.volume - a.volume)
|
return markets.sort((a, b) => b.volume - a.volume)
|
||||||
}, [markets])
|
}, [markets])
|
||||||
|
|
||||||
// ✅ useCallback for functions passed to children
|
// PASS: useCallback for functions passed to children
|
||||||
const handleSearch = useCallback((query: string) => {
|
const handleSearch = useCallback((query: string) => {
|
||||||
setSearchQuery(query)
|
setSearchQuery(query)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// ✅ React.memo for pure components
|
// PASS: React.memo for pure components
|
||||||
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||||
return (
|
return (
|
||||||
<div className="market-card">
|
<div className="market-card">
|
||||||
@@ -322,7 +322,7 @@ export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
|||||||
```typescript
|
```typescript
|
||||||
import { lazy, Suspense } from 'react'
|
import { lazy, Suspense } from 'react'
|
||||||
|
|
||||||
// ✅ Lazy load heavy components
|
// PASS: Lazy load heavy components
|
||||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||||
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
||||||
|
|
||||||
@@ -517,7 +517,7 @@ export class ErrorBoundary extends React.Component<
|
|||||||
```typescript
|
```typescript
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
|
|
||||||
// ✅ List animations
|
// PASS: List animations
|
||||||
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@@ -536,7 +536,7 @@ export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Modal animations
|
// PASS: Modal animations
|
||||||
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ func TestValidate(t *testing.T) {
|
|||||||
{"valid", "test@example.com", false},
|
{"valid", "test@example.com", false},
|
||||||
{"invalid", "not-an-email", true},
|
{"invalid", "not-an-email", true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
err := Validate(tt.input)
|
err := Validate(tt.input)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func TestValidateEmail(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
err := ValidateEmail(tt.email)
|
err := ValidateEmail(tt.email)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("ValidateEmail(%q) error = %v, wantErr %v",
|
t.Errorf("ValidateEmail(%q) error = %v, wantErr %v",
|
||||||
tt.email, err, tt.wantErr)
|
tt.email, err, tt.wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -95,19 +95,19 @@ Use `t.Cleanup()` for resource cleanup:
|
|||||||
```go
|
```go
|
||||||
func testDB(t *testing.T) *sql.DB {
|
func testDB(t *testing.T) *sql.DB {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3", ":memory:")
|
db, err := sql.Open("sqlite3", ":memory:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to open test db: %v", err)
|
t.Fatalf("failed to open test db: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup runs after test completes
|
// Cleanup runs after test completes
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
if err := db.Close(); err != nil {
|
if err := db.Close(); err != nil {
|
||||||
t.Errorf("failed to close db: %v", err)
|
t.Errorf("failed to close db: %v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ go test -cover ./... | grep -E 'coverage: [0-7][0-9]\.[0-9]%' && exit 1
|
|||||||
```go
|
```go
|
||||||
func BenchmarkValidateEmail(b *testing.B) {
|
func BenchmarkValidateEmail(b *testing.B) {
|
||||||
email := "user@example.com"
|
email := "user@example.com"
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
ValidateEmail(email)
|
ValidateEmail(email)
|
||||||
@@ -212,7 +212,7 @@ func TestUserService(t *testing.T) {
|
|||||||
"1": {ID: "1", Name: "Alice"},
|
"1": {ID: "1", Name: "Alice"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
service := NewUserService(mock)
|
service := NewUserService(mock)
|
||||||
// ... test logic
|
// ... test logic
|
||||||
}
|
}
|
||||||
@@ -245,16 +245,16 @@ func TestWithPostgres(t *testing.T) {
|
|||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("skipping integration test")
|
t.Skip("skipping integration test")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup test container
|
// Setup test container
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
container, err := testcontainers.GenericContainer(ctx, ...)
|
container, err := testcontainers.GenericContainer(ctx, ...)
|
||||||
assertNoError(t, err)
|
assertNoError(t, err)
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
container.Terminate(ctx)
|
container.Terminate(ctx)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ... test logic
|
// ... test logic
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -290,10 +290,10 @@ package user
|
|||||||
func TestUserHandler(t *testing.T) {
|
func TestUserHandler(t *testing.T) {
|
||||||
req := httptest.NewRequest("GET", "/users/1", nil)
|
req := httptest.NewRequest("GET", "/users/1", nil)
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
handler := NewUserHandler(mockRepo)
|
handler := NewUserHandler(mockRepo)
|
||||||
handler.ServeHTTP(rec, req)
|
handler.ServeHTTP(rec, req)
|
||||||
|
|
||||||
assertEqual(t, rec.Code, http.StatusOK)
|
assertEqual(t, rec.Code, http.StatusOK)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -304,7 +304,7 @@ func TestUserHandler(t *testing.T) {
|
|||||||
func TestWithTimeout(t *testing.T) {
|
func TestWithTimeout(t *testing.T) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err := SlowOperation(ctx)
|
err := SlowOperation(ctx)
|
||||||
if !errors.Is(err, context.DeadlineExceeded) {
|
if !errors.Is(err, context.DeadlineExceeded) {
|
||||||
t.Errorf("expected timeout error, got %v", err)
|
t.Errorf("expected timeout error, got %v", err)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class UserRepository:
|
|||||||
def find_by_id(self, id: str) -> dict | None:
|
def find_by_id(self, id: str) -> dict | None:
|
||||||
# implementation
|
# implementation
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def save(self, entity: dict) -> dict:
|
def save(self, entity: dict) -> dict:
|
||||||
# implementation
|
# implementation
|
||||||
pass
|
pass
|
||||||
@@ -104,11 +104,11 @@ class FileProcessor:
|
|||||||
def __init__(self, filename: str):
|
def __init__(self, filename: str):
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.file = None
|
self.file = None
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.file = open(self.filename, 'r')
|
self.file = open(self.filename, 'r')
|
||||||
return self.file
|
return self.file
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
if self.file:
|
if self.file:
|
||||||
self.file.close()
|
self.file.close()
|
||||||
@@ -173,13 +173,13 @@ def slow_function():
|
|||||||
def singleton(cls):
|
def singleton(cls):
|
||||||
"""Decorator to make a class a singleton"""
|
"""Decorator to make a class a singleton"""
|
||||||
instances = {}
|
instances = {}
|
||||||
|
|
||||||
@wraps(cls)
|
@wraps(cls)
|
||||||
def get_instance(*args, **kwargs):
|
def get_instance(*args, **kwargs):
|
||||||
if cls not in instances:
|
if cls not in instances:
|
||||||
instances[cls] = cls(*args, **kwargs)
|
instances[cls] = cls(*args, **kwargs)
|
||||||
return instances[cls]
|
return instances[cls]
|
||||||
|
|
||||||
return get_instance
|
return get_instance
|
||||||
|
|
||||||
@singleton
|
@singleton
|
||||||
@@ -216,7 +216,7 @@ class AsyncDatabase:
|
|||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
await self.connect()
|
await self.connect()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
await self.disconnect()
|
await self.disconnect()
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ class Repository(Generic[T]):
|
|||||||
"""Generic repository pattern"""
|
"""Generic repository pattern"""
|
||||||
def __init__(self, entity_type: type[T]):
|
def __init__(self, entity_type: type[T]):
|
||||||
self.entity_type = entity_type
|
self.entity_type = entity_type
|
||||||
|
|
||||||
def find_by_id(self, id: str) -> T | None:
|
def find_by_id(self, id: str) -> T | None:
|
||||||
# implementation
|
# implementation
|
||||||
pass
|
pass
|
||||||
@@ -280,17 +280,17 @@ class UserService:
|
|||||||
self.repository = repository
|
self.repository = repository
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.cache = cache
|
self.cache = cache
|
||||||
|
|
||||||
def get_user(self, user_id: str) -> User | None:
|
def get_user(self, user_id: str) -> User | None:
|
||||||
if self.cache:
|
if self.cache:
|
||||||
cached = self.cache.get(user_id)
|
cached = self.cache.get(user_id)
|
||||||
if cached:
|
if cached:
|
||||||
return cached
|
return cached
|
||||||
|
|
||||||
user = self.repository.find_by_id(user_id)
|
user = self.repository.find_by_id(user_id)
|
||||||
if user and self.cache:
|
if user and self.cache:
|
||||||
self.cache.set(user_id, user)
|
self.cache.set(user_id, user)
|
||||||
|
|
||||||
return user
|
return user
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -375,16 +375,16 @@ class User:
|
|||||||
def __init__(self, name: str):
|
def __init__(self, name: str):
|
||||||
self._name = name
|
self._name = name
|
||||||
self._email = None
|
self._email = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Read-only property"""
|
"""Read-only property"""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def email(self) -> str | None:
|
def email(self) -> str | None:
|
||||||
return self._email
|
return self._email
|
||||||
|
|
||||||
@email.setter
|
@email.setter
|
||||||
def email(self, value: str) -> None:
|
def email(self, value: str) -> None:
|
||||||
if '@' not in value:
|
if '@' not in value:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Use **pytest** as the testing framework for its powerful features and clean synt
|
|||||||
def test_user_creation():
|
def test_user_creation():
|
||||||
"""Test that a user can be created with valid data"""
|
"""Test that a user can be created with valid data"""
|
||||||
user = User(name="Alice", email="alice@example.com")
|
user = User(name="Alice", email="alice@example.com")
|
||||||
|
|
||||||
assert user.name == "Alice"
|
assert user.name == "Alice"
|
||||||
assert user.email == "alice@example.com"
|
assert user.email == "alice@example.com"
|
||||||
assert user.is_active is True
|
assert user.is_active is True
|
||||||
@@ -52,12 +52,12 @@ def db_session():
|
|||||||
engine = create_engine("sqlite:///:memory:")
|
engine = create_engine("sqlite:///:memory:")
|
||||||
Session = sessionmaker(bind=engine)
|
Session = sessionmaker(bind=engine)
|
||||||
session = Session()
|
session = Session()
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
Base.metadata.create_all(engine)
|
Base.metadata.create_all(engine)
|
||||||
|
|
||||||
yield session
|
yield session
|
||||||
|
|
||||||
# Teardown
|
# Teardown
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ def test_user_repository(db_session):
|
|||||||
"""Test using the db_session fixture"""
|
"""Test using the db_session fixture"""
|
||||||
repo = UserRepository(db_session)
|
repo = UserRepository(db_session)
|
||||||
user = repo.create(name="Alice", email="alice@example.com")
|
user = repo.create(name="Alice", email="alice@example.com")
|
||||||
|
|
||||||
assert user.id is not None
|
assert user.id is not None
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -206,10 +206,10 @@ def test_user_service_with_mock():
|
|||||||
"""Test with mock repository"""
|
"""Test with mock repository"""
|
||||||
mock_repo = Mock()
|
mock_repo = Mock()
|
||||||
mock_repo.find_by_id.return_value = User(id="1", name="Alice")
|
mock_repo.find_by_id.return_value = User(id="1", name="Alice")
|
||||||
|
|
||||||
service = UserService(mock_repo)
|
service = UserService(mock_repo)
|
||||||
user = service.get_user("1")
|
user = service.get_user("1")
|
||||||
|
|
||||||
assert user.name == "Alice"
|
assert user.name == "Alice"
|
||||||
mock_repo.find_by_id.assert_called_once_with("1")
|
mock_repo.find_by_id.assert_called_once_with("1")
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ def test_send_notification(mock_email_service):
|
|||||||
"""Test with patched dependency"""
|
"""Test with patched dependency"""
|
||||||
service = NotificationService()
|
service = NotificationService()
|
||||||
service.send("user@example.com", "Hello")
|
service.send("user@example.com", "Hello")
|
||||||
|
|
||||||
mock_email_service.send.assert_called_once()
|
mock_email_service.send.assert_called_once()
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -229,10 +229,10 @@ def test_with_mocker(mocker):
|
|||||||
"""Using pytest-mock plugin"""
|
"""Using pytest-mock plugin"""
|
||||||
mock_repo = mocker.Mock()
|
mock_repo = mocker.Mock()
|
||||||
mock_repo.find_by_id.return_value = User(id="1", name="Alice")
|
mock_repo.find_by_id.return_value = User(id="1", name="Alice")
|
||||||
|
|
||||||
service = UserService(mock_repo)
|
service = UserService(mock_repo)
|
||||||
user = service.get_user("1")
|
user = service.get_user("1")
|
||||||
|
|
||||||
assert user.name == "Alice"
|
assert user.name == "Alice"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -357,7 +357,7 @@ def test_with_context():
|
|||||||
"""pytest provides detailed assertion introspection"""
|
"""pytest provides detailed assertion introspection"""
|
||||||
result = calculate_total([1, 2, 3])
|
result = calculate_total([1, 2, 3])
|
||||||
expected = 6
|
expected = 6
|
||||||
|
|
||||||
# pytest shows: assert 5 == 6
|
# pytest shows: assert 5 == 6
|
||||||
assert result == expected
|
assert result == expected
|
||||||
```
|
```
|
||||||
@@ -378,7 +378,7 @@ import pytest
|
|||||||
def test_float_comparison():
|
def test_float_comparison():
|
||||||
result = 0.1 + 0.2
|
result = 0.1 + 0.2
|
||||||
assert result == pytest.approx(0.3)
|
assert result == pytest.approx(0.3)
|
||||||
|
|
||||||
# With tolerance
|
# With tolerance
|
||||||
assert result == pytest.approx(0.3, abs=1e-9)
|
assert result == pytest.approx(0.3, abs=1e-9)
|
||||||
```
|
```
|
||||||
@@ -402,7 +402,7 @@ def test_exception_details():
|
|||||||
"""Capture and inspect exception"""
|
"""Capture and inspect exception"""
|
||||||
with pytest.raises(ValidationError) as exc_info:
|
with pytest.raises(ValidationError) as exc_info:
|
||||||
validate_user(name="", age=-1)
|
validate_user(name="", age=-1)
|
||||||
|
|
||||||
assert "name" in exc_info.value.errors
|
assert "name" in exc_info.value.errors
|
||||||
assert "age" in exc_info.value.errors
|
assert "age" in exc_info.value.errors
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ This skill ensures all code follows security best practices and identifies poten
|
|||||||
|
|
||||||
### 1. Secrets Management
|
### 1. Secrets Management
|
||||||
|
|
||||||
#### ❌ NEVER Do This
|
#### FAIL: NEVER Do This
|
||||||
```typescript
|
```typescript
|
||||||
const apiKey = "sk-proj-xxxxx" // Hardcoded secret
|
const apiKey = "sk-proj-xxxxx" // Hardcoded secret
|
||||||
const dbPassword = "password123" // In source code
|
const dbPassword = "password123" // In source code
|
||||||
```
|
```
|
||||||
|
|
||||||
#### ✅ ALWAYS Do This
|
#### PASS: ALWAYS Do This
|
||||||
```typescript
|
```typescript
|
||||||
const apiKey = process.env.OPENAI_API_KEY
|
const apiKey = process.env.OPENAI_API_KEY
|
||||||
const dbUrl = process.env.DATABASE_URL
|
const dbUrl = process.env.DATABASE_URL
|
||||||
@@ -110,14 +110,14 @@ function validateFileUpload(file: File) {
|
|||||||
|
|
||||||
### 3. SQL Injection Prevention
|
### 3. SQL Injection Prevention
|
||||||
|
|
||||||
#### ❌ NEVER Concatenate SQL
|
#### FAIL: NEVER Concatenate SQL
|
||||||
```typescript
|
```typescript
|
||||||
// DANGEROUS - SQL Injection vulnerability
|
// DANGEROUS - SQL Injection vulnerability
|
||||||
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
|
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
|
||||||
await db.query(query)
|
await db.query(query)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### ✅ ALWAYS Use Parameterized Queries
|
#### PASS: ALWAYS Use Parameterized Queries
|
||||||
```typescript
|
```typescript
|
||||||
// Safe - parameterized query
|
// Safe - parameterized query
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
@@ -142,10 +142,10 @@ await db.query(
|
|||||||
|
|
||||||
#### JWT Token Handling
|
#### JWT Token Handling
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ WRONG: localStorage (vulnerable to XSS)
|
// FAIL: WRONG: localStorage (vulnerable to XSS)
|
||||||
localStorage.setItem('token', token)
|
localStorage.setItem('token', token)
|
||||||
|
|
||||||
// ✅ CORRECT: httpOnly cookies
|
// PASS: CORRECT: httpOnly cookies
|
||||||
res.setHeader('Set-Cookie',
|
res.setHeader('Set-Cookie',
|
||||||
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
||||||
```
|
```
|
||||||
@@ -302,18 +302,18 @@ app.use('/api/search', searchLimiter)
|
|||||||
|
|
||||||
#### Logging
|
#### Logging
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ WRONG: Logging sensitive data
|
// FAIL: WRONG: Logging sensitive data
|
||||||
console.log('User login:', { email, password })
|
console.log('User login:', { email, password })
|
||||||
console.log('Payment:', { cardNumber, cvv })
|
console.log('Payment:', { cardNumber, cvv })
|
||||||
|
|
||||||
// ✅ CORRECT: Redact sensitive data
|
// PASS: CORRECT: Redact sensitive data
|
||||||
console.log('User login:', { email, userId })
|
console.log('User login:', { email, userId })
|
||||||
console.log('Payment:', { last4: card.last4, userId })
|
console.log('Payment:', { last4: card.last4, userId })
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Error Messages
|
#### Error Messages
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ WRONG: Exposing internal details
|
// FAIL: WRONG: Exposing internal details
|
||||||
catch (error) {
|
catch (error) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: error.message, stack: error.stack },
|
{ error: error.message, stack: error.stack },
|
||||||
@@ -321,7 +321,7 @@ catch (error) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ CORRECT: Generic error messages
|
// PASS: CORRECT: Generic error messages
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('Internal error:', error)
|
console.error('Internal error:', error)
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -318,39 +318,39 @@ npm run test:coverage
|
|||||||
|
|
||||||
## Common Testing Mistakes to Avoid
|
## Common Testing Mistakes to Avoid
|
||||||
|
|
||||||
### ❌ WRONG: Testing Implementation Details
|
### FAIL: WRONG: Testing Implementation Details
|
||||||
```typescript
|
```typescript
|
||||||
// Don't test internal state
|
// Don't test internal state
|
||||||
expect(component.state.count).toBe(5)
|
expect(component.state.count).toBe(5)
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ CORRECT: Test User-Visible Behavior
|
### PASS: CORRECT: Test User-Visible Behavior
|
||||||
```typescript
|
```typescript
|
||||||
// Test what users see
|
// Test what users see
|
||||||
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
||||||
```
|
```
|
||||||
|
|
||||||
### ❌ WRONG: Brittle Selectors
|
### FAIL: WRONG: Brittle Selectors
|
||||||
```typescript
|
```typescript
|
||||||
// Breaks easily
|
// Breaks easily
|
||||||
await page.click('.css-class-xyz')
|
await page.click('.css-class-xyz')
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ CORRECT: Semantic Selectors
|
### PASS: CORRECT: Semantic Selectors
|
||||||
```typescript
|
```typescript
|
||||||
// Resilient to changes
|
// Resilient to changes
|
||||||
await page.click('button:has-text("Submit")')
|
await page.click('button:has-text("Submit")')
|
||||||
await page.click('[data-testid="submit-button"]')
|
await page.click('[data-testid="submit-button"]')
|
||||||
```
|
```
|
||||||
|
|
||||||
### ❌ WRONG: No Test Isolation
|
### FAIL: WRONG: No Test Isolation
|
||||||
```typescript
|
```typescript
|
||||||
// Tests depend on each other
|
// Tests depend on each other
|
||||||
test('creates user', () => { /* ... */ })
|
test('creates user', () => { /* ... */ })
|
||||||
test('updates same user', () => { /* depends on previous test */ })
|
test('updates same user', () => { /* depends on previous test */ })
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ CORRECT: Independent Tests
|
### PASS: CORRECT: Independent Tests
|
||||||
```typescript
|
```typescript
|
||||||
// Each test sets up its own data
|
// Each test sets up its own data
|
||||||
test('creates user', () => {
|
test('creates user', () => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"args": ["-y", "@upstash/context7-mcp@2.1.4"]
|
"args": ["-y", "@upstash/context7-mcp@2.1.4"]
|
||||||
},
|
},
|
||||||
"exa": {
|
"exa": {
|
||||||
|
"type": "http",
|
||||||
"url": "https://mcp.exa.ai/mcp"
|
"url": "https://mcp.exa.ai/mcp"
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
|
|||||||
@@ -353,13 +353,13 @@ If you need to switch back:
|
|||||||
|
|
||||||
| Feature | Claude Code | OpenCode | Status |
|
| Feature | Claude Code | OpenCode | Status |
|
||||||
|---------|-------------|----------|--------|
|
|---------|-------------|----------|--------|
|
||||||
| Agents | ✅ 12 agents | ✅ 12 agents | **Full parity** |
|
| Agents | PASS: 12 agents | PASS: 12 agents | **Full parity** |
|
||||||
| Commands | ✅ 23 commands | ✅ 23 commands | **Full parity** |
|
| Commands | PASS: 23 commands | PASS: 23 commands | **Full parity** |
|
||||||
| Skills | ✅ 16 skills | ✅ 16 skills | **Full parity** |
|
| Skills | PASS: 16 skills | PASS: 16 skills | **Full parity** |
|
||||||
| Hooks | ✅ 3 phases | ✅ 20+ events | **OpenCode has MORE** |
|
| Hooks | PASS: 3 phases | PASS: 20+ events | **OpenCode has MORE** |
|
||||||
| Rules | ✅ 8 rules | ✅ 8 rules | **Full parity** |
|
| Rules | PASS: 8 rules | PASS: 8 rules | **Full parity** |
|
||||||
| MCP Servers | ✅ Full | ✅ Full | **Full parity** |
|
| MCP Servers | PASS: Full | PASS: Full | **Full parity** |
|
||||||
| Custom Tools | ✅ Via hooks | ✅ Native support | **OpenCode is better** |
|
| Custom Tools | PASS: Via hooks | PASS: Native support | **OpenCode is better** |
|
||||||
|
|
||||||
## Feedback
|
## Feedback
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# OpenCode ECC Plugin
|
# OpenCode ECC Plugin
|
||||||
|
|
||||||
> ⚠️ This README is specific to OpenCode usage.
|
> WARNING: This README is specific to OpenCode usage.
|
||||||
> If you installed ECC via npm (e.g. `npm install opencode-ecc`), refer to the root README instead.
|
> If you installed ECC via npm (e.g. `npm install opencode-ecc`), refer to the root README instead.
|
||||||
|
|
||||||
Everything Claude Code (ECC) plugin for OpenCode - agents, commands, hooks, and skills.
|
Everything Claude Code (ECC) plugin for OpenCode - agents, commands, hooks, and skills.
|
||||||
@@ -11,10 +11,10 @@ Everything Claude Code (ECC) plugin for OpenCode - agents, commands, hooks, and
|
|||||||
|
|
||||||
There are two ways to use Everything Claude Code (ECC):
|
There are two ways to use Everything Claude Code (ECC):
|
||||||
|
|
||||||
1. **npm package (recommended for most users)**
|
1. **npm package (recommended for most users)**
|
||||||
Install via npm/bun/yarn and use the `ecc-install` CLI to set up rules and agents.
|
Install via npm/bun/yarn and use the `ecc-install` CLI to set up rules and agents.
|
||||||
|
|
||||||
2. **Direct clone / plugin mode**
|
2. **Direct clone / plugin mode**
|
||||||
Clone the repository and run OpenCode directly inside it.
|
Clone the repository and run OpenCode directly inside it.
|
||||||
|
|
||||||
Choose the method that matches your workflow below.
|
Choose the method that matches your workflow below.
|
||||||
|
|||||||
@@ -19,20 +19,20 @@ Fix build and TypeScript errors with minimal changes: $ARGUMENTS
|
|||||||
## Approach
|
## Approach
|
||||||
|
|
||||||
### DO:
|
### DO:
|
||||||
- ✅ Fix type errors with correct types
|
- PASS: Fix type errors with correct types
|
||||||
- ✅ Add missing imports
|
- PASS: Add missing imports
|
||||||
- ✅ Fix syntax errors
|
- PASS: Fix syntax errors
|
||||||
- ✅ Make minimal changes
|
- PASS: Make minimal changes
|
||||||
- ✅ Preserve existing behavior
|
- PASS: Preserve existing behavior
|
||||||
- ✅ Run `tsc --noEmit` after each change
|
- PASS: Run `tsc --noEmit` after each change
|
||||||
|
|
||||||
### DON'T:
|
### DON'T:
|
||||||
- ❌ Refactor code
|
- FAIL: Refactor code
|
||||||
- ❌ Add new features
|
- FAIL: Add new features
|
||||||
- ❌ Change architecture
|
- FAIL: Change architecture
|
||||||
- ❌ Use `any` type (unless absolutely necessary)
|
- FAIL: Use `any` type (unless absolutely necessary)
|
||||||
- ❌ Add `@ts-ignore` comments
|
- FAIL: Add `@ts-ignore` comments
|
||||||
- ❌ Change business logic
|
- FAIL: Change business logic
|
||||||
|
|
||||||
## Common Error Fixes
|
## Common Error Fixes
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Create a snapshot of current progress including:
|
|||||||
- Coverage: XX%
|
- Coverage: XX%
|
||||||
|
|
||||||
**Build**
|
**Build**
|
||||||
- Status: ✅ Passing / ❌ Failing
|
- Status: PASS: Passing / FAIL: Failing
|
||||||
- Errors: [if any]
|
- Errors: [if any]
|
||||||
|
|
||||||
**Changes Since Last Checkpoint**
|
**Changes Since Last Checkpoint**
|
||||||
|
|||||||
@@ -90,9 +90,9 @@ test.describe('Feature: [Name]', () => {
|
|||||||
```
|
```
|
||||||
E2E Test Results
|
E2E Test Results
|
||||||
================
|
================
|
||||||
✅ Passed: X
|
PASS: Passed: X
|
||||||
❌ Failed: Y
|
FAIL: Failed: Y
|
||||||
⏭️ Skipped: Z
|
SKIPPED: Skipped: Z
|
||||||
|
|
||||||
Failed Tests:
|
Failed Tests:
|
||||||
- test-name: Error message
|
- test-name: Error message
|
||||||
|
|||||||
@@ -4,22 +4,23 @@ Run a deterministic repository harness audit and return a prioritized scorecard.
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
`/harness-audit [scope] [--format text|json]`
|
`/harness-audit [scope] [--format text|json] [--root path]`
|
||||||
|
|
||||||
- `scope` (optional): `repo` (default), `hooks`, `skills`, `commands`, `agents`
|
- `scope` (optional): `repo` (default), `hooks`, `skills`, `commands`, `agents`
|
||||||
- `--format`: output style (`text` default, `json` for automation)
|
- `--format`: output style (`text` default, `json` for automation)
|
||||||
|
- `--root`: audit a specific path instead of the current working directory
|
||||||
|
|
||||||
## Deterministic Engine
|
## Deterministic Engine
|
||||||
|
|
||||||
Always run:
|
Always run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node scripts/harness-audit.js <scope> --format <text|json>
|
node scripts/harness-audit.js <scope> --format <text|json> [--root <path>]
|
||||||
```
|
```
|
||||||
|
|
||||||
This script is the source of truth for scoring and checks. Do not invent additional dimensions or ad-hoc points.
|
This script is the source of truth for scoring and checks. Do not invent additional dimensions or ad-hoc points.
|
||||||
|
|
||||||
Rubric version: `2026-03-16`.
|
Rubric version: `2026-03-30`.
|
||||||
|
|
||||||
The script computes 7 fixed categories (`0-10` normalized each):
|
The script computes 7 fixed categories (`0-10` normalized each):
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ The script computes 7 fixed categories (`0-10` normalized each):
|
|||||||
7. Cost Efficiency
|
7. Cost Efficiency
|
||||||
|
|
||||||
Scores are derived from explicit file/rule checks and are reproducible for the same commit.
|
Scores are derived from explicit file/rule checks and are reproducible for the same commit.
|
||||||
|
The script audits the current working directory by default and auto-detects whether the target is the ECC repo itself or a consumer project using ECC.
|
||||||
|
|
||||||
## Output Contract
|
## Output Contract
|
||||||
|
|
||||||
|
|||||||
@@ -47,17 +47,17 @@ Execute comprehensive verification:
|
|||||||
## Verification Report
|
## Verification Report
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
- Status: ✅ PASS / ❌ FAIL
|
- Status: PASS: PASS / FAIL: FAIL
|
||||||
- Score: X/Y checks passed
|
- Score: X/Y checks passed
|
||||||
|
|
||||||
### Details
|
### Details
|
||||||
| Check | Status | Notes |
|
| Check | Status | Notes |
|
||||||
|-------|--------|-------|
|
|-------|--------|-------|
|
||||||
| TypeScript | ✅/❌ | [details] |
|
| TypeScript | PASS:/FAIL: | [details] |
|
||||||
| Lint | ✅/❌ | [details] |
|
| Lint | PASS:/FAIL: | [details] |
|
||||||
| Tests | ✅/❌ | [details] |
|
| Tests | PASS:/FAIL: | [details] |
|
||||||
| Coverage | ✅/❌ | XX% (target: 80%) |
|
| Coverage | PASS:/FAIL: | XX% (target: 80%) |
|
||||||
| Build | ✅/❌ | [details] |
|
| Build | PASS:/FAIL: | [details] |
|
||||||
|
|
||||||
### Action Items
|
### Action Items
|
||||||
[If FAIL, list what needs to be fixed]
|
[If FAIL, list what needs to be fixed]
|
||||||
|
|||||||
@@ -39,6 +39,40 @@ ensure_manifest_entry() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
manifest_has_entry() {
|
||||||
|
local manifest="$1"
|
||||||
|
local entry="$2"
|
||||||
|
|
||||||
|
[ -f "$manifest" ] && grep -Fqx "$entry" "$manifest"
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_managed_file() {
|
||||||
|
local source_path="$1"
|
||||||
|
local target_path="$2"
|
||||||
|
local manifest="$3"
|
||||||
|
local manifest_entry="$4"
|
||||||
|
local make_executable="${5:-0}"
|
||||||
|
|
||||||
|
local already_managed=0
|
||||||
|
if manifest_has_entry "$manifest" "$manifest_entry"; then
|
||||||
|
already_managed=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$target_path" ]; then
|
||||||
|
if [ "$already_managed" -eq 1 ]; then
|
||||||
|
ensure_manifest_entry "$manifest" "$manifest_entry"
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "$source_path" "$target_path"
|
||||||
|
if [ "$make_executable" -eq 1 ]; then
|
||||||
|
chmod +x "$target_path"
|
||||||
|
fi
|
||||||
|
ensure_manifest_entry "$manifest" "$manifest_entry"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# Install function
|
# Install function
|
||||||
do_install() {
|
do_install() {
|
||||||
local target_dir="$PWD"
|
local target_dir="$PWD"
|
||||||
@@ -95,12 +129,8 @@ do_install() {
|
|||||||
[ -f "$f" ] || continue
|
[ -f "$f" ] || continue
|
||||||
local_name=$(basename "$f")
|
local_name=$(basename "$f")
|
||||||
target_path="$trae_full_path/commands/$local_name"
|
target_path="$trae_full_path/commands/$local_name"
|
||||||
if [ ! -f "$target_path" ]; then
|
if copy_managed_file "$f" "$target_path" "$MANIFEST" "commands/$local_name"; then
|
||||||
cp "$f" "$target_path"
|
|
||||||
ensure_manifest_entry "$MANIFEST" "commands/$local_name"
|
|
||||||
commands=$((commands + 1))
|
commands=$((commands + 1))
|
||||||
else
|
|
||||||
ensure_manifest_entry "$MANIFEST" "commands/$local_name"
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@@ -111,12 +141,8 @@ do_install() {
|
|||||||
[ -f "$f" ] || continue
|
[ -f "$f" ] || continue
|
||||||
local_name=$(basename "$f")
|
local_name=$(basename "$f")
|
||||||
target_path="$trae_full_path/agents/$local_name"
|
target_path="$trae_full_path/agents/$local_name"
|
||||||
if [ ! -f "$target_path" ]; then
|
if copy_managed_file "$f" "$target_path" "$MANIFEST" "agents/$local_name"; then
|
||||||
cp "$f" "$target_path"
|
|
||||||
ensure_manifest_entry "$MANIFEST" "agents/$local_name"
|
|
||||||
agents=$((agents + 1))
|
agents=$((agents + 1))
|
||||||
else
|
|
||||||
ensure_manifest_entry "$MANIFEST" "agents/$local_name"
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@@ -134,11 +160,9 @@ do_install() {
|
|||||||
target_path="$target_skill_dir/$relative_path"
|
target_path="$target_skill_dir/$relative_path"
|
||||||
|
|
||||||
mkdir -p "$(dirname "$target_path")"
|
mkdir -p "$(dirname "$target_path")"
|
||||||
if [ ! -f "$target_path" ]; then
|
if copy_managed_file "$source_file" "$target_path" "$MANIFEST" "skills/$skill_name/$relative_path"; then
|
||||||
cp "$source_file" "$target_path"
|
|
||||||
skill_copied=1
|
skill_copied=1
|
||||||
fi
|
fi
|
||||||
ensure_manifest_entry "$MANIFEST" "skills/$skill_name/$relative_path"
|
|
||||||
done < <(find "$d" -type f | sort)
|
done < <(find "$d" -type f | sort)
|
||||||
|
|
||||||
if [ "$skill_copied" -eq 1 ]; then
|
if [ "$skill_copied" -eq 1 ]; then
|
||||||
@@ -154,11 +178,9 @@ do_install() {
|
|||||||
target_path="$trae_full_path/rules/$relative_path"
|
target_path="$trae_full_path/rules/$relative_path"
|
||||||
|
|
||||||
mkdir -p "$(dirname "$target_path")"
|
mkdir -p "$(dirname "$target_path")"
|
||||||
if [ ! -f "$target_path" ]; then
|
if copy_managed_file "$rule_file" "$target_path" "$MANIFEST" "rules/$relative_path"; then
|
||||||
cp "$rule_file" "$target_path"
|
|
||||||
rules=$((rules + 1))
|
rules=$((rules + 1))
|
||||||
fi
|
fi
|
||||||
ensure_manifest_entry "$MANIFEST" "rules/$relative_path"
|
|
||||||
done < <(find "$REPO_ROOT/rules" -type f | sort)
|
done < <(find "$REPO_ROOT/rules" -type f | sort)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -167,12 +189,8 @@ do_install() {
|
|||||||
if [ -f "$readme_file" ]; then
|
if [ -f "$readme_file" ]; then
|
||||||
local_name=$(basename "$readme_file")
|
local_name=$(basename "$readme_file")
|
||||||
target_path="$trae_full_path/$local_name"
|
target_path="$trae_full_path/$local_name"
|
||||||
if [ ! -f "$target_path" ]; then
|
if copy_managed_file "$readme_file" "$target_path" "$MANIFEST" "$local_name"; then
|
||||||
cp "$readme_file" "$target_path"
|
|
||||||
ensure_manifest_entry "$MANIFEST" "$local_name"
|
|
||||||
other=$((other + 1))
|
other=$((other + 1))
|
||||||
else
|
|
||||||
ensure_manifest_entry "$MANIFEST" "$local_name"
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -182,13 +200,8 @@ do_install() {
|
|||||||
if [ -f "$script_file" ]; then
|
if [ -f "$script_file" ]; then
|
||||||
local_name=$(basename "$script_file")
|
local_name=$(basename "$script_file")
|
||||||
target_path="$trae_full_path/$local_name"
|
target_path="$trae_full_path/$local_name"
|
||||||
if [ ! -f "$target_path" ]; then
|
if copy_managed_file "$script_file" "$target_path" "$MANIFEST" "$local_name" 1; then
|
||||||
cp "$script_file" "$target_path"
|
|
||||||
chmod +x "$target_path"
|
|
||||||
ensure_manifest_entry "$MANIFEST" "$local_name"
|
|
||||||
other=$((other + 1))
|
other=$((other + 1))
|
||||||
else
|
|
||||||
ensure_manifest_entry "$MANIFEST" "$local_name"
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ git add . && git commit -m "feat: add my-skill" && git push -u origin feat/my-co
|
|||||||
|
|
||||||
Skills are knowledge modules that Claude Code loads based on context.
|
Skills are knowledge modules that Claude Code loads based on context.
|
||||||
|
|
||||||
> **📚 Comprehensive Guide:** For detailed guidance on creating effective skills, see [Skill Development Guide](docs/SKILL-DEVELOPMENT-GUIDE.md). It covers:
|
> ** Comprehensive Guide:** For detailed guidance on creating effective skills, see [Skill Development Guide](docs/SKILL-DEVELOPMENT-GUIDE.md). It covers:
|
||||||
> - Skill architecture and categories
|
> - Skill architecture and categories
|
||||||
> - Writing effective content with examples
|
> - Writing effective content with examples
|
||||||
> - Best practices and common patterns
|
> - Best practices and common patterns
|
||||||
|
|||||||
66
README.md
66
README.md
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
**🌐 Language / 语言 / 語言 / Dil**
|
**Language / 语言 / 語言 / Dil**
|
||||||
|
|
||||||
[**English**](README.md) | [Português (Brasil)](docs/pt-BR/README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md) | [한국어](docs/ko-KR/README.md)
|
[**English**](README.md) | [Português (Brasil)](docs/pt-BR/README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md) | [한국어](docs/ko-KR/README.md)
|
||||||
| [Türkçe](docs/tr/README.md)
|
| [Türkçe](docs/tr/README.md)
|
||||||
@@ -151,7 +151,7 @@ See the full changelog in [Releases](https://github.com/affaan-m/everything-clau
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## Quick Start
|
||||||
|
|
||||||
Get up and running in under 2 minutes:
|
Get up and running in under 2 minutes:
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ Get up and running in under 2 minutes:
|
|||||||
|
|
||||||
### Step 2: Install Rules (Required)
|
### Step 2: Install Rules (Required)
|
||||||
|
|
||||||
> ⚠️ **Important:** Claude Code plugins cannot distribute `rules` automatically. Install them manually:
|
> WARNING: **Important:** Claude Code plugins cannot distribute `rules` automatically. Install them manually:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone the repo first
|
# Clone the repo first
|
||||||
@@ -220,11 +220,11 @@ For manual install instructions see the README in the `rules/` folder. When copy
|
|||||||
/plugin list everything-claude-code@everything-claude-code
|
/plugin list everything-claude-code@everything-claude-code
|
||||||
```
|
```
|
||||||
|
|
||||||
✨ **That's it!** You now have access to 30 agents, 135 skills, and 60 commands.
|
**That's it!** You now have access to 30 agents, 135 skills, and 60 commands.
|
||||||
|
|
||||||
### Multi-model commands require additional setup
|
### Multi-model commands require additional setup
|
||||||
|
|
||||||
> ⚠️ `multi-*` commands are **not** covered by the base plugin/rules install above.
|
> WARNING: `multi-*` commands are **not** covered by the base plugin/rules install above.
|
||||||
>
|
>
|
||||||
> To use `/multi-plan`, `/multi-execute`, `/multi-backend`, `/multi-frontend`, and `/multi-workflow`, you must also install the `ccg-workflow` runtime.
|
> To use `/multi-plan`, `/multi-execute`, `/multi-backend`, `/multi-frontend`, and `/multi-workflow`, you must also install the `ccg-workflow` runtime.
|
||||||
>
|
>
|
||||||
@@ -238,7 +238,7 @@ For manual install instructions see the README in the `rules/` folder. When copy
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🌐 Cross-Platform Support
|
## Cross-Platform Support
|
||||||
|
|
||||||
This plugin now fully supports **Windows, macOS, and Linux**, alongside tight integration across major IDEs (Cursor, OpenCode, Antigravity) and CLI harnesses. All hooks and scripts have been rewritten in Node.js for maximum compatibility.
|
This plugin now fully supports **Windows, macOS, and Linux**, alongside tight integration across major IDEs (Cursor, OpenCode, Antigravity) and CLI harnesses. All hooks and scripts have been rewritten in Node.js for maximum compatibility.
|
||||||
|
|
||||||
@@ -285,7 +285,7 @@ export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck"
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📦 What's Inside
|
## What's Inside
|
||||||
|
|
||||||
This repo is a **Claude Code plugin** - install it directly or copy components manually.
|
This repo is a **Claude Code plugin** - install it directly or copy components manually.
|
||||||
|
|
||||||
@@ -487,7 +487,7 @@ everything-claude-code/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠️ Ecosystem Tools
|
## Ecosystem Tools
|
||||||
|
|
||||||
### Skill Creator
|
### Skill Creator
|
||||||
|
|
||||||
@@ -552,7 +552,7 @@ Use `/security-scan` in Claude Code to run it, or add to CI with the [GitHub Act
|
|||||||
|
|
||||||
[GitHub](https://github.com/affaan-m/agentshield) | [npm](https://www.npmjs.com/package/ecc-agentshield)
|
[GitHub](https://github.com/affaan-m/agentshield) | [npm](https://www.npmjs.com/package/ecc-agentshield)
|
||||||
|
|
||||||
### 🧠 Continuous Learning v2
|
### Continuous Learning v2
|
||||||
|
|
||||||
The instinct-based learning system automatically learns your patterns:
|
The instinct-based learning system automatically learns your patterns:
|
||||||
|
|
||||||
@@ -567,7 +567,7 @@ See `skills/continuous-learning-v2/` for full documentation.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 Requirements
|
## Requirements
|
||||||
|
|
||||||
### Claude Code CLI Version
|
### Claude Code CLI Version
|
||||||
|
|
||||||
@@ -582,7 +582,7 @@ claude --version
|
|||||||
|
|
||||||
### Important: Hooks Auto-Loading Behavior
|
### Important: Hooks Auto-Loading Behavior
|
||||||
|
|
||||||
> ⚠️ **For Contributors:** Do NOT add a `"hooks"` field to `.claude-plugin/plugin.json`. This is enforced by a regression test.
|
> WARNING: **For Contributors:** Do NOT add a `"hooks"` field to `.claude-plugin/plugin.json`. This is enforced by a regression test.
|
||||||
|
|
||||||
Claude Code v2.1+ **automatically loads** `hooks/hooks.json` from any installed plugin by convention. Explicitly declaring it in `plugin.json` causes a duplicate detection error:
|
Claude Code v2.1+ **automatically loads** `hooks/hooks.json` from any installed plugin by convention. Explicitly declaring it in `plugin.json` causes a duplicate detection error:
|
||||||
|
|
||||||
@@ -594,7 +594,7 @@ Duplicate hooks file detected: ./hooks/hooks.json resolves to already-loaded fil
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📥 Installation
|
## Installation
|
||||||
|
|
||||||
### Option 1: Install as Plugin (Recommended)
|
### Option 1: Install as Plugin (Recommended)
|
||||||
|
|
||||||
@@ -650,7 +650,7 @@ This gives you instant access to all commands, agents, skills, and hooks.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 🔧 Option 2: Manual Installation
|
### Option 2: Manual Installation
|
||||||
|
|
||||||
If you prefer manual control over what's installed:
|
If you prefer manual control over what's installed:
|
||||||
|
|
||||||
@@ -679,7 +679,7 @@ cp -r everything-claude-code/skills/search-first ~/.claude/skills/
|
|||||||
|
|
||||||
# Optional: add niche/framework-specific skills only when needed
|
# Optional: add niche/framework-specific skills only when needed
|
||||||
# for s in django-patterns django-tdd laravel-patterns springboot-patterns; do
|
# for s in django-patterns django-tdd laravel-patterns springboot-patterns; do
|
||||||
# cp -r everything-claude-code/skills/$s ~/.claude/skills/
|
# cp -r everything-claude-code/skills/$s ~/.claude/skills/
|
||||||
# done
|
# done
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -695,7 +695,7 @@ Copy desired MCP servers from `mcp-configs/mcp-servers.json` to your `~/.claude.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 Key Concepts
|
## Key Concepts
|
||||||
|
|
||||||
### Agents
|
### Agents
|
||||||
|
|
||||||
@@ -758,7 +758,7 @@ See [`rules/README.md`](rules/README.md) for installation and structure details.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🗺️ Which Agent Should I Use?
|
## Which Agent Should I Use?
|
||||||
|
|
||||||
Not sure where to start? Use this quick reference:
|
Not sure where to start? Use this quick reference:
|
||||||
|
|
||||||
@@ -804,7 +804,7 @@ Not sure where to start? Use this quick reference:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ❓ FAQ
|
## FAQ
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>How do I check which agents/commands are installed?</b></summary>
|
<summary><b>How do I check which agents/commands are installed?</b></summary>
|
||||||
@@ -903,7 +903,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). The short version:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧪 Running Tests
|
## Running Tests
|
||||||
|
|
||||||
The plugin includes a comprehensive test suite:
|
The plugin includes a comprehensive test suite:
|
||||||
|
|
||||||
@@ -919,7 +919,7 @@ node tests/hooks/hooks.test.js
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🤝 Contributing
|
## Contributing
|
||||||
|
|
||||||
**Contributions are welcome and encouraged.**
|
**Contributions are welcome and encouraged.**
|
||||||
|
|
||||||
@@ -1089,7 +1089,7 @@ ECC ships three sample role configs:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔌 OpenCode Support
|
## OpenCode Support
|
||||||
|
|
||||||
ECC provides **full OpenCode support** including plugins and hooks.
|
ECC provides **full OpenCode support** including plugins and hooks.
|
||||||
|
|
||||||
@@ -1109,13 +1109,13 @@ The configuration is automatically detected from `.opencode/opencode.json`.
|
|||||||
|
|
||||||
| Feature | Claude Code | OpenCode | Status |
|
| Feature | Claude Code | OpenCode | Status |
|
||||||
|---------|-------------|----------|--------|
|
|---------|-------------|----------|--------|
|
||||||
| Agents | ✅ 30 agents | ✅ 12 agents | **Claude Code leads** |
|
| Agents | PASS: 30 agents | PASS: 12 agents | **Claude Code leads** |
|
||||||
| Commands | ✅ 60 commands | ✅ 31 commands | **Claude Code leads** |
|
| Commands | PASS: 60 commands | PASS: 31 commands | **Claude Code leads** |
|
||||||
| Skills | ✅ 135 skills | ✅ 37 skills | **Claude Code leads** |
|
| Skills | PASS: 135 skills | PASS: 37 skills | **Claude Code leads** |
|
||||||
| Hooks | ✅ 8 event types | ✅ 11 events | **OpenCode has more!** |
|
| Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** |
|
||||||
| Rules | ✅ 29 rules | ✅ 13 instructions | **Claude Code leads** |
|
| Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** |
|
||||||
| MCP Servers | ✅ 14 servers | ✅ Full | **Full parity** |
|
| MCP Servers | PASS: 14 servers | PASS: Full | **Full parity** |
|
||||||
| Custom Tools | ✅ Via hooks | ✅ 6 native tools | **OpenCode is better** |
|
| Custom Tools | PASS: Via hooks | PASS: 6 native tools | **OpenCode is better** |
|
||||||
|
|
||||||
### Hook Support via Plugins
|
### Hook Support via Plugins
|
||||||
|
|
||||||
@@ -1240,7 +1240,7 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📖 Background
|
## Background
|
||||||
|
|
||||||
I've been using Claude Code since the experimental rollout. Won the Anthropic x Forum Ventures hackathon in Sep 2025 with [@DRodriguezFX](https://x.com/DRodriguezFX) — built [zenith.chat](https://zenith.chat) entirely using Claude Code.
|
I've been using Claude Code since the experimental rollout. Won the Anthropic x Forum Ventures hackathon in Sep 2025 with [@DRodriguezFX](https://x.com/DRodriguezFX) — built [zenith.chat](https://zenith.chat) entirely using Claude Code.
|
||||||
|
|
||||||
@@ -1314,7 +1314,7 @@ Agent Teams spawns multiple context windows. Each teammate consumes tokens indep
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚠️ Important Notes
|
## WARNING: Important Notes
|
||||||
|
|
||||||
### Token Optimization
|
### Token Optimization
|
||||||
|
|
||||||
@@ -1346,7 +1346,7 @@ These configs work for my workflow. You should:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 💜 Sponsors
|
## Sponsors
|
||||||
|
|
||||||
This project is free and open source. Sponsors help keep it maintained and growing.
|
This project is free and open source. Sponsors help keep it maintained and growing.
|
||||||
|
|
||||||
@@ -1354,13 +1354,13 @@ This project is free and open source. Sponsors help keep it maintained and growi
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🌟 Star History
|
## Star History
|
||||||
|
|
||||||
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
|
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔗 Links
|
## Links
|
||||||
|
|
||||||
- **Shorthand Guide (Start Here):** [The Shorthand Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2012378465664745795)
|
- **Shorthand Guide (Start Here):** [The Shorthand Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2012378465664745795)
|
||||||
- **Longform Guide (Advanced):** [The Longform Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2014040193557471352)
|
- **Longform Guide (Advanced):** [The Longform Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2014040193557471352)
|
||||||
@@ -1369,7 +1369,7 @@ This project is free and open source. Sponsors help keep it maintained and growi
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📄 License
|
## License
|
||||||
|
|
||||||
MIT - Use freely, modify as needed, contribute back if you can.
|
MIT - Use freely, modify as needed, contribute back if you can.
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
**🌐 Language / 语言 / 語言**
|
**Language / 语言 / 語言**
|
||||||
|
|
||||||
[**English**](README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md) | [한국어](docs/ko-KR/README.md)
|
[**English**](README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md) | [한국어](docs/ko-KR/README.md)
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 快速开始
|
## 快速开始
|
||||||
|
|
||||||
在 2 分钟内快速上手:
|
在 2 分钟内快速上手:
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
|
|
||||||
### 第二步:安装规则(必需)
|
### 第二步:安装规则(必需)
|
||||||
|
|
||||||
> ⚠️ **重要提示:** Claude Code 插件无法自动分发 `rules`,需要手动安装:
|
> WARNING: **重要提示:** Claude Code 插件无法自动分发 `rules`,需要手动安装:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 首先克隆仓库
|
# 首先克隆仓库
|
||||||
@@ -106,11 +106,11 @@ cp -r everything-claude-code/rules/perl ~/.claude/rules/
|
|||||||
/plugin list everything-claude-code@everything-claude-code
|
/plugin list everything-claude-code@everything-claude-code
|
||||||
```
|
```
|
||||||
|
|
||||||
✨ **完成!** 你现在可以使用 13 个代理、43 个技能和 31 个命令。
|
**完成!** 你现在可以使用 13 个代理、43 个技能和 31 个命令。
|
||||||
|
|
||||||
### multi-* 命令需要额外配置
|
### multi-* 命令需要额外配置
|
||||||
|
|
||||||
> ⚠️ 上面的基础插件 / rules 安装**不包含** `multi-*` 命令所需的运行时。
|
> WARNING: 上面的基础插件 / rules 安装**不包含** `multi-*` 命令所需的运行时。
|
||||||
>
|
>
|
||||||
> 如果要使用 `/multi-plan`、`/multi-execute`、`/multi-backend`、`/multi-frontend` 和 `/multi-workflow`,还需要额外安装 `ccg-workflow` 运行时。
|
> 如果要使用 `/multi-plan`、`/multi-execute`、`/multi-backend`、`/multi-frontend` 和 `/multi-workflow`,还需要额外安装 `ccg-workflow` 运行时。
|
||||||
>
|
>
|
||||||
@@ -124,7 +124,7 @@ cp -r everything-claude-code/rules/perl ~/.claude/rules/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🌐 跨平台支持
|
## 跨平台支持
|
||||||
|
|
||||||
此插件现在完全支持 **Windows、macOS 和 Linux**。所有钩子和脚本都已用 Node.js 重写,以实现最大的兼容性。
|
此插件现在完全支持 **Windows、macOS 和 Linux**。所有钩子和脚本都已用 Node.js 重写,以实现最大的兼容性。
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ node scripts/setup-package-manager.js --detect
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📦 里面有什么
|
## 里面有什么
|
||||||
|
|
||||||
这个仓库是一个 **Claude Code 插件** - 直接安装或手动复制组件。
|
这个仓库是一个 **Claude Code 插件** - 直接安装或手动复制组件。
|
||||||
|
|
||||||
@@ -276,7 +276,7 @@ everything-claude-code/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠️ 生态系统工具
|
## 生态系统工具
|
||||||
|
|
||||||
### 技能创建器
|
### 技能创建器
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ everything-claude-code/
|
|||||||
- **直觉集合** - 用于 continuous-learning-v2
|
- **直觉集合** - 用于 continuous-learning-v2
|
||||||
- **模式提取** - 从你的提交历史中学习
|
- **模式提取** - 从你的提交历史中学习
|
||||||
|
|
||||||
### 🧠 持续学习 v2
|
### 持续学习 v2
|
||||||
|
|
||||||
基于直觉的学习系统自动学习你的模式:
|
基于直觉的学习系统自动学习你的模式:
|
||||||
|
|
||||||
@@ -328,7 +328,7 @@ everything-claude-code/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📥 安装
|
## 安装
|
||||||
|
|
||||||
### 选项 1:作为插件安装(推荐)
|
### 选项 1:作为插件安装(推荐)
|
||||||
|
|
||||||
@@ -387,7 +387,7 @@ everything-claude-code/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 🔧 选项 2:手动安装
|
### 选项 2:手动安装
|
||||||
|
|
||||||
如果你希望对安装的内容进行手动控制:
|
如果你希望对安装的内容进行手动控制:
|
||||||
|
|
||||||
@@ -425,7 +425,7 @@ cp -r everything-claude-code/skills/* ~/.claude/skills/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 关键概念
|
## 关键概念
|
||||||
|
|
||||||
### 代理
|
### 代理
|
||||||
|
|
||||||
@@ -485,7 +485,7 @@ model: opus
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧪 运行测试
|
## 运行测试
|
||||||
|
|
||||||
插件包含一个全面的测试套件:
|
插件包含一个全面的测试套件:
|
||||||
|
|
||||||
@@ -501,7 +501,7 @@ node tests/hooks/hooks.test.js
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🤝 贡献
|
## 贡献
|
||||||
|
|
||||||
**欢迎并鼓励贡献。**
|
**欢迎并鼓励贡献。**
|
||||||
|
|
||||||
@@ -523,7 +523,7 @@ node tests/hooks/hooks.test.js
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📖 背景
|
## 背景
|
||||||
|
|
||||||
自实验性推出以来,我一直在使用 Claude Code。2025 年 9 月,与 [@DRodriguezFX](https://x.com/DRodriguezFX) 一起使用 Claude Code 构建 [zenith.chat](https://zenith.chat),赢得了 Anthropic x Forum Ventures 黑客马拉松。
|
自实验性推出以来,我一直在使用 Claude Code。2025 年 9 月,与 [@DRodriguezFX](https://x.com/DRodriguezFX) 一起使用 Claude Code 构建 [zenith.chat](https://zenith.chat),赢得了 Anthropic x Forum Ventures 黑客马拉松。
|
||||||
|
|
||||||
@@ -531,7 +531,7 @@ node tests/hooks/hooks.test.js
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚠️ 重要说明
|
## WARNING: 重要说明
|
||||||
|
|
||||||
### 上下文窗口管理
|
### 上下文窗口管理
|
||||||
|
|
||||||
@@ -554,13 +554,13 @@ node tests/hooks/hooks.test.js
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🌟 Star 历史
|
## Star 历史
|
||||||
|
|
||||||
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
|
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔗 链接
|
## 链接
|
||||||
|
|
||||||
- **精简指南(从这里开始):** [The Shorthand Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2012378465664745795)
|
- **精简指南(从这里开始):** [The Shorthand Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2012378465664745795)
|
||||||
- **详细指南(高级):** [The Longform Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2014040193557471352)
|
- **详细指南(高级):** [The Longform Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2014040193557471352)
|
||||||
@@ -570,7 +570,7 @@ node tests/hooks/hooks.test.js
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📄 许可证
|
## 许可证
|
||||||
|
|
||||||
MIT - 自由使用,根据需要修改,如果可以请回馈。
|
MIT - 自由使用,根据需要修改,如果可以请回馈。
|
||||||
|
|
||||||
|
|||||||
38
RULES.md
Normal file
38
RULES.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Rules
|
||||||
|
|
||||||
|
## Must Always
|
||||||
|
- Delegate to specialized agents for domain tasks.
|
||||||
|
- Write tests before implementation and verify critical paths.
|
||||||
|
- Validate inputs and keep security checks intact.
|
||||||
|
- Prefer immutable updates over mutating shared state.
|
||||||
|
- Follow established repository patterns before inventing new ones.
|
||||||
|
- Keep contributions focused, reviewable, and well-described.
|
||||||
|
|
||||||
|
## Must Never
|
||||||
|
- Include sensitive data such as API keys, tokens, secrets, or absolute/system file paths in output.
|
||||||
|
- Submit untested changes.
|
||||||
|
- Bypass security checks or validation hooks.
|
||||||
|
- Duplicate existing functionality without a clear reason.
|
||||||
|
- Ship code without checking the relevant test suite.
|
||||||
|
|
||||||
|
## Agent Format
|
||||||
|
- Agents live in `agents/*.md`.
|
||||||
|
- Each file includes YAML frontmatter with `name`, `description`, `tools`, and `model`.
|
||||||
|
- File names are lowercase with hyphens and must match the agent name.
|
||||||
|
- Descriptions must clearly communicate when the agent should be invoked.
|
||||||
|
|
||||||
|
## Skill Format
|
||||||
|
- Skills live in `skills/<name>/SKILL.md`.
|
||||||
|
- Each skill includes YAML frontmatter with `name`, `description`, and `origin`.
|
||||||
|
- Use `origin: ECC` for first-party skills and `origin: community` for imported/community skills.
|
||||||
|
- Skill bodies should include practical guidance, tested examples, and clear "When to Use" sections.
|
||||||
|
|
||||||
|
## Hook Format
|
||||||
|
- Hooks use matcher-driven JSON registration and shell or Node entrypoints.
|
||||||
|
- Matchers should be specific instead of broad catch-alls.
|
||||||
|
- Exit `1` only when blocking behavior is intentional; otherwise exit `0`.
|
||||||
|
- Error and info messages should be actionable.
|
||||||
|
|
||||||
|
## Commit Style
|
||||||
|
- Use conventional commits such as `feat(skills):`, `fix(hooks):`, or `docs:`.
|
||||||
|
- Keep changes modular and explain user-facing impact in the PR summary.
|
||||||
17
SOUL.md
Normal file
17
SOUL.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Soul
|
||||||
|
|
||||||
|
## Core Identity
|
||||||
|
Everything Claude Code (ECC) is a production-ready AI coding plugin with 30 specialized agents, 135 skills, 60 commands, and automated hook workflows for software development.
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
1. **Agent-First** — route work to the right specialist as early as possible.
|
||||||
|
2. **Test-Driven** — write or refresh tests before trusting implementation changes.
|
||||||
|
3. **Security-First** — validate inputs, protect secrets, and keep safe defaults.
|
||||||
|
4. **Immutability** — prefer explicit state transitions over mutation.
|
||||||
|
5. **Plan Before Execute** — complex changes should be broken into deliberate phases.
|
||||||
|
|
||||||
|
## Agent Orchestration Philosophy
|
||||||
|
ECC is designed so specialists are invoked proactively: planners for implementation strategy, reviewers for code quality, security reviewers for sensitive code, and build resolvers when the toolchain breaks.
|
||||||
|
|
||||||
|
## Cross-Harness Vision
|
||||||
|
This gitagent surface is an initial portability layer for ECC's shared identity, governance, and skill catalog. Native agents, commands, and hooks remain authoritative in the repository until full manifest coverage is added.
|
||||||
154
agent.yaml
Normal file
154
agent.yaml
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
spec_version: "0.1.0"
|
||||||
|
name: everything-claude-code
|
||||||
|
version: 1.9.0
|
||||||
|
description: "Initial gitagent export surface for ECC's shared skill catalog, governance, and identity. Native agents, commands, and hooks remain authoritative in the repository while manifest coverage expands."
|
||||||
|
author: affaan-m
|
||||||
|
license: MIT
|
||||||
|
model:
|
||||||
|
preferred: claude-opus-4-6
|
||||||
|
fallback:
|
||||||
|
- claude-sonnet-4-6
|
||||||
|
skills:
|
||||||
|
- agent-eval
|
||||||
|
- agent-harness-construction
|
||||||
|
- agent-payment-x402
|
||||||
|
- agentic-engineering
|
||||||
|
- ai-first-engineering
|
||||||
|
- ai-regression-testing
|
||||||
|
- android-clean-architecture
|
||||||
|
- api-design
|
||||||
|
- architecture-decision-records
|
||||||
|
- article-writing
|
||||||
|
- autonomous-loops
|
||||||
|
- backend-patterns
|
||||||
|
- benchmark
|
||||||
|
- blueprint
|
||||||
|
- browser-qa
|
||||||
|
- bun-runtime
|
||||||
|
- canary-watch
|
||||||
|
- carrier-relationship-management
|
||||||
|
- ck
|
||||||
|
- claude-api
|
||||||
|
- claude-devfleet
|
||||||
|
- click-path-audit
|
||||||
|
- clickhouse-io
|
||||||
|
- codebase-onboarding
|
||||||
|
- coding-standards
|
||||||
|
- compose-multiplatform-patterns
|
||||||
|
- configure-ecc
|
||||||
|
- content-engine
|
||||||
|
- content-hash-cache-pattern
|
||||||
|
- context-budget
|
||||||
|
- continuous-agent-loop
|
||||||
|
- continuous-learning
|
||||||
|
- continuous-learning-v2
|
||||||
|
- cost-aware-llm-pipeline
|
||||||
|
- cpp-coding-standards
|
||||||
|
- cpp-testing
|
||||||
|
- crosspost
|
||||||
|
- customs-trade-compliance
|
||||||
|
- data-scraper-agent
|
||||||
|
- database-migrations
|
||||||
|
- deep-research
|
||||||
|
- deployment-patterns
|
||||||
|
- design-system
|
||||||
|
- django-patterns
|
||||||
|
- django-security
|
||||||
|
- django-tdd
|
||||||
|
- django-verification
|
||||||
|
- dmux-workflows
|
||||||
|
- docker-patterns
|
||||||
|
- documentation-lookup
|
||||||
|
- e2e-testing
|
||||||
|
- energy-procurement
|
||||||
|
- enterprise-agent-ops
|
||||||
|
- eval-harness
|
||||||
|
- exa-search
|
||||||
|
- fal-ai-media
|
||||||
|
- flutter-dart-code-review
|
||||||
|
- foundation-models-on-device
|
||||||
|
- frontend-patterns
|
||||||
|
- frontend-slides
|
||||||
|
- git-workflow
|
||||||
|
- golang-patterns
|
||||||
|
- golang-testing
|
||||||
|
- healthcare-cdss-patterns
|
||||||
|
- healthcare-emr-patterns
|
||||||
|
- healthcare-eval-harness
|
||||||
|
- healthcare-phi-compliance
|
||||||
|
- inventory-demand-planning
|
||||||
|
- investor-materials
|
||||||
|
- investor-outreach
|
||||||
|
- iterative-retrieval
|
||||||
|
- java-coding-standards
|
||||||
|
- jpa-patterns
|
||||||
|
- kotlin-coroutines-flows
|
||||||
|
- kotlin-exposed-patterns
|
||||||
|
- kotlin-ktor-patterns
|
||||||
|
- kotlin-patterns
|
||||||
|
- kotlin-testing
|
||||||
|
- laravel-patterns
|
||||||
|
- laravel-plugin-discovery
|
||||||
|
- laravel-security
|
||||||
|
- laravel-tdd
|
||||||
|
- laravel-verification
|
||||||
|
- liquid-glass-design
|
||||||
|
- logistics-exception-management
|
||||||
|
- market-research
|
||||||
|
- mcp-server-patterns
|
||||||
|
- nanoclaw-repl
|
||||||
|
- nextjs-turbopack
|
||||||
|
- nutrient-document-processing
|
||||||
|
- nuxt4-patterns
|
||||||
|
- perl-patterns
|
||||||
|
- perl-security
|
||||||
|
- perl-testing
|
||||||
|
- plankton-code-quality
|
||||||
|
- postgres-patterns
|
||||||
|
- product-lens
|
||||||
|
- production-scheduling
|
||||||
|
- project-guidelines-example
|
||||||
|
- prompt-optimizer
|
||||||
|
- python-patterns
|
||||||
|
- python-testing
|
||||||
|
- pytorch-patterns
|
||||||
|
- quality-nonconformance
|
||||||
|
- ralphinho-rfc-pipeline
|
||||||
|
- regex-vs-llm-structured-text
|
||||||
|
- repo-scan
|
||||||
|
- returns-reverse-logistics
|
||||||
|
- rules-distill
|
||||||
|
- rust-patterns
|
||||||
|
- rust-testing
|
||||||
|
- safety-guard
|
||||||
|
- santa-method
|
||||||
|
- search-first
|
||||||
|
- security-review
|
||||||
|
- security-scan
|
||||||
|
- skill-comply
|
||||||
|
- skill-stocktake
|
||||||
|
- springboot-patterns
|
||||||
|
- springboot-security
|
||||||
|
- springboot-tdd
|
||||||
|
- springboot-verification
|
||||||
|
- strategic-compact
|
||||||
|
- swift-actor-persistence
|
||||||
|
- swift-concurrency-6-2
|
||||||
|
- swift-protocol-di-testing
|
||||||
|
- swiftui-patterns
|
||||||
|
- tdd-workflow
|
||||||
|
- team-builder
|
||||||
|
- token-budget-advisor
|
||||||
|
- verification-loop
|
||||||
|
- video-editing
|
||||||
|
- videodb
|
||||||
|
- visa-doc-translate
|
||||||
|
- x-api
|
||||||
|
tags:
|
||||||
|
- agent-harness
|
||||||
|
- developer-tools
|
||||||
|
- code-review
|
||||||
|
- testing
|
||||||
|
- security
|
||||||
|
- cross-platform
|
||||||
|
- gitagent
|
||||||
@@ -228,7 +228,7 @@ const results = await batchFetch(['user1', 'user2', 'user3']);
|
|||||||
const fetchWithCache = async (url: string, ttl = 300000) => {
|
const fetchWithCache = async (url: string, ttl = 300000) => {
|
||||||
const cached = cache.get(url);
|
const cached = cache.get(url);
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
|
||||||
const data = await fetch(url).then(r => r.json());
|
const data = await fetch(url).then(r => r.json());
|
||||||
cache.set(url, data, ttl);
|
cache.set(url, data, ttl);
|
||||||
return data;
|
return data;
|
||||||
@@ -376,16 +376,16 @@ getTTFB(console.log); // Time to First Byte
|
|||||||
## Bundle Analysis
|
## Bundle Analysis
|
||||||
| Metric | Current | Target | Status |
|
| Metric | Current | Target | Status |
|
||||||
|--------|---------|--------|--------|
|
|--------|---------|--------|--------|
|
||||||
| Total Size (gzip) | XXX KB | < 200 KB | ⚠️ |
|
| Total Size (gzip) | XXX KB | < 200 KB | WARNING: |
|
||||||
| Main Bundle | XXX KB | < 100 KB | ✅ |
|
| Main Bundle | XXX KB | < 100 KB | PASS: |
|
||||||
| Vendor Bundle | XXX KB | < 150 KB | ⚠️ |
|
| Vendor Bundle | XXX KB | < 150 KB | WARNING: |
|
||||||
|
|
||||||
## Web Vitals
|
## Web Vitals
|
||||||
| Metric | Current | Target | Status |
|
| Metric | Current | Target | Status |
|
||||||
|--------|---------|--------|--------|
|
|--------|---------|--------|--------|
|
||||||
| LCP | X.Xs | < 2.5s | ✅ |
|
| LCP | X.Xs | < 2.5s | PASS: |
|
||||||
| FID | XXms | < 100ms | ✅ |
|
| FID | XXms | < 100ms | PASS: |
|
||||||
| CLS | X.XX | < 0.1 | ⚠️ |
|
| CLS | X.XX | < 0.1 | WARNING: |
|
||||||
|
|
||||||
## Critical Issues
|
## Critical Issues
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ Flag it clearly before resuming:
|
|||||||
```
|
```
|
||||||
ASIDE: [answer]
|
ASIDE: [answer]
|
||||||
|
|
||||||
⚠️ Note: This answer suggests [issue] with the current approach. Want to address this before continuing, or proceed as planned?
|
WARNING: Note: This answer suggests [issue] with the current approach. Want to address this before continuing, or proceed as planned?
|
||||||
```
|
```
|
||||||
Wait for the user's decision before resuming.
|
Wait for the user's decision before resuming.
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ Note the change needed but do not make it during the aside:
|
|||||||
```
|
```
|
||||||
ASIDE: [answer]
|
ASIDE: [answer]
|
||||||
|
|
||||||
📝 Worth fixing: [what should be changed]. I'll flag this after the current task unless you want to address it now.
|
Worth fixing: [what should be changed]. I'll flag this after the current task unless you want to address it now.
|
||||||
```
|
```
|
||||||
|
|
||||||
**Question is ambiguous or too vague:**
|
**Question is ambiguous or too vague:**
|
||||||
@@ -150,7 +150,7 @@ No — the shared cache object in src/cache/store.ts:34 is mutated without locki
|
|||||||
Under concurrent requests this is a race condition. It's low risk in a single-process
|
Under concurrent requests this is a race condition. It's low risk in a single-process
|
||||||
Node.js server but would be a real problem with worker threads or clustering.
|
Node.js server but would be a real problem with worker threads or clustering.
|
||||||
|
|
||||||
⚠️ Note: This could affect the feature we're building. Want to address this now or continue and fix it in a follow-up?
|
WARNING: Note: This could affect the feature we're building. Want to address this now or continue and fix it in a follow-up?
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Comprehensive security and quality review of uncommitted changes:
|
|||||||
**Security Issues (CRITICAL):**
|
**Security Issues (CRITICAL):**
|
||||||
- Hardcoded credentials, API keys, tokens
|
- Hardcoded credentials, API keys, tokens
|
||||||
- SQL injection vulnerabilities
|
- SQL injection vulnerabilities
|
||||||
- XSS vulnerabilities
|
- XSS vulnerabilities
|
||||||
- Missing input validation
|
- Missing input validation
|
||||||
- Insecure dependencies
|
- Insecure dependencies
|
||||||
- Path traversal risks
|
- Path traversal risks
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ All tests passed.
|
|||||||
| Files modified | 2 |
|
| Files modified | 2 |
|
||||||
| Remaining issues | 0 |
|
| Remaining issues | 0 |
|
||||||
|
|
||||||
Build Status: ✅ SUCCESS
|
Build Status: PASS: SUCCESS
|
||||||
```
|
```
|
||||||
|
|
||||||
## Common Errors Fixed
|
## Common Errors Fixed
|
||||||
|
|||||||
@@ -108,16 +108,16 @@ void processUser(const User& user) {
|
|||||||
- HIGH: 1
|
- HIGH: 1
|
||||||
- MEDIUM: 0
|
- MEDIUM: 0
|
||||||
|
|
||||||
Recommendation: ❌ Block merge until CRITICAL issue is fixed
|
Recommendation: FAIL: Block merge until CRITICAL issue is fixed
|
||||||
```
|
```
|
||||||
|
|
||||||
## Approval Criteria
|
## Approval Criteria
|
||||||
|
|
||||||
| Status | Condition |
|
| Status | Condition |
|
||||||
|--------|-----------|
|
|--------|-----------|
|
||||||
| ✅ Approve | No CRITICAL or HIGH issues |
|
| PASS: Approve | No CRITICAL or HIGH issues |
|
||||||
| ⚠️ Warning | Only MEDIUM issues (merge with caution) |
|
| WARNING: Warning | Only MEDIUM issues (merge with caution) |
|
||||||
| ❌ Block | CRITICAL or HIGH issues found |
|
| FAIL: Block | CRITICAL or HIGH issues found |
|
||||||
|
|
||||||
## Integration with Other Commands
|
## Integration with Other Commands
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ Artifacts generated:
|
|||||||
╔══════════════════════════════════════════════════════════════╗
|
╔══════════════════════════════════════════════════════════════╗
|
||||||
║ E2E Test Results ║
|
║ E2E Test Results ║
|
||||||
╠══════════════════════════════════════════════════════════════╣
|
╠══════════════════════════════════════════════════════════════╣
|
||||||
║ Status: ✅ ALL TESTS PASSED ║
|
║ Status: PASS: ALL TESTS PASSED ║
|
||||||
║ Total: 3 tests ║
|
║ Total: 3 tests ║
|
||||||
║ Passed: 3 (100%) ║
|
║ Passed: 3 (100%) ║
|
||||||
║ Failed: 0 ║
|
║ Failed: 0 ║
|
||||||
@@ -191,15 +191,15 @@ Artifacts generated:
|
|||||||
╚══════════════════════════════════════════════════════════════╝
|
╚══════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
Artifacts:
|
Artifacts:
|
||||||
📸 Screenshots: 2 files
|
Screenshots: 2 files
|
||||||
📹 Videos: 0 files (only on failure)
|
Videos: 0 files (only on failure)
|
||||||
🔍 Traces: 0 files (only on failure)
|
Traces: 0 files (only on failure)
|
||||||
📊 HTML Report: playwright-report/index.html
|
HTML Report: playwright-report/index.html
|
||||||
|
|
||||||
View report: npx playwright show-report
|
View report: npx playwright show-report
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ E2E test suite ready for CI/CD integration!
|
PASS: E2E test suite ready for CI/CD integration!
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test Artifacts
|
## Test Artifacts
|
||||||
@@ -235,7 +235,7 @@ open artifacts/search-results.png
|
|||||||
If a test fails intermittently:
|
If a test fails intermittently:
|
||||||
|
|
||||||
```
|
```
|
||||||
⚠️ FLAKY TEST DETECTED: tests/e2e/markets/trade.spec.ts
|
WARNING: FLAKY TEST DETECTED: tests/e2e/markets/trade.spec.ts
|
||||||
|
|
||||||
Test passed 7/10 runs (70% pass rate)
|
Test passed 7/10 runs (70% pass rate)
|
||||||
|
|
||||||
@@ -254,10 +254,10 @@ Quarantine recommendation: Mark as test.fixme() until fixed
|
|||||||
## Browser Configuration
|
## Browser Configuration
|
||||||
|
|
||||||
Tests run on multiple browsers by default:
|
Tests run on multiple browsers by default:
|
||||||
- ✅ Chromium (Desktop Chrome)
|
- PASS: Chromium (Desktop Chrome)
|
||||||
- ✅ Firefox (Desktop)
|
- PASS: Firefox (Desktop)
|
||||||
- ✅ WebKit (Desktop Safari)
|
- PASS: WebKit (Desktop Safari)
|
||||||
- ✅ Mobile Chrome (optional)
|
- PASS: Mobile Chrome (optional)
|
||||||
|
|
||||||
Configure in `playwright.config.ts` to adjust browsers.
|
Configure in `playwright.config.ts` to adjust browsers.
|
||||||
|
|
||||||
@@ -285,7 +285,7 @@ Add to your CI pipeline:
|
|||||||
|
|
||||||
For PMX, prioritize these E2E tests:
|
For PMX, prioritize these E2E tests:
|
||||||
|
|
||||||
**🔴 CRITICAL (Must Always Pass):**
|
**CRITICAL (Must Always Pass):**
|
||||||
1. User can connect wallet
|
1. User can connect wallet
|
||||||
2. User can browse markets
|
2. User can browse markets
|
||||||
3. User can search markets (semantic search)
|
3. User can search markets (semantic search)
|
||||||
@@ -294,7 +294,7 @@ For PMX, prioritize these E2E tests:
|
|||||||
6. Market resolves correctly
|
6. Market resolves correctly
|
||||||
7. User can withdraw funds
|
7. User can withdraw funds
|
||||||
|
|
||||||
**🟡 IMPORTANT:**
|
**IMPORTANT:**
|
||||||
1. Market creation flow
|
1. Market creation flow
|
||||||
2. User profile updates
|
2. User profile updates
|
||||||
3. Real-time price updates
|
3. Real-time price updates
|
||||||
@@ -305,20 +305,20 @@ For PMX, prioritize these E2E tests:
|
|||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
**DO:**
|
**DO:**
|
||||||
- ✅ Use Page Object Model for maintainability
|
- PASS: Use Page Object Model for maintainability
|
||||||
- ✅ Use data-testid attributes for selectors
|
- PASS: Use data-testid attributes for selectors
|
||||||
- ✅ Wait for API responses, not arbitrary timeouts
|
- PASS: Wait for API responses, not arbitrary timeouts
|
||||||
- ✅ Test critical user journeys end-to-end
|
- PASS: Test critical user journeys end-to-end
|
||||||
- ✅ Run tests before merging to main
|
- PASS: Run tests before merging to main
|
||||||
- ✅ Review artifacts when tests fail
|
- PASS: Review artifacts when tests fail
|
||||||
|
|
||||||
**DON'T:**
|
**DON'T:**
|
||||||
- ❌ Use brittle selectors (CSS classes can change)
|
- FAIL: Use brittle selectors (CSS classes can change)
|
||||||
- ❌ Test implementation details
|
- FAIL: Test implementation details
|
||||||
- ❌ Run tests against production
|
- FAIL: Run tests against production
|
||||||
- ❌ Ignore flaky tests
|
- FAIL: Ignore flaky tests
|
||||||
- ❌ Skip artifact review on failures
|
- FAIL: Skip artifact review on failures
|
||||||
- ❌ Test every edge case with E2E (use unit tests)
|
- FAIL: Test every edge case with E2E (use unit tests)
|
||||||
|
|
||||||
## Important Notes
|
## Important Notes
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ ok project/internal/handler 0.023s
|
|||||||
| Files modified | 2 |
|
| Files modified | 2 |
|
||||||
| Remaining issues | 0 |
|
| Remaining issues | 0 |
|
||||||
|
|
||||||
Build Status: ✅ SUCCESS
|
Build Status: PASS: SUCCESS
|
||||||
```
|
```
|
||||||
|
|
||||||
## Common Errors Fixed
|
## Common Errors Fixed
|
||||||
|
|||||||
@@ -124,16 +124,16 @@ return fmt.Errorf("get user %s: %w", userID, err)
|
|||||||
- HIGH: 1
|
- HIGH: 1
|
||||||
- MEDIUM: 0
|
- MEDIUM: 0
|
||||||
|
|
||||||
Recommendation: ❌ Block merge until CRITICAL issue is fixed
|
Recommendation: FAIL: Block merge until CRITICAL issue is fixed
|
||||||
```
|
```
|
||||||
|
|
||||||
## Approval Criteria
|
## Approval Criteria
|
||||||
|
|
||||||
| Status | Condition |
|
| Status | Condition |
|
||||||
|--------|-----------|
|
|--------|-----------|
|
||||||
| ✅ Approve | No CRITICAL or HIGH issues |
|
| PASS: Approve | No CRITICAL or HIGH issues |
|
||||||
| ⚠️ Warning | Only MEDIUM issues (merge with caution) |
|
| WARNING: Warning | Only MEDIUM issues (merge with caution) |
|
||||||
| ❌ Block | CRITICAL or HIGH issues found |
|
| FAIL: Block | CRITICAL or HIGH issues found |
|
||||||
|
|
||||||
## Integration with Other Commands
|
## Integration with Other Commands
|
||||||
|
|
||||||
|
|||||||
@@ -4,22 +4,23 @@ Run a deterministic repository harness audit and return a prioritized scorecard.
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
`/harness-audit [scope] [--format text|json]`
|
`/harness-audit [scope] [--format text|json] [--root path]`
|
||||||
|
|
||||||
- `scope` (optional): `repo` (default), `hooks`, `skills`, `commands`, `agents`
|
- `scope` (optional): `repo` (default), `hooks`, `skills`, `commands`, `agents`
|
||||||
- `--format`: output style (`text` default, `json` for automation)
|
- `--format`: output style (`text` default, `json` for automation)
|
||||||
|
- `--root`: audit a specific path instead of the current working directory
|
||||||
|
|
||||||
## Deterministic Engine
|
## Deterministic Engine
|
||||||
|
|
||||||
Always run:
|
Always run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node scripts/harness-audit.js <scope> --format <text|json>
|
node scripts/harness-audit.js <scope> --format <text|json> [--root <path>]
|
||||||
```
|
```
|
||||||
|
|
||||||
This script is the source of truth for scoring and checks. Do not invent additional dimensions or ad-hoc points.
|
This script is the source of truth for scoring and checks. Do not invent additional dimensions or ad-hoc points.
|
||||||
|
|
||||||
Rubric version: `2026-03-16`.
|
Rubric version: `2026-03-30`.
|
||||||
|
|
||||||
The script computes 7 fixed categories (`0-10` normalized each):
|
The script computes 7 fixed categories (`0-10` normalized each):
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ The script computes 7 fixed categories (`0-10` normalized each):
|
|||||||
7. Cost Efficiency
|
7. Cost Efficiency
|
||||||
|
|
||||||
Scores are derived from explicit file/rule checks and are reproducible for the same commit.
|
Scores are derived from explicit file/rule checks and are reproducible for the same commit.
|
||||||
|
The script audits the current working directory by default and auto-detects whether the target is the ECC repo itself or a consumer project using ECC.
|
||||||
|
|
||||||
## Output Contract
|
## Output Contract
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ Import instincts from local file paths or HTTP(S) URLs.
|
|||||||
## Import Process
|
## Import Process
|
||||||
|
|
||||||
```
|
```
|
||||||
📥 Importing instincts from: team-instincts.yaml
|
Importing instincts from: team-instincts.yaml
|
||||||
================================================
|
================================================
|
||||||
|
|
||||||
Found 12 instincts to import.
|
Found 12 instincts to import.
|
||||||
@@ -60,12 +60,12 @@ These will be added:
|
|||||||
|
|
||||||
## Duplicate Instincts (3)
|
## Duplicate Instincts (3)
|
||||||
Already have similar instincts:
|
Already have similar instincts:
|
||||||
⚠️ prefer-functional-style
|
WARNING: prefer-functional-style
|
||||||
Local: 0.8 confidence, 12 observations
|
Local: 0.8 confidence, 12 observations
|
||||||
Import: 0.7 confidence
|
Import: 0.7 confidence
|
||||||
→ Keep local (higher confidence)
|
→ Keep local (higher confidence)
|
||||||
|
|
||||||
⚠️ test-first-workflow
|
WARNING: test-first-workflow
|
||||||
Local: 0.75 confidence
|
Local: 0.75 confidence
|
||||||
Import: 0.9 confidence
|
Import: 0.9 confidence
|
||||||
→ Update to import (higher confidence)
|
→ Update to import (higher confidence)
|
||||||
@@ -102,7 +102,7 @@ project_name: "my-project"
|
|||||||
|
|
||||||
After import:
|
After import:
|
||||||
```
|
```
|
||||||
✅ Import complete!
|
PASS: Import complete!
|
||||||
|
|
||||||
Added: 8 instincts
|
Added: 8 instincts
|
||||||
Updated: 1 instinct
|
Updated: 1 instinct
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ $ ./gradlew test
|
|||||||
| Files modified | 2 |
|
| Files modified | 2 |
|
||||||
| Remaining issues | 0 |
|
| Remaining issues | 0 |
|
||||||
|
|
||||||
Build Status: ✅ SUCCESS
|
Build Status: PASS: SUCCESS
|
||||||
````
|
````
|
||||||
|
|
||||||
## Common Errors Fixed
|
## Common Errors Fixed
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ Agent:
|
|||||||
## Static Analysis Results
|
## Static Analysis Results
|
||||||
✓ Build: Successful
|
✓ Build: Successful
|
||||||
✓ detekt: No issues
|
✓ detekt: No issues
|
||||||
⚠ ktlint: 2 formatting warnings
|
WARNING: ktlint: 2 formatting warnings
|
||||||
|
|
||||||
## Issues Found
|
## Issues Found
|
||||||
|
|
||||||
@@ -116,16 +116,16 @@ launch {
|
|||||||
- HIGH: 1
|
- HIGH: 1
|
||||||
- MEDIUM: 0
|
- MEDIUM: 0
|
||||||
|
|
||||||
Recommendation: ❌ Block merge until CRITICAL issue is fixed
|
Recommendation: FAIL: Block merge until CRITICAL issue is fixed
|
||||||
````
|
````
|
||||||
|
|
||||||
## Approval Criteria
|
## Approval Criteria
|
||||||
|
|
||||||
| Status | Condition |
|
| Status | Condition |
|
||||||
|--------|-----------|
|
|--------|-----------|
|
||||||
| ✅ Approve | No CRITICAL or HIGH issues |
|
| PASS: Approve | No CRITICAL or HIGH issues |
|
||||||
| ⚠️ Warning | Only MEDIUM issues (merge with caution) |
|
| WARNING: Warning | Only MEDIUM issues (merge with caution) |
|
||||||
| ❌ Block | CRITICAL or HIGH issues found |
|
| FAIL: Block | CRITICAL or HIGH issues found |
|
||||||
|
|
||||||
## Integration with Other Commands
|
## Integration with Other Commands
|
||||||
|
|
||||||
|
|||||||
@@ -73,19 +73,19 @@ origin: auto-extracted
|
|||||||
| **Absorb into [X]** | Should be appended to an existing skill | Show target skill and additions → Step 6 |
|
| **Absorb into [X]** | Should be appended to an existing skill | Show target skill and additions → Step 6 |
|
||||||
| **Drop** | Trivial, redundant, or too abstract | Explain reasoning and stop |
|
| **Drop** | Trivial, redundant, or too abstract | Explain reasoning and stop |
|
||||||
|
|
||||||
**Guideline dimensions** (informing the verdict, not scored):
|
**Guideline dimensions** (informing the verdict, not scored):
|
||||||
|
|
||||||
- **Specificity & Actionability**: Contains code examples or commands that are immediately usable
|
- **Specificity & Actionability**: Contains code examples or commands that are immediately usable
|
||||||
- **Scope Fit**: Name, trigger conditions, and content are aligned and focused on a single pattern
|
- **Scope Fit**: Name, trigger conditions, and content are aligned and focused on a single pattern
|
||||||
- **Uniqueness**: Provides value not covered by existing skills (informed by checklist results)
|
- **Uniqueness**: Provides value not covered by existing skills (informed by checklist results)
|
||||||
- **Reusability**: Realistic trigger scenarios exist in future sessions
|
- **Reusability**: Realistic trigger scenarios exist in future sessions
|
||||||
|
|
||||||
6. **Verdict-specific confirmation flow**
|
6. **Verdict-specific confirmation flow**
|
||||||
|
|
||||||
- **Improve then Save**: Present the required improvements + revised draft + updated checklist/verdict after one re-evaluation; if the revised verdict is **Save**, save after user confirmation, otherwise follow the new verdict
|
- **Improve then Save**: Present the required improvements + revised draft + updated checklist/verdict after one re-evaluation; if the revised verdict is **Save**, save after user confirmation, otherwise follow the new verdict
|
||||||
- **Save**: Present save path + checklist results + 1-line verdict rationale + full draft → save after user confirmation
|
- **Save**: Present save path + checklist results + 1-line verdict rationale + full draft → save after user confirmation
|
||||||
- **Absorb into [X]**: Present target path + additions (diff format) + checklist results + verdict rationale → append after user confirmation
|
- **Absorb into [X]**: Present target path + additions (diff format) + checklist results + verdict rationale → append after user confirmation
|
||||||
- **Drop**: Show checklist results + reasoning only (no confirmation needed)
|
- **Drop**: Show checklist results + reasoning only (no confirmation needed)
|
||||||
|
|
||||||
7. Save / Absorb to the determined location
|
7. Save / Absorb to the determined location
|
||||||
|
|
||||||
|
|||||||
@@ -203,19 +203,19 @@ Synthesize both analyses, generate **Step-by-step Implementation Plan**:
|
|||||||
2. Save plan to `.claude/plan/<feature-name>.md` (extract feature name from requirement, e.g., `user-auth`, `payment-module`)
|
2. Save plan to `.claude/plan/<feature-name>.md` (extract feature name from requirement, e.g., `user-auth`, `payment-module`)
|
||||||
3. Output prompt in **bold text** (MUST use actual saved file path):
|
3. Output prompt in **bold text** (MUST use actual saved file path):
|
||||||
|
|
||||||
---
|
---
|
||||||
**Plan generated and saved to `.claude/plan/actual-feature-name.md`**
|
**Plan generated and saved to `.claude/plan/actual-feature-name.md`**
|
||||||
|
|
||||||
**Please review the plan above. You can:**
|
**Please review the plan above. You can:**
|
||||||
- **Modify plan**: Tell me what needs adjustment, I'll update the plan
|
- **Modify plan**: Tell me what needs adjustment, I'll update the plan
|
||||||
- **Execute plan**: Copy the following command to a new session
|
- **Execute plan**: Copy the following command to a new session
|
||||||
|
|
||||||
```
|
```
|
||||||
/ccg:execute .claude/plan/actual-feature-name.md
|
/ccg:execute .claude/plan/actual-feature-name.md
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
|
|
||||||
**NOTE**: The `actual-feature-name.md` above MUST be replaced with the actual saved filename!
|
**NOTE**: The `actual-feature-name.md` above MUST be replaced with the actual saved filename!
|
||||||
|
|
||||||
4. **Immediately terminate current response** (Stop here. No more tool calls.)
|
4. **Immediately terminate current response** (Stop here. No more tool calls.)
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ Agent:
|
|||||||
## Static Analysis Results
|
## Static Analysis Results
|
||||||
✓ ruff: No issues
|
✓ ruff: No issues
|
||||||
✓ mypy: No errors
|
✓ mypy: No errors
|
||||||
⚠️ black: 2 files need reformatting
|
WARNING: black: 2 files need reformatting
|
||||||
✓ bandit: No security issues
|
✓ bandit: No security issues
|
||||||
|
|
||||||
## Issues Found
|
## Issues Found
|
||||||
@@ -155,7 +155,7 @@ with open("config.json") as f: # Good
|
|||||||
- HIGH: 1
|
- HIGH: 1
|
||||||
- MEDIUM: 2
|
- MEDIUM: 2
|
||||||
|
|
||||||
Recommendation: ❌ Block merge until CRITICAL issue is fixed
|
Recommendation: FAIL: Block merge until CRITICAL issue is fixed
|
||||||
|
|
||||||
## Formatting Required
|
## Formatting Required
|
||||||
Run: `black app/routes/user.py app/services/auth.py`
|
Run: `black app/routes/user.py app/services/auth.py`
|
||||||
@@ -165,9 +165,9 @@ Run: `black app/routes/user.py app/services/auth.py`
|
|||||||
|
|
||||||
| Status | Condition |
|
| Status | Condition |
|
||||||
|--------|-----------|
|
|--------|-----------|
|
||||||
| ✅ Approve | No CRITICAL or HIGH issues |
|
| PASS: Approve | No CRITICAL or HIGH issues |
|
||||||
| ⚠️ Warning | Only MEDIUM issues (merge with caution) |
|
| WARNING: Warning | Only MEDIUM issues (merge with caution) |
|
||||||
| ❌ Block | CRITICAL or HIGH issues found |
|
| FAIL: Block | CRITICAL or HIGH issues found |
|
||||||
|
|
||||||
## Integration with Other Commands
|
## Integration with Other Commands
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ Deleted: 12 unused functions
|
|||||||
Skipped: 2 items (tests failed)
|
Skipped: 2 items (tests failed)
|
||||||
Saved: ~450 lines removed
|
Saved: ~450 lines removed
|
||||||
──────────────────────────────
|
──────────────────────────────
|
||||||
All tests passing ✅
|
All tests passing PASS:
|
||||||
```
|
```
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ WHAT WE'RE BUILDING:
|
|||||||
[2-3 sentence summary in your own words]
|
[2-3 sentence summary in your own words]
|
||||||
|
|
||||||
CURRENT STATE:
|
CURRENT STATE:
|
||||||
✅ Working: [count] items confirmed
|
PASS: Working: [count] items confirmed
|
||||||
🔄 In Progress: [list files that are in progress]
|
In Progress: [list files that are in progress]
|
||||||
🗒️ Not Started: [list planned but untouched]
|
Not Started: [list planned but untouched]
|
||||||
|
|
||||||
WHAT NOT TO RETRY:
|
WHAT NOT TO RETRY:
|
||||||
[list every failed approach with its reason — this is critical]
|
[list every failed approach with its reason — this is critical]
|
||||||
@@ -99,10 +99,10 @@ If no next step is defined — ask the user where to start, and optionally sugge
|
|||||||
Load the most recently modified matching file for that date, regardless of whether it uses the legacy no-id format or the current short-id format.
|
Load the most recently modified matching file for that date, regardless of whether it uses the legacy no-id format or the current short-id format.
|
||||||
|
|
||||||
**Session file references files that no longer exist:**
|
**Session file references files that no longer exist:**
|
||||||
Note this during the briefing — "⚠️ `path/to/file.ts` referenced in session but not found on disk."
|
Note this during the briefing — "WARNING: `path/to/file.ts` referenced in session but not found on disk."
|
||||||
|
|
||||||
**Session file is from more than 7 days ago:**
|
**Session file is from more than 7 days ago:**
|
||||||
Note the gap — "⚠️ This session is from N days ago (threshold: 7 days). Things may have changed." — then proceed normally.
|
Note the gap — "WARNING: This session is from N days ago (threshold: 7 days). Things may have changed." — then proceed normally.
|
||||||
|
|
||||||
**User provides a file path directly (e.g., forwarded from a teammate):**
|
**User provides a file path directly (e.g., forwarded from a teammate):**
|
||||||
Read it and follow the same briefing process — the format is the same regardless of source.
|
Read it and follow the same briefing process — the format is the same regardless of source.
|
||||||
@@ -126,13 +126,13 @@ Register and login endpoints are partially done. Route protection
|
|||||||
via middleware hasn't been started yet.
|
via middleware hasn't been started yet.
|
||||||
|
|
||||||
CURRENT STATE:
|
CURRENT STATE:
|
||||||
✅ Working: 3 items (register endpoint, JWT generation, password hashing)
|
PASS: Working: 3 items (register endpoint, JWT generation, password hashing)
|
||||||
🔄 In Progress: app/api/auth/login/route.ts (token works, cookie not set yet)
|
In Progress: app/api/auth/login/route.ts (token works, cookie not set yet)
|
||||||
🗒️ Not Started: middleware.ts, app/login/page.tsx
|
Not Started: middleware.ts, app/login/page.tsx
|
||||||
|
|
||||||
WHAT NOT TO RETRY:
|
WHAT NOT TO RETRY:
|
||||||
❌ Next-Auth — conflicts with custom Prisma adapter, threw adapter error on every request
|
FAIL: Next-Auth — conflicts with custom Prisma adapter, threw adapter error on every request
|
||||||
❌ localStorage for JWT — causes SSR hydration mismatch, incompatible with Next.js
|
FAIL: localStorage for JWT — causes SSR hydration mismatch, incompatible with Next.js
|
||||||
|
|
||||||
OPEN QUESTIONS / BLOCKERS:
|
OPEN QUESTIONS / BLOCKERS:
|
||||||
- Does cookies().set() work inside a Route Handler or only Server Actions?
|
- Does cookies().set() work inside a Route Handler or only Server Actions?
|
||||||
|
|||||||
@@ -130,10 +130,10 @@ If nothing is queued: "No specific untried approaches identified."
|
|||||||
|
|
||||||
| File | Status | Notes |
|
| File | Status | Notes |
|
||||||
| ----------------- | -------------- | -------------------------- |
|
| ----------------- | -------------- | -------------------------- |
|
||||||
| `path/to/file.ts` | ✅ Complete | [what it does] |
|
| `path/to/file.ts` | PASS: Complete | [what it does] |
|
||||||
| `path/to/file.ts` | 🔄 In Progress | [what's done, what's left] |
|
| `path/to/file.ts` | In Progress | [what's done, what's left] |
|
||||||
| `path/to/file.ts` | ❌ Broken | [what's wrong] |
|
| `path/to/file.ts` | FAIL: Broken | [what's wrong] |
|
||||||
| `path/to/file.ts` | 🗒️ Not Started | [planned but not touched] |
|
| `path/to/file.ts` | Not Started | [planned but not touched] |
|
||||||
|
|
||||||
If no files were touched: "No files modified this session."
|
If no files were touched: "No files modified this session."
|
||||||
|
|
||||||
@@ -235,11 +235,11 @@ refreshes without exposing the token to JavaScript.
|
|||||||
|
|
||||||
| File | Status | Notes |
|
| File | Status | Notes |
|
||||||
| -------------------------------- | -------------- | ----------------------------------------------- |
|
| -------------------------------- | -------------- | ----------------------------------------------- |
|
||||||
| `app/api/auth/register/route.ts` | ✅ Complete | Works, tested |
|
| `app/api/auth/register/route.ts` | PASS: Complete | Works, tested |
|
||||||
| `app/api/auth/login/route.ts` | 🔄 In Progress | Token generates but not setting cookie yet |
|
| `app/api/auth/login/route.ts` | In Progress | Token generates but not setting cookie yet |
|
||||||
| `lib/auth.ts` | ✅ Complete | JWT helpers, all tested |
|
| `lib/auth.ts` | PASS: Complete | JWT helpers, all tested |
|
||||||
| `middleware.ts` | 🗒️ Not Started | Route protection, needs cookie read logic first |
|
| `middleware.ts` | Not Started | Route protection, needs cookie read logic first |
|
||||||
| `app/login/page.tsx` | 🗒️ Not Started | UI not started |
|
| `app/login/page.tsx` | Not Started | UI not started |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ FAIL lib/liquidity.test.ts
|
|||||||
1 test failed, 0 passed
|
1 test failed, 0 passed
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ Tests fail as expected. Ready to implement.
|
PASS: Tests fail as expected. Ready to implement.
|
||||||
|
|
||||||
## Step 4: Implement Minimal Code (GREEN)
|
## Step 4: Implement Minimal Code (GREEN)
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ PASS lib/liquidity.test.ts
|
|||||||
3 tests passed
|
3 tests passed
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ All tests passing!
|
PASS: All tests passing!
|
||||||
|
|
||||||
## Step 6: Refactor (IMPROVE)
|
## Step 6: Refactor (IMPROVE)
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ PASS lib/liquidity.test.ts
|
|||||||
3 tests passed
|
3 tests passed
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ Refactoring complete, tests still passing!
|
PASS: Refactoring complete, tests still passing!
|
||||||
|
|
||||||
## Step 8: Check Coverage
|
## Step 8: Check Coverage
|
||||||
|
|
||||||
@@ -247,29 +247,29 @@ File | % Stmts | % Branch | % Funcs | % Lines
|
|||||||
---------------|---------|----------|---------|--------
|
---------------|---------|----------|---------|--------
|
||||||
liquidity.ts | 100 | 100 | 100 | 100
|
liquidity.ts | 100 | 100 | 100 | 100
|
||||||
|
|
||||||
Coverage: 100% ✅ (Target: 80%)
|
Coverage: 100% PASS: (Target: 80%)
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ TDD session complete!
|
PASS: TDD session complete!
|
||||||
```
|
```
|
||||||
|
|
||||||
## TDD Best Practices
|
## TDD Best Practices
|
||||||
|
|
||||||
**DO:**
|
**DO:**
|
||||||
- ✅ Write the test FIRST, before any implementation
|
- PASS: Write the test FIRST, before any implementation
|
||||||
- ✅ Run tests and verify they FAIL before implementing
|
- PASS: Run tests and verify they FAIL before implementing
|
||||||
- ✅ Write minimal code to make tests pass
|
- PASS: Write minimal code to make tests pass
|
||||||
- ✅ Refactor only after tests are green
|
- PASS: Refactor only after tests are green
|
||||||
- ✅ Add edge cases and error scenarios
|
- PASS: Add edge cases and error scenarios
|
||||||
- ✅ Aim for 80%+ coverage (100% for critical code)
|
- PASS: Aim for 80%+ coverage (100% for critical code)
|
||||||
|
|
||||||
**DON'T:**
|
**DON'T:**
|
||||||
- ❌ Write implementation before tests
|
- FAIL: Write implementation before tests
|
||||||
- ❌ Skip running tests after each change
|
- FAIL: Skip running tests after each change
|
||||||
- ❌ Write too much code at once
|
- FAIL: Write too much code at once
|
||||||
- ❌ Ignore failing tests
|
- FAIL: Ignore failing tests
|
||||||
- ❌ Test implementation details (test behavior)
|
- FAIL: Test implementation details (test behavior)
|
||||||
- ❌ Mock everything (prefer integration tests)
|
- FAIL: Mock everything (prefer integration tests)
|
||||||
|
|
||||||
## Test Types to Include
|
## Test Types to Include
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ File Before After
|
|||||||
src/services/auth.ts 45% 88%
|
src/services/auth.ts 45% 88%
|
||||||
src/utils/validation.ts 32% 82%
|
src/utils/validation.ts 32% 82%
|
||||||
──────────────────────────────
|
──────────────────────────────
|
||||||
Overall: 67% 84% ✅
|
Overall: 67% 84% PASS:
|
||||||
```
|
```
|
||||||
|
|
||||||
## Focus Areas
|
## Focus Areas
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ Link to complementary skills.
|
|||||||
|
|
||||||
Good skills are **focused and actionable**:
|
Good skills are **focused and actionable**:
|
||||||
|
|
||||||
| ✅ Good Focus | ❌ Too Broad |
|
| PASS: Good Focus | FAIL: Too Broad |
|
||||||
|---------------|--------------|
|
|---------------|--------------|
|
||||||
| `react-hook-patterns` | `react` |
|
| `react-hook-patterns` | `react` |
|
||||||
| `postgresql-indexing` | `databases` |
|
| `postgresql-indexing` | `databases` |
|
||||||
@@ -186,11 +186,11 @@ Another pattern with code.
|
|||||||
|
|
||||||
Write content that Claude can **immediately use**:
|
Write content that Claude can **immediately use**:
|
||||||
|
|
||||||
- ✅ Copy-pasteable code examples
|
- PASS: Copy-pasteable code examples
|
||||||
- ✅ Clear decision trees
|
- PASS: Clear decision trees
|
||||||
- ✅ Checklists for verification
|
- PASS: Checklists for verification
|
||||||
- ❌ Vague explanations without examples
|
- FAIL: Vague explanations without examples
|
||||||
- ❌ Long prose without actionable guidance
|
- FAIL: Long prose without actionable guidance
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -334,11 +334,11 @@ Good:
|
|||||||
async function fetchData(url: string) {
|
async function fetchData(url: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`)
|
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return await response.json()
|
return await response.json()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fetch failed:', error)
|
console.error('Fetch failed:', error)
|
||||||
@@ -361,7 +361,7 @@ Show what NOT to do:
|
|||||||
```markdown
|
```markdown
|
||||||
## Anti-Patterns
|
## Anti-Patterns
|
||||||
|
|
||||||
### ❌ Direct State Mutation
|
### FAIL: Direct State Mutation
|
||||||
|
|
||||||
\`\`\`typescript
|
\`\`\`typescript
|
||||||
// NEVER do this
|
// NEVER do this
|
||||||
@@ -369,7 +369,7 @@ user.name = 'New Name'
|
|||||||
items.push(newItem)
|
items.push(newItem)
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
### ✅ Immutable Updates
|
### PASS: Immutable Updates
|
||||||
|
|
||||||
\`\`\`typescript
|
\`\`\`typescript
|
||||||
// ALWAYS do this
|
// ALWAYS do this
|
||||||
@@ -583,7 +583,7 @@ description: Quick reference for [API/Library].
|
|||||||
2. **Test with Claude Code**:
|
2. **Test with Claude Code**:
|
||||||
```
|
```
|
||||||
You: "I need to [task that should trigger your skill]"
|
You: "I need to [task that should trigger your skill]"
|
||||||
|
|
||||||
Claude should reference your skill's patterns.
|
Claude should reference your skill's patterns.
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -729,12 +729,12 @@ origin: ECC
|
|||||||
### Borrowing Rules
|
### Borrowing Rules
|
||||||
|
|
||||||
\`\`\`rust
|
\`\`\`rust
|
||||||
// ✅ CORRECT: Borrow when you don't need ownership
|
// PASS: CORRECT: Borrow when you don't need ownership
|
||||||
fn process_data(data: &str) -> usize {
|
fn process_data(data: &str) -> usize {
|
||||||
data.len()
|
data.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ CORRECT: Take ownership when you need to modify or consume
|
// PASS: CORRECT: Take ownership when you need to modify or consume
|
||||||
fn consume_data(data: Vec<u8>) -> String {
|
fn consume_data(data: Vec<u8>) -> String {
|
||||||
String::from_utf8(data).unwrap()
|
String::from_utf8(data).unwrap()
|
||||||
}
|
}
|
||||||
@@ -751,7 +751,7 @@ use thiserror::Error;
|
|||||||
pub enum AppError {
|
pub enum AppError {
|
||||||
#[error("IO error: {0}")]
|
#[error("IO error: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error("Parse error: {0}")]
|
#[error("Parse error: {0}")]
|
||||||
Parse(#[from] std::num::ParseIntError),
|
Parse(#[from] std::num::ParseIntError),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ Use these as starting points in negotiation:
|
|||||||
|
|
||||||
Use this on calls:
|
Use this on calls:
|
||||||
|
|
||||||
> ECC is now positioned as an agent harness performance system, not a config repo.
|
> ECC is now positioned as an agent harness performance system, not a config repo.
|
||||||
> We track adoption through npm distribution, GitHub App installs, and repository growth.
|
> We track adoption through npm distribution, GitHub App installs, and repository growth.
|
||||||
> Claude plugin installs are structurally undercounted publicly, so we use a blended metrics model.
|
> Claude plugin installs are structurally undercounted publicly, so we use a blended metrics model.
|
||||||
> The project supports Claude Code, Cursor, OpenCode, and Codex app/CLI with production-grade hook reliability and a large passing test suite.
|
> The project supports Claude Code, Cursor, OpenCode, and Codex app/CLI with production-grade hook reliability and a large passing test suite.
|
||||||
|
|
||||||
For launch-ready social copy snippets, see [`social-launch-copy.md`](./social-launch-copy.md).
|
For launch-ready social copy snippets, see [`social-launch-copy.md`](./social-launch-copy.md).
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
**🌐 言語 / Language / 語言**
|
**言語 / Language / 語言**
|
||||||
|
|
||||||
[**English**](README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md)
|
[**English**](README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md)
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 クイックスタート
|
## クイックスタート
|
||||||
|
|
||||||
2分以内に起動できます:
|
2分以内に起動できます:
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
|
|
||||||
### ステップ2:ルールをインストール(必須)
|
### ステップ2:ルールをインストール(必須)
|
||||||
|
|
||||||
> ⚠️ **重要:** Claude Codeプラグインは`rules`を自動配布できません。手動でインストールしてください:
|
> WARNING: **重要:** Claude Codeプラグインは`rules`を自動配布できません。手動でインストールしてください:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# まずリポジトリをクローン
|
# まずリポジトリをクローン
|
||||||
@@ -143,11 +143,11 @@ cp -r everything-claude-code/rules/golang/* ~/.claude/rules/
|
|||||||
/plugin list everything-claude-code@everything-claude-code
|
/plugin list everything-claude-code@everything-claude-code
|
||||||
```
|
```
|
||||||
|
|
||||||
✨ **完了です!** これで13のエージェント、43のスキル、31のコマンドにアクセスできます。
|
**完了です!** これで13のエージェント、43のスキル、31のコマンドにアクセスできます。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🌐 クロスプラットフォーム対応
|
## クロスプラットフォーム対応
|
||||||
|
|
||||||
このプラグインは **Windows、macOS、Linux** を完全にサポートしています。すべてのフックとスクリプトが Node.js で書き直され、最大の互換性を実現しています。
|
このプラグインは **Windows、macOS、Linux** を完全にサポートしています。すべてのフックとスクリプトが Node.js で書き直され、最大の互換性を実現しています。
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ node scripts/setup-package-manager.js --detect
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📦 含まれるもの
|
## 含まれるもの
|
||||||
|
|
||||||
このリポジトリは**Claude Codeプラグイン**です - 直接インストールするか、コンポーネントを手動でコピーできます。
|
このリポジトリは**Claude Codeプラグイン**です - 直接インストールするか、コンポーネントを手動でコピーできます。
|
||||||
|
|
||||||
@@ -315,7 +315,7 @@ everything-claude-code/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠️ エコシステムツール
|
## エコシステムツール
|
||||||
|
|
||||||
### スキル作成ツール
|
### スキル作成ツール
|
||||||
|
|
||||||
@@ -374,7 +374,7 @@ Claude Codeで`/security-scan`を実行、または[GitHub Action](https://githu
|
|||||||
|
|
||||||
[GitHub](https://github.com/affaan-m/agentshield) | [npm](https://www.npmjs.com/package/ecc-agentshield)
|
[GitHub](https://github.com/affaan-m/agentshield) | [npm](https://www.npmjs.com/package/ecc-agentshield)
|
||||||
|
|
||||||
### 🧠 継続的学習 v2
|
### 継続的学習 v2
|
||||||
|
|
||||||
instinctベースの学習システムがパターンを自動学習:
|
instinctベースの学習システムがパターンを自動学習:
|
||||||
|
|
||||||
@@ -389,7 +389,7 @@ instinctベースの学習システムがパターンを自動学習:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 要件
|
## 要件
|
||||||
|
|
||||||
### Claude Code CLI バージョン
|
### Claude Code CLI バージョン
|
||||||
|
|
||||||
@@ -404,7 +404,7 @@ claude --version
|
|||||||
|
|
||||||
### 重要: フック自動読み込み動作
|
### 重要: フック自動読み込み動作
|
||||||
|
|
||||||
> ⚠️ **貢献者向け:** `.claude-plugin/plugin.json`に`"hooks"`フィールドを追加しないでください。これは回帰テストで強制されます。
|
> WARNING: **貢献者向け:** `.claude-plugin/plugin.json`に`"hooks"`フィールドを追加しないでください。これは回帰テストで強制されます。
|
||||||
|
|
||||||
Claude Code v2.1+は、インストール済みプラグインの`hooks/hooks.json`(規約)を自動読み込みします。`plugin.json`で明示的に宣言するとエラーが発生します:
|
Claude Code v2.1+は、インストール済みプラグインの`hooks/hooks.json`(規約)を自動読み込みします。`plugin.json`で明示的に宣言するとエラーが発生します:
|
||||||
|
|
||||||
@@ -416,7 +416,7 @@ Duplicate hook file detected: ./hooks/hooks.json is already resolved to a loaded
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📥 インストール
|
## インストール
|
||||||
|
|
||||||
### オプション1:プラグインとしてインストール(推奨)
|
### オプション1:プラグインとしてインストール(推奨)
|
||||||
|
|
||||||
@@ -471,7 +471,7 @@ Duplicate hook file detected: ./hooks/hooks.json is already resolved to a loaded
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 🔧 オプション2:手動インストール
|
### オプション2:手動インストール
|
||||||
|
|
||||||
インストール内容を手動で制御したい場合:
|
インストール内容を手動で制御したい場合:
|
||||||
|
|
||||||
@@ -507,7 +507,7 @@ cp -r everything-claude-code/skills/* ~/.claude/skills/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 主要概念
|
## 主要概念
|
||||||
|
|
||||||
### エージェント
|
### エージェント
|
||||||
|
|
||||||
@@ -569,7 +569,7 @@ rules/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧪 テストを実行
|
## テストを実行
|
||||||
|
|
||||||
プラグインには包括的なテストスイートが含まれています:
|
プラグインには包括的なテストスイートが含まれています:
|
||||||
|
|
||||||
@@ -585,7 +585,7 @@ node tests/hooks/hooks.test.js
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🤝 貢献
|
## 貢献
|
||||||
|
|
||||||
**貢献は大歓迎で、奨励されています。**
|
**貢献は大歓迎で、奨励されています。**
|
||||||
|
|
||||||
@@ -637,7 +637,7 @@ npm install ecc-universal
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔌 OpenCodeサポート
|
## OpenCodeサポート
|
||||||
|
|
||||||
ECCは**フルOpenCodeサポート**をプラグインとフック含めて提供。
|
ECCは**フルOpenCodeサポート**をプラグインとフック含めて提供。
|
||||||
|
|
||||||
@@ -657,13 +657,13 @@ opencode
|
|||||||
|
|
||||||
| 機能 | Claude Code | OpenCode | ステータス |
|
| 機能 | Claude Code | OpenCode | ステータス |
|
||||||
|---------|-------------|----------|--------|
|
|---------|-------------|----------|--------|
|
||||||
| Agents | ✅ 14 エージェント | ✅ 12 エージェント | **Claude Code がリード** |
|
| Agents | PASS: 14 エージェント | PASS: 12 エージェント | **Claude Code がリード** |
|
||||||
| Commands | ✅ 30 コマンド | ✅ 24 コマンド | **Claude Code がリード** |
|
| Commands | PASS: 30 コマンド | PASS: 24 コマンド | **Claude Code がリード** |
|
||||||
| Skills | ✅ 28 スキル | ✅ 16 スキル | **Claude Code がリード** |
|
| Skills | PASS: 28 スキル | PASS: 16 スキル | **Claude Code がリード** |
|
||||||
| Hooks | ✅ 3 フェーズ | ✅ 20+ イベント | **OpenCode が多い!** |
|
| Hooks | PASS: 3 フェーズ | PASS: 20+ イベント | **OpenCode が多い!** |
|
||||||
| Rules | ✅ 8 ルール | ✅ 8 ルール | **完全パリティ** |
|
| Rules | PASS: 8 ルール | PASS: 8 ルール | **完全パリティ** |
|
||||||
| MCP Servers | ✅ 完全 | ✅ 完全 | **完全パリティ** |
|
| MCP Servers | PASS: 完全 | PASS: 完全 | **完全パリティ** |
|
||||||
| Custom Tools | ✅ フック経由 | ✅ ネイティブサポート | **OpenCode がより良い** |
|
| Custom Tools | PASS: フック経由 | PASS: ネイティブサポート | **OpenCode がより良い** |
|
||||||
|
|
||||||
### プラグイン経由のフックサポート
|
### プラグイン経由のフックサポート
|
||||||
|
|
||||||
@@ -737,7 +737,7 @@ npm install ecc-universal
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📖 背景
|
## 背景
|
||||||
|
|
||||||
実験的なリリース以来、Claude Codeを使用してきました。2025年9月、[@DRodriguezFX](https://x.com/DRodriguezFX)と一緒にClaude Codeで[zenith.chat](https://zenith.chat)を構築し、Anthropic x Forum Venturesハッカソンで優勝しました。
|
実験的なリリース以来、Claude Codeを使用してきました。2025年9月、[@DRodriguezFX](https://x.com/DRodriguezFX)と一緒にClaude Codeで[zenith.chat](https://zenith.chat)を構築し、Anthropic x Forum Venturesハッカソンで優勝しました。
|
||||||
|
|
||||||
@@ -745,7 +745,7 @@ npm install ecc-universal
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚠️ 重要な注記
|
## WARNING: 重要な注記
|
||||||
|
|
||||||
### コンテキストウィンドウ管理
|
### コンテキストウィンドウ管理
|
||||||
|
|
||||||
@@ -768,13 +768,13 @@ npm install ecc-universal
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🌟 Star 履歴
|
## Star 履歴
|
||||||
|
|
||||||
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
|
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔗 リンク
|
## リンク
|
||||||
|
|
||||||
- **簡潔ガイド(まずはこれ):** [Everything Claude Code 簡潔ガイド](https://x.com/affaanmustafa/status/2012378465664745795)
|
- **簡潔ガイド(まずはこれ):** [Everything Claude Code 簡潔ガイド](https://x.com/affaanmustafa/status/2012378465664745795)
|
||||||
- **詳細ガイド(高度):** [Everything Claude Code 詳細ガイド](https://x.com/affaanmustafa/status/2014040193557471352)
|
- **詳細ガイド(高度):** [Everything Claude Code 詳細ガイド](https://x.com/affaanmustafa/status/2014040193557471352)
|
||||||
@@ -784,7 +784,7 @@ npm install ecc-universal
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📄 ライセンス
|
## ライセンス
|
||||||
|
|
||||||
MIT - 自由に使用、必要に応じて修正、可能であれば貢献してください。
|
MIT - 自由に使用、必要に応じて修正、可能であれば貢献してください。
|
||||||
|
|
||||||
|
|||||||
@@ -103,12 +103,12 @@ c) 影響度別に優先順位付け
|
|||||||
|
|
||||||
**パターン 1: 型推論の失敗**
|
**パターン 1: 型推論の失敗**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: Parameter 'x' implicitly has an 'any' type
|
// FAIL: エラー: Parameter 'x' implicitly has an 'any' type
|
||||||
function add(x, y) {
|
function add(x, y) {
|
||||||
return x + y
|
return x + y
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 修正: 型アノテーションを追加
|
// PASS: 修正: 型アノテーションを追加
|
||||||
function add(x: number, y: number): number {
|
function add(x: number, y: number): number {
|
||||||
return x + y
|
return x + y
|
||||||
}
|
}
|
||||||
@@ -116,25 +116,25 @@ function add(x: number, y: number): number {
|
|||||||
|
|
||||||
**パターン 2: Null/Undefinedエラー**
|
**パターン 2: Null/Undefinedエラー**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: Object is possibly 'undefined'
|
// FAIL: エラー: Object is possibly 'undefined'
|
||||||
const name = user.name.toUpperCase()
|
const name = user.name.toUpperCase()
|
||||||
|
|
||||||
// ✅ 修正: オプショナルチェーン
|
// PASS: 修正: オプショナルチェーン
|
||||||
const name = user?.name?.toUpperCase()
|
const name = user?.name?.toUpperCase()
|
||||||
|
|
||||||
// ✅ または: Nullチェック
|
// PASS: または: Nullチェック
|
||||||
const name = user && user.name ? user.name.toUpperCase() : ''
|
const name = user && user.name ? user.name.toUpperCase() : ''
|
||||||
```
|
```
|
||||||
|
|
||||||
**パターン 3: プロパティの欠落**
|
**パターン 3: プロパティの欠落**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: Property 'age' does not exist on type 'User'
|
// FAIL: エラー: Property 'age' does not exist on type 'User'
|
||||||
interface User {
|
interface User {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
const user: User = { name: 'John', age: 30 }
|
const user: User = { name: 'John', age: 30 }
|
||||||
|
|
||||||
// ✅ 修正: インターフェースにプロパティを追加
|
// PASS: 修正: インターフェースにプロパティを追加
|
||||||
interface User {
|
interface User {
|
||||||
name: string
|
name: string
|
||||||
age?: number // 常に存在しない場合はオプショナル
|
age?: number // 常に存在しない場合はオプショナル
|
||||||
@@ -143,10 +143,10 @@ interface User {
|
|||||||
|
|
||||||
**パターン 4: インポートエラー**
|
**パターン 4: インポートエラー**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: Cannot find module '@/lib/utils'
|
// FAIL: エラー: Cannot find module '@/lib/utils'
|
||||||
import { formatDate } from '@/lib/utils'
|
import { formatDate } from '@/lib/utils'
|
||||||
|
|
||||||
// ✅ 修正1: tsconfigのパスが正しいか確認
|
// PASS: 修正1: tsconfigのパスが正しいか確認
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"paths": {
|
"paths": {
|
||||||
@@ -155,38 +155,38 @@ import { formatDate } from '@/lib/utils'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 修正2: 相対インポートを使用
|
// PASS: 修正2: 相対インポートを使用
|
||||||
import { formatDate } from '../lib/utils'
|
import { formatDate } from '../lib/utils'
|
||||||
|
|
||||||
// ✅ 修正3: 欠落しているパッケージをインストール
|
// PASS: 修正3: 欠落しているパッケージをインストール
|
||||||
npm install @/lib/utils
|
npm install @/lib/utils
|
||||||
```
|
```
|
||||||
|
|
||||||
**パターン 5: 型の不一致**
|
**パターン 5: 型の不一致**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: Type 'string' is not assignable to type 'number'
|
// FAIL: エラー: Type 'string' is not assignable to type 'number'
|
||||||
const age: number = "30"
|
const age: number = "30"
|
||||||
|
|
||||||
// ✅ 修正: 文字列を数値にパース
|
// PASS: 修正: 文字列を数値にパース
|
||||||
const age: number = parseInt("30", 10)
|
const age: number = parseInt("30", 10)
|
||||||
|
|
||||||
// ✅ または: 型を変更
|
// PASS: または: 型を変更
|
||||||
const age: string = "30"
|
const age: string = "30"
|
||||||
```
|
```
|
||||||
|
|
||||||
**パターン 6: ジェネリック制約**
|
**パターン 6: ジェネリック制約**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: Type 'T' is not assignable to type 'string'
|
// FAIL: エラー: Type 'T' is not assignable to type 'string'
|
||||||
function getLength<T>(item: T): number {
|
function getLength<T>(item: T): number {
|
||||||
return item.length
|
return item.length
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 修正: 制約を追加
|
// PASS: 修正: 制約を追加
|
||||||
function getLength<T extends { length: number }>(item: T): number {
|
function getLength<T extends { length: number }>(item: T): number {
|
||||||
return item.length
|
return item.length
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ または: より具体的な制約
|
// PASS: または: より具体的な制約
|
||||||
function getLength<T extends string | any[]>(item: T): number {
|
function getLength<T extends string | any[]>(item: T): number {
|
||||||
return item.length
|
return item.length
|
||||||
}
|
}
|
||||||
@@ -194,14 +194,14 @@ function getLength<T extends string | any[]>(item: T): number {
|
|||||||
|
|
||||||
**パターン 7: React Hookエラー**
|
**パターン 7: React Hookエラー**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: React Hook "useState" cannot be called in a function
|
// FAIL: エラー: React Hook "useState" cannot be called in a function
|
||||||
function MyComponent() {
|
function MyComponent() {
|
||||||
if (condition) {
|
if (condition) {
|
||||||
const [state, setState] = useState(0) // エラー!
|
const [state, setState] = useState(0) // エラー!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 修正: フックをトップレベルに移動
|
// PASS: 修正: フックをトップレベルに移動
|
||||||
function MyComponent() {
|
function MyComponent() {
|
||||||
const [state, setState] = useState(0)
|
const [state, setState] = useState(0)
|
||||||
|
|
||||||
@@ -215,12 +215,12 @@ function MyComponent() {
|
|||||||
|
|
||||||
**パターン 8: Async/Awaitエラー**
|
**パターン 8: Async/Awaitエラー**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: 'await' expressions are only allowed within async functions
|
// FAIL: エラー: 'await' expressions are only allowed within async functions
|
||||||
function fetchData() {
|
function fetchData() {
|
||||||
const data = await fetch('/api/data')
|
const data = await fetch('/api/data')
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 修正: asyncキーワードを追加
|
// PASS: 修正: asyncキーワードを追加
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const data = await fetch('/api/data')
|
const data = await fetch('/api/data')
|
||||||
}
|
}
|
||||||
@@ -228,14 +228,14 @@ async function fetchData() {
|
|||||||
|
|
||||||
**パターン 9: モジュールが見つからない**
|
**パターン 9: モジュールが見つからない**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: Cannot find module 'react' or its corresponding type declarations
|
// FAIL: エラー: Cannot find module 'react' or its corresponding type declarations
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
// ✅ 修正: 依存関係をインストール
|
// PASS: 修正: 依存関係をインストール
|
||||||
npm install react
|
npm install react
|
||||||
npm install --save-dev @types/react
|
npm install --save-dev @types/react
|
||||||
|
|
||||||
// ✅ 確認: package.jsonに依存関係があることを確認
|
// PASS: 確認: package.jsonに依存関係があることを確認
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.0.0"
|
"react": "^19.0.0"
|
||||||
@@ -248,18 +248,18 @@ npm install --save-dev @types/react
|
|||||||
|
|
||||||
**パターン 10: Next.js固有のエラー**
|
**パターン 10: Next.js固有のエラー**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: Fast Refresh had to perform a full reload
|
// FAIL: エラー: Fast Refresh had to perform a full reload
|
||||||
// 通常、コンポーネント以外のエクスポートが原因
|
// 通常、コンポーネント以外のエクスポートが原因
|
||||||
|
|
||||||
// ✅ 修正: エクスポートを分離
|
// PASS: 修正: エクスポートを分離
|
||||||
// ❌ 間違い: file.tsx
|
// FAIL: 間違い: file.tsx
|
||||||
export const MyComponent = () => <div />
|
export const MyComponent = () => <div />
|
||||||
export const someConstant = 42 // フルリロードの原因
|
export const someConstant = 42 // フルリロードの原因
|
||||||
|
|
||||||
// ✅ 正しい: component.tsx
|
// PASS: 正しい: component.tsx
|
||||||
export const MyComponent = () => <div />
|
export const MyComponent = () => <div />
|
||||||
|
|
||||||
// ✅ 正しい: constants.ts
|
// PASS: 正しい: constants.ts
|
||||||
export const someConstant = 42
|
export const someConstant = 42
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ export const someConstant = 42
|
|||||||
|
|
||||||
### Next.js 15 + React 19の互換性
|
### Next.js 15 + React 19の互換性
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: React 19の型変更
|
// FAIL: エラー: React 19の型変更
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -278,7 +278,7 @@ const Component: FC<Props> = ({ children }) => {
|
|||||||
return <div>{children}</div>
|
return <div>{children}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 修正: React 19ではFCは不要
|
// PASS: 修正: React 19ではFCは不要
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
@@ -290,12 +290,12 @@ const Component = ({ children }: Props) => {
|
|||||||
|
|
||||||
### Supabaseクライアントの型
|
### Supabaseクライアントの型
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: Type 'any' not assignable
|
// FAIL: エラー: Type 'any' not assignable
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('*')
|
.select('*')
|
||||||
|
|
||||||
// ✅ 修正: 型アノテーションを追加
|
// PASS: 修正: 型アノテーションを追加
|
||||||
interface Market {
|
interface Market {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@@ -310,10 +310,10 @@ const { data } = await supabase
|
|||||||
|
|
||||||
### Redis Stackの型
|
### Redis Stackの型
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: Property 'ft' does not exist on type 'RedisClientType'
|
// FAIL: エラー: Property 'ft' does not exist on type 'RedisClientType'
|
||||||
const results = await client.ft.search('idx:markets', query)
|
const results = await client.ft.search('idx:markets', query)
|
||||||
|
|
||||||
// ✅ 修正: 適切なRedis Stackの型を使用
|
// PASS: 修正: 適切なRedis Stackの型を使用
|
||||||
import { createClient } from 'redis'
|
import { createClient } from 'redis'
|
||||||
|
|
||||||
const client = createClient({
|
const client = createClient({
|
||||||
@@ -328,10 +328,10 @@ const results = await client.ft.search('idx:markets', query)
|
|||||||
|
|
||||||
### Solana Web3.jsの型
|
### Solana Web3.jsの型
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ エラー: Argument of type 'string' not assignable to 'PublicKey'
|
// FAIL: エラー: Argument of type 'string' not assignable to 'PublicKey'
|
||||||
const publicKey = wallet.address
|
const publicKey = wallet.address
|
||||||
|
|
||||||
// ✅ 修正: PublicKeyコンストラクタを使用
|
// PASS: 修正: PublicKeyコンストラクタを使用
|
||||||
import { PublicKey } from '@solana/web3.js'
|
import { PublicKey } from '@solana/web3.js'
|
||||||
const publicKey = new PublicKey(wallet.address)
|
const publicKey = new PublicKey(wallet.address)
|
||||||
```
|
```
|
||||||
@@ -341,34 +341,34 @@ const publicKey = new PublicKey(wallet.address)
|
|||||||
**重要: できる限り最小限の変更を行う**
|
**重要: できる限り最小限の変更を行う**
|
||||||
|
|
||||||
### すべきこと:
|
### すべきこと:
|
||||||
✅ 欠落している型アノテーションを追加
|
PASS: 欠落している型アノテーションを追加
|
||||||
✅ 必要な箇所にnullチェックを追加
|
PASS: 必要な箇所にnullチェックを追加
|
||||||
✅ インポート/エクスポートを修正
|
PASS: インポート/エクスポートを修正
|
||||||
✅ 欠落している依存関係を追加
|
PASS: 欠落している依存関係を追加
|
||||||
✅ 型定義を更新
|
PASS: 型定義を更新
|
||||||
✅ 設定ファイルを修正
|
PASS: 設定ファイルを修正
|
||||||
|
|
||||||
### してはいけないこと:
|
### してはいけないこと:
|
||||||
❌ 関連のないコードをリファクタリング
|
FAIL: 関連のないコードをリファクタリング
|
||||||
❌ アーキテクチャを変更
|
FAIL: アーキテクチャを変更
|
||||||
❌ 変数/関数の名前を変更(エラーの原因でない限り)
|
FAIL: 変数/関数の名前を変更(エラーの原因でない限り)
|
||||||
❌ 新機能を追加
|
FAIL: 新機能を追加
|
||||||
❌ ロジックフローを変更(エラー修正以外)
|
FAIL: ロジックフローを変更(エラー修正以外)
|
||||||
❌ パフォーマンスを最適化
|
FAIL: パフォーマンスを最適化
|
||||||
❌ コードスタイルを改善
|
FAIL: コードスタイルを改善
|
||||||
|
|
||||||
**最小差分の例:**
|
**最小差分の例:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ファイルは200行あり、45行目にエラーがある
|
// ファイルは200行あり、45行目にエラーがある
|
||||||
|
|
||||||
// ❌ 間違い: ファイル全体をリファクタリング
|
// FAIL: 間違い: ファイル全体をリファクタリング
|
||||||
// - 変数の名前変更
|
// - 変数の名前変更
|
||||||
// - 関数の抽出
|
// - 関数の抽出
|
||||||
// - パターンの変更
|
// - パターンの変更
|
||||||
// 結果: 50行変更
|
// 結果: 50行変更
|
||||||
|
|
||||||
// ✅ 正しい: エラーのみを修正
|
// PASS: 正しい: エラーのみを修正
|
||||||
// - 45行目に型アノテーションを追加
|
// - 45行目に型アノテーションを追加
|
||||||
// 結果: 1行変更
|
// 結果: 1行変更
|
||||||
|
|
||||||
@@ -376,12 +376,12 @@ function processData(data) { // 45行目 - エラー: 'data' implicitly has 'any
|
|||||||
return data.map(item => item.value)
|
return data.map(item => item.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 最小限の修正:
|
// PASS: 最小限の修正:
|
||||||
function processData(data: any[]) { // この行のみを変更
|
function processData(data: any[]) { // この行のみを変更
|
||||||
return data.map(item => item.value)
|
return data.map(item => item.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ より良い最小限の修正(型が既知の場合):
|
// PASS: より良い最小限の修正(型が既知の場合):
|
||||||
function processData(data: Array<{ value: number }>) {
|
function processData(data: Array<{ value: number }>) {
|
||||||
return data.map(item => item.value)
|
return data.map(item => item.value)
|
||||||
}
|
}
|
||||||
@@ -396,7 +396,7 @@ function processData(data: Array<{ value: number }>) {
|
|||||||
**ビルド対象:** Next.jsプロダクション / TypeScriptチェック / ESLint
|
**ビルド対象:** Next.jsプロダクション / TypeScriptチェック / ESLint
|
||||||
**初期エラー数:** X
|
**初期エラー数:** X
|
||||||
**修正済みエラー数:** Y
|
**修正済みエラー数:** Y
|
||||||
**ビルドステータス:** ✅ 成功 / ❌ 失敗
|
**ビルドステータス:** PASS: 成功 / FAIL: 失敗
|
||||||
|
|
||||||
## 修正済みエラー
|
## 修正済みエラー
|
||||||
|
|
||||||
@@ -430,17 +430,17 @@ Parameter 'market' implicitly has an 'any' type.
|
|||||||
|
|
||||||
## 検証手順
|
## 検証手順
|
||||||
|
|
||||||
1. ✅ TypeScriptチェック成功: `npx tsc --noEmit`
|
1. PASS: TypeScriptチェック成功: `npx tsc --noEmit`
|
||||||
2. ✅ Next.jsビルド成功: `npm run build`
|
2. PASS: Next.jsビルド成功: `npm run build`
|
||||||
3. ✅ ESLintチェック成功: `npx eslint .`
|
3. PASS: ESLintチェック成功: `npx eslint .`
|
||||||
4. ✅ 新しいエラーが導入されていない
|
4. PASS: 新しいエラーが導入されていない
|
||||||
5. ✅ 開発サーバー起動: `npm run dev`
|
5. PASS: 開発サーバー起動: `npm run dev`
|
||||||
|
|
||||||
## まとめ
|
## まとめ
|
||||||
|
|
||||||
- 解決されたエラー総数: X
|
- 解決されたエラー総数: X
|
||||||
- 変更行数総数: Y
|
- 変更行数総数: Y
|
||||||
- ビルドステータス: ✅ 成功
|
- ビルドステータス: PASS: 成功
|
||||||
- 修正時間: Z 分
|
- 修正時間: Z 分
|
||||||
- ブロッキング問題: 0 件残存
|
- ブロッキング問題: 0 件残存
|
||||||
|
|
||||||
@@ -470,19 +470,19 @@ Parameter 'market' implicitly has an 'any' type.
|
|||||||
|
|
||||||
## ビルドエラーの優先度レベル
|
## ビルドエラーの優先度レベル
|
||||||
|
|
||||||
### 🔴 クリティカル(即座に修正)
|
### クリティカル(即座に修正)
|
||||||
- ビルドが完全に壊れている
|
- ビルドが完全に壊れている
|
||||||
- 開発サーバーが起動しない
|
- 開発サーバーが起動しない
|
||||||
- プロダクションデプロイがブロックされている
|
- プロダクションデプロイがブロックされている
|
||||||
- 複数のファイルが失敗している
|
- 複数のファイルが失敗している
|
||||||
|
|
||||||
### 🟡 高(早急に修正)
|
### 高(早急に修正)
|
||||||
- 単一ファイルの失敗
|
- 単一ファイルの失敗
|
||||||
- 新しいコードの型エラー
|
- 新しいコードの型エラー
|
||||||
- インポートエラー
|
- インポートエラー
|
||||||
- 重要でないビルド警告
|
- 重要でないビルド警告
|
||||||
|
|
||||||
### 🟢 中(可能な時に修正)
|
### 中(可能な時に修正)
|
||||||
- リンター警告
|
- リンター警告
|
||||||
- 非推奨APIの使用
|
- 非推奨APIの使用
|
||||||
- 非厳格な型の問題
|
- 非厳格な型の問題
|
||||||
@@ -521,13 +521,13 @@ npm install
|
|||||||
## 成功指標
|
## 成功指標
|
||||||
|
|
||||||
ビルドエラー解決後:
|
ビルドエラー解決後:
|
||||||
- ✅ `npx tsc --noEmit` が終了コード0で終了
|
- PASS: `npx tsc --noEmit` が終了コード0で終了
|
||||||
- ✅ `npm run build` が正常に完了
|
- PASS: `npm run build` が正常に完了
|
||||||
- ✅ 新しいエラーが導入されていない
|
- PASS: 新しいエラーが導入されていない
|
||||||
- ✅ 最小限の行数変更(影響を受けたファイルの5%未満)
|
- PASS: 最小限の行数変更(影響を受けたファイルの5%未満)
|
||||||
- ✅ ビルド時間が大幅に増加していない
|
- PASS: ビルド時間が大幅に増加していない
|
||||||
- ✅ 開発サーバーがエラーなく動作
|
- PASS: 開発サーバーがエラーなく動作
|
||||||
- ✅ テストが依然として成功
|
- PASS: テストが依然として成功
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -81,15 +81,15 @@ model: opus
|
|||||||
問題: APIキーがソースコードに公開されている
|
問題: APIキーがソースコードに公開されている
|
||||||
修正: 環境変数に移動
|
修正: 環境変数に移動
|
||||||
|
|
||||||
const apiKey = "sk-abc123"; // ❌ Bad
|
const apiKey = "sk-abc123"; // FAIL: Bad
|
||||||
const apiKey = process.env.API_KEY; // ✓ Good
|
const apiKey = process.env.API_KEY; // ✓ Good
|
||||||
```
|
```
|
||||||
|
|
||||||
## 承認基準
|
## 承認基準
|
||||||
|
|
||||||
- ✅ 承認: CRITICALまたはHIGH問題なし
|
- PASS: 承認: CRITICALまたはHIGH問題なし
|
||||||
- ⚠️ 警告: MEDIUM問題のみ(注意してマージ可能)
|
- WARNING: 警告: MEDIUM問題のみ(注意してマージ可能)
|
||||||
- ❌ ブロック: CRITICALまたはHIGH問題が見つかった
|
- FAIL: ブロック: CRITICALまたはHIGH問題が見つかった
|
||||||
|
|
||||||
## プロジェクト固有のガイドライン(例)
|
## プロジェクト固有のガイドライン(例)
|
||||||
|
|
||||||
|
|||||||
@@ -112,14 +112,14 @@ c) データ保護
|
|||||||
**影響:** 大きなテーブルで100〜1000倍高速なクエリ
|
**影響:** 大きなテーブルで100〜1000倍高速なクエリ
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: 外部キーにインデックスがない
|
-- FAIL: 悪い: 外部キーにインデックスがない
|
||||||
CREATE TABLE orders (
|
CREATE TABLE orders (
|
||||||
id bigint PRIMARY KEY,
|
id bigint PRIMARY KEY,
|
||||||
customer_id bigint REFERENCES customers(id)
|
customer_id bigint REFERENCES customers(id)
|
||||||
-- インデックスが欠落!
|
-- インデックスが欠落!
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ✅ 良い: 外部キーにインデックス
|
-- PASS: 良い: 外部キーにインデックス
|
||||||
CREATE TABLE orders (
|
CREATE TABLE orders (
|
||||||
id bigint PRIMARY KEY,
|
id bigint PRIMARY KEY,
|
||||||
customer_id bigint REFERENCES customers(id)
|
customer_id bigint REFERENCES customers(id)
|
||||||
@@ -137,11 +137,11 @@ CREATE INDEX orders_customer_id_idx ON orders (customer_id);
|
|||||||
| **Hash** | 等価のみ | `=`(B-treeより若干高速) |
|
| **Hash** | 等価のみ | `=`(B-treeより若干高速) |
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: JSONB包含のためのB-tree
|
-- FAIL: 悪い: JSONB包含のためのB-tree
|
||||||
CREATE INDEX products_attrs_idx ON products (attributes);
|
CREATE INDEX products_attrs_idx ON products (attributes);
|
||||||
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
|
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
|
||||||
|
|
||||||
-- ✅ 良い: JSONBのためのGIN
|
-- PASS: 良い: JSONBのためのGIN
|
||||||
CREATE INDEX products_attrs_idx ON products USING gin (attributes);
|
CREATE INDEX products_attrs_idx ON products USING gin (attributes);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -150,11 +150,11 @@ CREATE INDEX products_attrs_idx ON products USING gin (attributes);
|
|||||||
**影響:** 複数列クエリで5〜10倍高速
|
**影響:** 複数列クエリで5〜10倍高速
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: 個別のインデックス
|
-- FAIL: 悪い: 個別のインデックス
|
||||||
CREATE INDEX orders_status_idx ON orders (status);
|
CREATE INDEX orders_status_idx ON orders (status);
|
||||||
CREATE INDEX orders_created_idx ON orders (created_at);
|
CREATE INDEX orders_created_idx ON orders (created_at);
|
||||||
|
|
||||||
-- ✅ 良い: 複合インデックス(等価列を最初に、次に範囲)
|
-- PASS: 良い: 複合インデックス(等価列を最初に、次に範囲)
|
||||||
CREATE INDEX orders_status_created_idx ON orders (status, created_at);
|
CREATE INDEX orders_status_created_idx ON orders (status, created_at);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -170,11 +170,11 @@ CREATE INDEX orders_status_created_idx ON orders (status, created_at);
|
|||||||
**影響:** テーブルルックアップを回避することで2〜5倍高速なクエリ
|
**影響:** テーブルルックアップを回避することで2〜5倍高速なクエリ
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: テーブルからnameを取得する必要がある
|
-- FAIL: 悪い: テーブルからnameを取得する必要がある
|
||||||
CREATE INDEX users_email_idx ON users (email);
|
CREATE INDEX users_email_idx ON users (email);
|
||||||
SELECT email, name FROM users WHERE email = 'user@example.com';
|
SELECT email, name FROM users WHERE email = 'user@example.com';
|
||||||
|
|
||||||
-- ✅ 良い: すべての列がインデックスに含まれる
|
-- PASS: 良い: すべての列がインデックスに含まれる
|
||||||
CREATE INDEX users_email_idx ON users (email) INCLUDE (name, created_at);
|
CREATE INDEX users_email_idx ON users (email) INCLUDE (name, created_at);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -183,10 +183,10 @@ CREATE INDEX users_email_idx ON users (email) INCLUDE (name, created_at);
|
|||||||
**影響:** 5〜20倍小さいインデックス、高速な書き込みとクエリ
|
**影響:** 5〜20倍小さいインデックス、高速な書き込みとクエリ
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: 完全なインデックスには削除された行が含まれる
|
-- FAIL: 悪い: 完全なインデックスには削除された行が含まれる
|
||||||
CREATE INDEX users_email_idx ON users (email);
|
CREATE INDEX users_email_idx ON users (email);
|
||||||
|
|
||||||
-- ✅ 良い: 部分インデックスは削除された行を除外
|
-- PASS: 良い: 部分インデックスは削除された行を除外
|
||||||
CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;
|
CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;
|
|||||||
### 1. データ型の選択
|
### 1. データ型の選択
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: 不適切な型選択
|
-- FAIL: 悪い: 不適切な型選択
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id int, -- 21億でオーバーフロー
|
id int, -- 21億でオーバーフロー
|
||||||
email varchar(255), -- 人為的な制限
|
email varchar(255), -- 人為的な制限
|
||||||
@@ -211,7 +211,7 @@ CREATE TABLE users (
|
|||||||
balance float -- 精度の損失
|
balance float -- 精度の損失
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ✅ 良い: 適切な型
|
-- PASS: 良い: 適切な型
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||||
email text NOT NULL,
|
email text NOT NULL,
|
||||||
@@ -224,18 +224,18 @@ CREATE TABLE users (
|
|||||||
### 2. 主キー戦略
|
### 2. 主キー戦略
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ✅ 単一データベース: IDENTITY(デフォルト、推奨)
|
-- PASS: 単一データベース: IDENTITY(デフォルト、推奨)
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY
|
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ✅ 分散システム: UUIDv7(時間順)
|
-- PASS: 分散システム: UUIDv7(時間順)
|
||||||
CREATE EXTENSION IF NOT EXISTS pg_uuidv7;
|
CREATE EXTENSION IF NOT EXISTS pg_uuidv7;
|
||||||
CREATE TABLE orders (
|
CREATE TABLE orders (
|
||||||
id uuid DEFAULT uuid_generate_v7() PRIMARY KEY
|
id uuid DEFAULT uuid_generate_v7() PRIMARY KEY
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ❌ 避ける: ランダムUUIDはインデックスの断片化を引き起こす
|
-- FAIL: 避ける: ランダムUUIDはインデックスの断片化を引き起こす
|
||||||
CREATE TABLE events (
|
CREATE TABLE events (
|
||||||
id uuid DEFAULT gen_random_uuid() PRIMARY KEY -- 断片化した挿入!
|
id uuid DEFAULT gen_random_uuid() PRIMARY KEY -- 断片化した挿入!
|
||||||
);
|
);
|
||||||
@@ -246,7 +246,7 @@ CREATE TABLE events (
|
|||||||
**使用する場合:** テーブル > 1億行、時系列データ、古いデータを削除する必要がある
|
**使用する場合:** テーブル > 1億行、時系列データ、古いデータを削除する必要がある
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ✅ 良い: 月ごとにパーティション化
|
-- PASS: 良い: 月ごとにパーティション化
|
||||||
CREATE TABLE events (
|
CREATE TABLE events (
|
||||||
id bigint GENERATED ALWAYS AS IDENTITY,
|
id bigint GENERATED ALWAYS AS IDENTITY,
|
||||||
created_at timestamptz NOT NULL,
|
created_at timestamptz NOT NULL,
|
||||||
@@ -266,11 +266,11 @@ DROP TABLE events_2023_01; -- 数時間かかるDELETEではなく即座に
|
|||||||
### 4. 小文字の識別子を使用
|
### 4. 小文字の識別子を使用
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: 引用符付きの混合ケースは至る所で引用符が必要
|
-- FAIL: 悪い: 引用符付きの混合ケースは至る所で引用符が必要
|
||||||
CREATE TABLE "Users" ("userId" bigint, "firstName" text);
|
CREATE TABLE "Users" ("userId" bigint, "firstName" text);
|
||||||
SELECT "firstName" FROM "Users"; -- 引用符が必須!
|
SELECT "firstName" FROM "Users"; -- 引用符が必須!
|
||||||
|
|
||||||
-- ✅ 良い: 小文字は引用符なしで機能
|
-- PASS: 良い: 小文字は引用符なしで機能
|
||||||
CREATE TABLE users (user_id bigint, first_name text);
|
CREATE TABLE users (user_id bigint, first_name text);
|
||||||
SELECT first_name FROM users;
|
SELECT first_name FROM users;
|
||||||
```
|
```
|
||||||
@@ -284,11 +284,11 @@ SELECT first_name FROM users;
|
|||||||
**影響:** 重要 - データベースで強制されるテナント分離
|
**影響:** 重要 - データベースで強制されるテナント分離
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: アプリケーションのみのフィルタリング
|
-- FAIL: 悪い: アプリケーションのみのフィルタリング
|
||||||
SELECT * FROM orders WHERE user_id = $current_user_id;
|
SELECT * FROM orders WHERE user_id = $current_user_id;
|
||||||
-- バグはすべての注文が露出することを意味する!
|
-- バグはすべての注文が露出することを意味する!
|
||||||
|
|
||||||
-- ✅ 良い: データベースで強制されるRLS
|
-- PASS: 良い: データベースで強制されるRLS
|
||||||
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
|
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
|
||||||
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
|
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
@@ -308,11 +308,11 @@ CREATE POLICY orders_user_policy ON orders
|
|||||||
**影響:** 5〜10倍高速なRLSクエリ
|
**影響:** 5〜10倍高速なRLSクエリ
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: 関数が行ごとに呼び出される
|
-- FAIL: 悪い: 関数が行ごとに呼び出される
|
||||||
CREATE POLICY orders_policy ON orders
|
CREATE POLICY orders_policy ON orders
|
||||||
USING (auth.uid() = user_id); -- 100万行に対して100万回呼び出される!
|
USING (auth.uid() = user_id); -- 100万行に対して100万回呼び出される!
|
||||||
|
|
||||||
-- ✅ 良い: SELECTでラップ(キャッシュされ、一度だけ呼び出される)
|
-- PASS: 良い: SELECTでラップ(キャッシュされ、一度だけ呼び出される)
|
||||||
CREATE POLICY orders_policy ON orders
|
CREATE POLICY orders_policy ON orders
|
||||||
USING ((SELECT auth.uid()) = user_id); -- 100倍高速
|
USING ((SELECT auth.uid()) = user_id); -- 100倍高速
|
||||||
|
|
||||||
@@ -323,10 +323,10 @@ CREATE INDEX orders_user_id_idx ON orders (user_id);
|
|||||||
### 3. 最小権限アクセス
|
### 3. 最小権限アクセス
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: 過度に許可的
|
-- FAIL: 悪い: 過度に許可的
|
||||||
GRANT ALL PRIVILEGES ON ALL TABLES TO app_user;
|
GRANT ALL PRIVILEGES ON ALL TABLES TO app_user;
|
||||||
|
|
||||||
-- ✅ 良い: 最小限の権限
|
-- PASS: 良い: 最小限の権限
|
||||||
CREATE ROLE app_readonly NOLOGIN;
|
CREATE ROLE app_readonly NOLOGIN;
|
||||||
GRANT USAGE ON SCHEMA public TO app_readonly;
|
GRANT USAGE ON SCHEMA public TO app_readonly;
|
||||||
GRANT SELECT ON public.products, public.categories TO app_readonly;
|
GRANT SELECT ON public.products, public.categories TO app_readonly;
|
||||||
@@ -378,14 +378,14 @@ SELECT pg_reload_conf();
|
|||||||
### 1. トランザクションを短く保つ
|
### 1. トランザクションを短く保つ
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: 外部APIコール中にロックを保持
|
-- FAIL: 悪い: 外部APIコール中にロックを保持
|
||||||
BEGIN;
|
BEGIN;
|
||||||
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
|
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
|
||||||
-- HTTPコールに5秒かかる...
|
-- HTTPコールに5秒かかる...
|
||||||
UPDATE orders SET status = 'paid' WHERE id = 1;
|
UPDATE orders SET status = 'paid' WHERE id = 1;
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
-- ✅ 良い: 最小限のロック期間
|
-- PASS: 良い: 最小限のロック期間
|
||||||
-- トランザクション外で最初にAPIコールを実行
|
-- トランザクション外で最初にAPIコールを実行
|
||||||
BEGIN;
|
BEGIN;
|
||||||
UPDATE orders SET status = 'paid', payment_id = $1
|
UPDATE orders SET status = 'paid', payment_id = $1
|
||||||
@@ -397,12 +397,12 @@ COMMIT; -- ミリ秒でロックを保持
|
|||||||
### 2. デッドロックを防ぐ
|
### 2. デッドロックを防ぐ
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: 一貫性のないロック順序がデッドロックを引き起こす
|
-- FAIL: 悪い: 一貫性のないロック順序がデッドロックを引き起こす
|
||||||
-- トランザクションA: 行1をロック、次に行2
|
-- トランザクションA: 行1をロック、次に行2
|
||||||
-- トランザクションB: 行2をロック、次に行1
|
-- トランザクションB: 行2をロック、次に行1
|
||||||
-- デッドロック!
|
-- デッドロック!
|
||||||
|
|
||||||
-- ✅ 良い: 一貫したロック順序
|
-- PASS: 良い: 一貫したロック順序
|
||||||
BEGIN;
|
BEGIN;
|
||||||
SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
|
SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
|
||||||
-- これで両方の行がロックされ、任意の順序で更新可能
|
-- これで両方の行がロックされ、任意の順序で更新可能
|
||||||
@@ -416,10 +416,10 @@ COMMIT;
|
|||||||
**影響:** ワーカーキューで10倍のスループット
|
**影響:** ワーカーキューで10倍のスループット
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: ワーカーが互いを待つ
|
-- FAIL: 悪い: ワーカーが互いを待つ
|
||||||
SELECT * FROM jobs WHERE status = 'pending' LIMIT 1 FOR UPDATE;
|
SELECT * FROM jobs WHERE status = 'pending' LIMIT 1 FOR UPDATE;
|
||||||
|
|
||||||
-- ✅ 良い: ワーカーはロックされた行をスキップ
|
-- PASS: 良い: ワーカーはロックされた行をスキップ
|
||||||
UPDATE jobs
|
UPDATE jobs
|
||||||
SET status = 'processing', worker_id = $1, started_at = now()
|
SET status = 'processing', worker_id = $1, started_at = now()
|
||||||
WHERE id = (
|
WHERE id = (
|
||||||
@@ -441,36 +441,36 @@ RETURNING *;
|
|||||||
**影響:** バルク挿入が10〜50倍高速
|
**影響:** バルク挿入が10〜50倍高速
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: 個別の挿入
|
-- FAIL: 悪い: 個別の挿入
|
||||||
INSERT INTO events (user_id, action) VALUES (1, 'click');
|
INSERT INTO events (user_id, action) VALUES (1, 'click');
|
||||||
INSERT INTO events (user_id, action) VALUES (2, 'view');
|
INSERT INTO events (user_id, action) VALUES (2, 'view');
|
||||||
-- 1000回のラウンドトリップ
|
-- 1000回のラウンドトリップ
|
||||||
|
|
||||||
-- ✅ 良い: バッチ挿入
|
-- PASS: 良い: バッチ挿入
|
||||||
INSERT INTO events (user_id, action) VALUES
|
INSERT INTO events (user_id, action) VALUES
|
||||||
(1, 'click'),
|
(1, 'click'),
|
||||||
(2, 'view'),
|
(2, 'view'),
|
||||||
(3, 'click');
|
(3, 'click');
|
||||||
-- 1回のラウンドトリップ
|
-- 1回のラウンドトリップ
|
||||||
|
|
||||||
-- ✅ 最良: 大きなデータセットにはCOPY
|
-- PASS: 最良: 大きなデータセットにはCOPY
|
||||||
COPY events (user_id, action) FROM '/path/to/data.csv' WITH (FORMAT csv);
|
COPY events (user_id, action) FROM '/path/to/data.csv' WITH (FORMAT csv);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. N+1クエリの排除
|
### 2. N+1クエリの排除
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: N+1パターン
|
-- FAIL: 悪い: N+1パターン
|
||||||
SELECT id FROM users WHERE active = true; -- 100件のIDを返す
|
SELECT id FROM users WHERE active = true; -- 100件のIDを返す
|
||||||
-- 次に100回のクエリ:
|
-- 次に100回のクエリ:
|
||||||
SELECT * FROM orders WHERE user_id = 1;
|
SELECT * FROM orders WHERE user_id = 1;
|
||||||
SELECT * FROM orders WHERE user_id = 2;
|
SELECT * FROM orders WHERE user_id = 2;
|
||||||
-- ... 98回以上
|
-- ... 98回以上
|
||||||
|
|
||||||
-- ✅ 良い: ANYを使用した単一クエリ
|
-- PASS: 良い: ANYを使用した単一クエリ
|
||||||
SELECT * FROM orders WHERE user_id = ANY(ARRAY[1, 2, 3, ...]);
|
SELECT * FROM orders WHERE user_id = ANY(ARRAY[1, 2, 3, ...]);
|
||||||
|
|
||||||
-- ✅ 良い: JOIN
|
-- PASS: 良い: JOIN
|
||||||
SELECT u.id, u.name, o.*
|
SELECT u.id, u.name, o.*
|
||||||
FROM users u
|
FROM users u
|
||||||
LEFT JOIN orders o ON o.user_id = u.id
|
LEFT JOIN orders o ON o.user_id = u.id
|
||||||
@@ -482,11 +482,11 @@ WHERE u.active = true;
|
|||||||
**影響:** ページの深さに関係なく一貫したO(1)パフォーマンス
|
**影響:** ページの深さに関係なく一貫したO(1)パフォーマンス
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: OFFSETは深さとともに遅くなる
|
-- FAIL: 悪い: OFFSETは深さとともに遅くなる
|
||||||
SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 199980;
|
SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 199980;
|
||||||
-- 200,000行をスキャン!
|
-- 200,000行をスキャン!
|
||||||
|
|
||||||
-- ✅ 良い: カーソルベース(常に高速)
|
-- PASS: 良い: カーソルベース(常に高速)
|
||||||
SELECT * FROM products WHERE id > 199980 ORDER BY id LIMIT 20;
|
SELECT * FROM products WHERE id > 199980 ORDER BY id LIMIT 20;
|
||||||
-- インデックスを使用、O(1)
|
-- インデックスを使用、O(1)
|
||||||
```
|
```
|
||||||
@@ -494,11 +494,11 @@ SELECT * FROM products WHERE id > 199980 ORDER BY id LIMIT 20;
|
|||||||
### 4. 挿入または更新のためのUPSERT
|
### 4. 挿入または更新のためのUPSERT
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ❌ 悪い: 競合状態
|
-- FAIL: 悪い: 競合状態
|
||||||
SELECT * FROM settings WHERE user_id = 123 AND key = 'theme';
|
SELECT * FROM settings WHERE user_id = 123 AND key = 'theme';
|
||||||
-- 両方のスレッドが何も見つけず、両方が挿入、一方が失敗
|
-- 両方のスレッドが何も見つけず、両方が挿入、一方が失敗
|
||||||
|
|
||||||
-- ✅ 良い: アトミックなUPSERT
|
-- PASS: 良い: アトミックなUPSERT
|
||||||
INSERT INTO settings (user_id, key, value)
|
INSERT INTO settings (user_id, key, value)
|
||||||
VALUES (123, 'theme', 'dark')
|
VALUES (123, 'theme', 'dark')
|
||||||
ON CONFLICT (user_id, key)
|
ON CONFLICT (user_id, key)
|
||||||
@@ -605,27 +605,27 @@ ORDER BY rank DESC;
|
|||||||
|
|
||||||
## フラグを立てるべきアンチパターン
|
## フラグを立てるべきアンチパターン
|
||||||
|
|
||||||
### ❌ クエリアンチパターン
|
### FAIL: クエリアンチパターン
|
||||||
- 本番コードでの`SELECT *`
|
- 本番コードでの`SELECT *`
|
||||||
- WHERE/JOIN列にインデックスがない
|
- WHERE/JOIN列にインデックスがない
|
||||||
- 大きなテーブルでのOFFSETページネーション
|
- 大きなテーブルでのOFFSETページネーション
|
||||||
- N+1クエリパターン
|
- N+1クエリパターン
|
||||||
- パラメータ化されていないクエリ(SQLインジェクションリスク)
|
- パラメータ化されていないクエリ(SQLインジェクションリスク)
|
||||||
|
|
||||||
### ❌ スキーマアンチパターン
|
### FAIL: スキーマアンチパターン
|
||||||
- IDに`int`(`bigint`を使用)
|
- IDに`int`(`bigint`を使用)
|
||||||
- 理由なく`varchar(255)`(`text`を使用)
|
- 理由なく`varchar(255)`(`text`を使用)
|
||||||
- タイムゾーンなしの`timestamp`(`timestamptz`を使用)
|
- タイムゾーンなしの`timestamp`(`timestamptz`を使用)
|
||||||
- 主キーとしてのランダムUUID(UUIDv7またはIDENTITYを使用)
|
- 主キーとしてのランダムUUID(UUIDv7またはIDENTITYを使用)
|
||||||
- 引用符を必要とする混合ケースの識別子
|
- 引用符を必要とする混合ケースの識別子
|
||||||
|
|
||||||
### ❌ セキュリティアンチパターン
|
### FAIL: セキュリティアンチパターン
|
||||||
- アプリケーションユーザーへの`GRANT ALL`
|
- アプリケーションユーザーへの`GRANT ALL`
|
||||||
- マルチテナントテーブルでRLSが欠落
|
- マルチテナントテーブルでRLSが欠落
|
||||||
- 行ごとに関数を呼び出すRLSポリシー(SELECTでラップされていない)
|
- 行ごとに関数を呼び出すRLSポリシー(SELECTでラップされていない)
|
||||||
- RLSポリシー列にインデックスがない
|
- RLSポリシー列にインデックスがない
|
||||||
|
|
||||||
### ❌ 接続アンチパターン
|
### FAIL: 接続アンチパターン
|
||||||
- 接続プーリングなし
|
- 接続プーリングなし
|
||||||
- アイドルタイムアウトなし
|
- アイドルタイムアウトなし
|
||||||
- トランザクションモードプーリングでのプリペアドステートメント
|
- トランザクションモードプーリングでのプリペアドステートメント
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ function extractJSDoc(pattern: string) {
|
|||||||
- [x] 古い参照なし
|
- [x] 古い参照なし
|
||||||
|
|
||||||
### 影響
|
### 影響
|
||||||
🟢 低 - ドキュメントのみ、コード変更なし
|
低 - ドキュメントのみ、コード変更なし
|
||||||
|
|
||||||
完全なアーキテクチャ概要についてはdocs/CODEMAPS/INDEX.mdを参照してください。
|
完全なアーキテクチャ概要についてはdocs/CODEMAPS/INDEX.mdを参照してください。
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -428,28 +428,28 @@ test('market search with complex query', async ({ page }) => {
|
|||||||
|
|
||||||
**1. 競合状態**
|
**1. 競合状態**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ 不安定: 要素が準備完了であると仮定しない
|
// FAIL: 不安定: 要素が準備完了であると仮定しない
|
||||||
await page.click('[data-testid="button"]')
|
await page.click('[data-testid="button"]')
|
||||||
|
|
||||||
// ✅ 安定: 要素が準備完了になるのを待つ
|
// PASS: 安定: 要素が準備完了になるのを待つ
|
||||||
await page.locator('[data-testid="button"]').click() // 組み込みの自動待機
|
await page.locator('[data-testid="button"]').click() // 組み込みの自動待機
|
||||||
```
|
```
|
||||||
|
|
||||||
**2. ネットワークタイミング**
|
**2. ネットワークタイミング**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ 不安定: 任意のタイムアウト
|
// FAIL: 不安定: 任意のタイムアウト
|
||||||
await page.waitForTimeout(5000)
|
await page.waitForTimeout(5000)
|
||||||
|
|
||||||
// ✅ 安定: 特定の条件を待つ
|
// PASS: 安定: 特定の条件を待つ
|
||||||
await page.waitForResponse(resp => resp.url().includes('/api/markets'))
|
await page.waitForResponse(resp => resp.url().includes('/api/markets'))
|
||||||
```
|
```
|
||||||
|
|
||||||
**3. アニメーションタイミング**
|
**3. アニメーションタイミング**
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ 不安定: アニメーション中にクリック
|
// FAIL: 不安定: アニメーション中にクリック
|
||||||
await page.click('[data-testid="menu-item"]')
|
await page.click('[data-testid="menu-item"]')
|
||||||
|
|
||||||
// ✅ 安定: アニメーションが完了するのを待つ
|
// PASS: 安定: アニメーションが完了するのを待つ
|
||||||
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
|
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
|
||||||
await page.waitForLoadState('networkidle')
|
await page.waitForLoadState('networkidle')
|
||||||
await page.click('[data-testid="menu-item"]')
|
await page.click('[data-testid="menu-item"]')
|
||||||
@@ -548,7 +548,7 @@ jobs:
|
|||||||
|
|
||||||
**日付:** YYYY-MM-DD HH:MM
|
**日付:** YYYY-MM-DD HH:MM
|
||||||
**期間:** Xm Ys
|
**期間:** Xm Ys
|
||||||
**ステータス:** ✅ 成功 / ❌ 失敗
|
**ステータス:** PASS: 成功 / FAIL: 失敗
|
||||||
|
|
||||||
## まとめ
|
## まとめ
|
||||||
|
|
||||||
@@ -561,20 +561,20 @@ jobs:
|
|||||||
## スイート別テスト結果
|
## スイート別テスト結果
|
||||||
|
|
||||||
### Markets - ブラウズと検索
|
### Markets - ブラウズと検索
|
||||||
- ✅ user can browse markets (2.3s)
|
- PASS: user can browse markets (2.3s)
|
||||||
- ✅ semantic search returns relevant results (1.8s)
|
- PASS: semantic search returns relevant results (1.8s)
|
||||||
- ✅ search handles no results (1.2s)
|
- PASS: search handles no results (1.2s)
|
||||||
- ❌ search with special characters (0.9s)
|
- FAIL: search with special characters (0.9s)
|
||||||
|
|
||||||
### Wallet - 接続
|
### Wallet - 接続
|
||||||
- ✅ user can connect MetaMask (3.1s)
|
- PASS: user can connect MetaMask (3.1s)
|
||||||
- ⚠️ user can connect Phantom (2.8s) - 不安定
|
- WARNING: user can connect Phantom (2.8s) - 不安定
|
||||||
- ✅ user can disconnect wallet (1.5s)
|
- PASS: user can disconnect wallet (1.5s)
|
||||||
|
|
||||||
### Trading - コアフロー
|
### Trading - コアフロー
|
||||||
- ✅ user can place buy order (5.2s)
|
- PASS: user can place buy order (5.2s)
|
||||||
- ❌ user can place sell order (4.8s)
|
- FAIL: user can place sell order (4.8s)
|
||||||
- ✅ insufficient balance shows error (1.9s)
|
- PASS: insufficient balance shows error (1.9s)
|
||||||
|
|
||||||
## 失敗したテスト
|
## 失敗したテスト
|
||||||
|
|
||||||
@@ -623,13 +623,13 @@ jobs:
|
|||||||
## 成功指標
|
## 成功指標
|
||||||
|
|
||||||
E2Eテスト実行後:
|
E2Eテスト実行後:
|
||||||
- ✅ すべての重要なジャーニーが成功(100%)
|
- PASS: すべての重要なジャーニーが成功(100%)
|
||||||
- ✅ 全体の成功率 > 95%
|
- PASS: 全体の成功率 > 95%
|
||||||
- ✅ 不安定率 < 5%
|
- PASS: 不安定率 < 5%
|
||||||
- ✅ デプロイをブロックする失敗したテストなし
|
- PASS: デプロイをブロックする失敗したテストなし
|
||||||
- ✅ アーティファクトがアップロードされアクセス可能
|
- PASS: アーティファクトがアップロードされアクセス可能
|
||||||
- ✅ テスト時間 < 10分
|
- PASS: テスト時間 < 10分
|
||||||
- ✅ HTMLレポートが生成された
|
- PASS: HTMLレポートが生成された
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -146,22 +146,22 @@ e) テストがまだ合格することを確認
|
|||||||
|
|
||||||
### 1. 未使用のインポート
|
### 1. 未使用のインポート
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ 未使用のインポートを削除
|
// FAIL: 未使用のインポートを削除
|
||||||
import { useState, useEffect, useMemo } from 'react' // useStateのみ使用
|
import { useState, useEffect, useMemo } from 'react' // useStateのみ使用
|
||||||
|
|
||||||
// ✅ 使用されているもののみを保持
|
// PASS: 使用されているもののみを保持
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. デッドコードブランチ
|
### 2. デッドコードブランチ
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ 到達不可能なコードを削除
|
// FAIL: 到達不可能なコードを削除
|
||||||
if (false) {
|
if (false) {
|
||||||
// これは決して実行されない
|
// これは決して実行されない
|
||||||
doSomething()
|
doSomething()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ 未使用の関数を削除
|
// FAIL: 未使用の関数を削除
|
||||||
export function unusedHelper() {
|
export function unusedHelper() {
|
||||||
// コードベースに参照なし
|
// コードベースに参照なし
|
||||||
}
|
}
|
||||||
@@ -169,18 +169,18 @@ export function unusedHelper() {
|
|||||||
|
|
||||||
### 3. 重複コンポーネント
|
### 3. 重複コンポーネント
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ 複数の類似コンポーネント
|
// FAIL: 複数の類似コンポーネント
|
||||||
components/Button.tsx
|
components/Button.tsx
|
||||||
components/PrimaryButton.tsx
|
components/PrimaryButton.tsx
|
||||||
components/NewButton.tsx
|
components/NewButton.tsx
|
||||||
|
|
||||||
// ✅ 1つに統合
|
// PASS: 1つに統合
|
||||||
components/Button.tsx (variantプロップ付き)
|
components/Button.tsx (variantプロップ付き)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. 未使用の依存関係
|
### 4. 未使用の依存関係
|
||||||
```json
|
```json
|
||||||
// ❌ インストールされているがインポートされていないパッケージ
|
// FAIL: インストールされているがインポートされていないパッケージ
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.17.21", // どこでも使用されていない
|
"lodash": "^4.17.21", // どこでも使用されていない
|
||||||
@@ -240,7 +240,7 @@ components/Button.tsx (variantプロップ付き)
|
|||||||
- 依存関係: -Xパッケージ
|
- 依存関係: -Xパッケージ
|
||||||
|
|
||||||
### リスクレベル
|
### リスクレベル
|
||||||
🟢 低 - 検証可能な未使用コードのみを削除
|
低 - 検証可能な未使用コードのみを削除
|
||||||
|
|
||||||
詳細はDELETION_LOG.mdを参照してください。
|
詳細はDELETION_LOG.mdを参照してください。
|
||||||
```
|
```
|
||||||
@@ -294,12 +294,12 @@ components/Button.tsx (variantプロップ付き)
|
|||||||
## 成功指標
|
## 成功指標
|
||||||
|
|
||||||
クリーンアップセッション後:
|
クリーンアップセッション後:
|
||||||
- ✅ すべてのテストが合格
|
- PASS: すべてのテストが合格
|
||||||
- ✅ ビルドが成功
|
- PASS: ビルドが成功
|
||||||
- ✅ コンソールエラーなし
|
- PASS: コンソールエラーなし
|
||||||
- ✅ DELETION_LOG.mdが更新された
|
- PASS: DELETION_LOG.mdが更新された
|
||||||
- ✅ バンドルサイズが削減された
|
- PASS: バンドルサイズが削減された
|
||||||
- ✅ 本番環境で回帰なし
|
- PASS: 本番環境で回帰なし
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -184,12 +184,12 @@ APIセキュリティ:
|
|||||||
### 1. ハードコードされたシークレット(重要)
|
### 1. ハードコードされたシークレット(重要)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ❌ 重要: ハードコードされたシークレット
|
// FAIL: 重要: ハードコードされたシークレット
|
||||||
const apiKey = "sk-proj-xxxxx"
|
const apiKey = "sk-proj-xxxxx"
|
||||||
const password = "admin123"
|
const password = "admin123"
|
||||||
const token = "ghp_xxxxxxxxxxxx"
|
const token = "ghp_xxxxxxxxxxxx"
|
||||||
|
|
||||||
// ✅ 正しい: 環境変数
|
// PASS: 正しい: 環境変数
|
||||||
const apiKey = process.env.OPENAI_API_KEY
|
const apiKey = process.env.OPENAI_API_KEY
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
throw new Error('OPENAI_API_KEY not configured')
|
throw new Error('OPENAI_API_KEY not configured')
|
||||||
@@ -199,11 +199,11 @@ if (!apiKey) {
|
|||||||
### 2. SQLインジェクション(重要)
|
### 2. SQLインジェクション(重要)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ❌ 重要: SQLインジェクションの脆弱性
|
// FAIL: 重要: SQLインジェクションの脆弱性
|
||||||
const query = `SELECT * FROM users WHERE id = ${userId}`
|
const query = `SELECT * FROM users WHERE id = ${userId}`
|
||||||
await db.query(query)
|
await db.query(query)
|
||||||
|
|
||||||
// ✅ 正しい: パラメータ化されたクエリ
|
// PASS: 正しい: パラメータ化されたクエリ
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('users')
|
.from('users')
|
||||||
.select('*')
|
.select('*')
|
||||||
@@ -213,11 +213,11 @@ const { data } = await supabase
|
|||||||
### 3. コマンドインジェクション(重要)
|
### 3. コマンドインジェクション(重要)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ❌ 重要: コマンドインジェクション
|
// FAIL: 重要: コマンドインジェクション
|
||||||
const { exec } = require('child_process')
|
const { exec } = require('child_process')
|
||||||
exec(`ping ${userInput}`, callback)
|
exec(`ping ${userInput}`, callback)
|
||||||
|
|
||||||
// ✅ 正しい: シェルコマンドではなくライブラリを使用
|
// PASS: 正しい: シェルコマンドではなくライブラリを使用
|
||||||
const dns = require('dns')
|
const dns = require('dns')
|
||||||
dns.lookup(userInput, callback)
|
dns.lookup(userInput, callback)
|
||||||
```
|
```
|
||||||
@@ -225,10 +225,10 @@ dns.lookup(userInput, callback)
|
|||||||
### 4. クロスサイトスクリプティング(XSS)(高)
|
### 4. クロスサイトスクリプティング(XSS)(高)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ❌ 高: XSS脆弱性
|
// FAIL: 高: XSS脆弱性
|
||||||
element.innerHTML = userInput
|
element.innerHTML = userInput
|
||||||
|
|
||||||
// ✅ 正しい: textContentを使用またはサニタイズ
|
// PASS: 正しい: textContentを使用またはサニタイズ
|
||||||
element.textContent = userInput
|
element.textContent = userInput
|
||||||
// または
|
// または
|
||||||
import DOMPurify from 'dompurify'
|
import DOMPurify from 'dompurify'
|
||||||
@@ -238,10 +238,10 @@ element.innerHTML = DOMPurify.sanitize(userInput)
|
|||||||
### 5. サーバーサイドリクエストフォージェリ(SSRF)(高)
|
### 5. サーバーサイドリクエストフォージェリ(SSRF)(高)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ❌ 高: SSRF脆弱性
|
// FAIL: 高: SSRF脆弱性
|
||||||
const response = await fetch(userProvidedUrl)
|
const response = await fetch(userProvidedUrl)
|
||||||
|
|
||||||
// ✅ 正しい: URLを検証してホワイトリスト
|
// PASS: 正しい: URLを検証してホワイトリスト
|
||||||
const allowedDomains = ['api.example.com', 'cdn.example.com']
|
const allowedDomains = ['api.example.com', 'cdn.example.com']
|
||||||
const url = new URL(userProvidedUrl)
|
const url = new URL(userProvidedUrl)
|
||||||
if (!allowedDomains.includes(url.hostname)) {
|
if (!allowedDomains.includes(url.hostname)) {
|
||||||
@@ -253,10 +253,10 @@ const response = await fetch(url.toString())
|
|||||||
### 6. 安全でない認証(重要)
|
### 6. 安全でない認証(重要)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ❌ 重要: 平文パスワード比較
|
// FAIL: 重要: 平文パスワード比較
|
||||||
if (password === storedPassword) { /* ログイン */ }
|
if (password === storedPassword) { /* ログイン */ }
|
||||||
|
|
||||||
// ✅ 正しい: ハッシュ化されたパスワード比較
|
// PASS: 正しい: ハッシュ化されたパスワード比較
|
||||||
import bcrypt from 'bcrypt'
|
import bcrypt from 'bcrypt'
|
||||||
const isValid = await bcrypt.compare(password, hashedPassword)
|
const isValid = await bcrypt.compare(password, hashedPassword)
|
||||||
```
|
```
|
||||||
@@ -264,13 +264,13 @@ const isValid = await bcrypt.compare(password, hashedPassword)
|
|||||||
### 7. 不十分な認可(重要)
|
### 7. 不十分な認可(重要)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ❌ 重要: 認可チェックなし
|
// FAIL: 重要: 認可チェックなし
|
||||||
app.get('/api/user/:id', async (req, res) => {
|
app.get('/api/user/:id', async (req, res) => {
|
||||||
const user = await getUser(req.params.id)
|
const user = await getUser(req.params.id)
|
||||||
res.json(user)
|
res.json(user)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ✅ 正しい: ユーザーがリソースにアクセスできることを確認
|
// PASS: 正しい: ユーザーがリソースにアクセスできることを確認
|
||||||
app.get('/api/user/:id', authenticateUser, async (req, res) => {
|
app.get('/api/user/:id', authenticateUser, async (req, res) => {
|
||||||
if (req.user.id !== req.params.id && !req.user.isAdmin) {
|
if (req.user.id !== req.params.id && !req.user.isAdmin) {
|
||||||
return res.status(403).json({ error: 'Forbidden' })
|
return res.status(403).json({ error: 'Forbidden' })
|
||||||
@@ -283,13 +283,13 @@ app.get('/api/user/:id', authenticateUser, async (req, res) => {
|
|||||||
### 8. 金融操作の競合状態(重要)
|
### 8. 金融操作の競合状態(重要)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ❌ 重要: 残高チェックの競合状態
|
// FAIL: 重要: 残高チェックの競合状態
|
||||||
const balance = await getBalance(userId)
|
const balance = await getBalance(userId)
|
||||||
if (balance >= amount) {
|
if (balance >= amount) {
|
||||||
await withdraw(userId, amount) // 別のリクエストが並行して出金できる!
|
await withdraw(userId, amount) // 別のリクエストが並行して出金できる!
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 正しい: ロック付きアトミックトランザクション
|
// PASS: 正しい: ロック付きアトミックトランザクション
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
const balance = await trx('balances')
|
const balance = await trx('balances')
|
||||||
.where({ user_id: userId })
|
.where({ user_id: userId })
|
||||||
@@ -309,13 +309,13 @@ await db.transaction(async (trx) => {
|
|||||||
### 9. 不十分なレート制限(高)
|
### 9. 不十分なレート制限(高)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ❌ 高: レート制限なし
|
// FAIL: 高: レート制限なし
|
||||||
app.post('/api/trade', async (req, res) => {
|
app.post('/api/trade', async (req, res) => {
|
||||||
await executeTrade(req.body)
|
await executeTrade(req.body)
|
||||||
res.json({ success: true })
|
res.json({ success: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
// ✅ 正しい: レート制限
|
// PASS: 正しい: レート制限
|
||||||
import rateLimit from 'express-rate-limit'
|
import rateLimit from 'express-rate-limit'
|
||||||
|
|
||||||
const tradeLimiter = rateLimit({
|
const tradeLimiter = rateLimit({
|
||||||
@@ -333,10 +333,10 @@ app.post('/api/trade', tradeLimiter, async (req, res) => {
|
|||||||
### 10. 機密データのロギング(中)
|
### 10. 機密データのロギング(中)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ❌ 中: 機密データのロギング
|
// FAIL: 中: 機密データのロギング
|
||||||
console.log('User login:', { email, password, apiKey })
|
console.log('User login:', { email, password, apiKey })
|
||||||
|
|
||||||
// ✅ 正しい: ログをサニタイズ
|
// PASS: 正しい: ログをサニタイズ
|
||||||
console.log('User login:', {
|
console.log('User login:', {
|
||||||
email: email.replace(/(?<=.).(?=.*@)/g, '*'),
|
email: email.replace(/(?<=.).(?=.*@)/g, '*'),
|
||||||
passwordProvided: !!password
|
passwordProvided: !!password
|
||||||
@@ -358,7 +358,7 @@ console.log('User login:', {
|
|||||||
- **高い問題:** Y
|
- **高い問題:** Y
|
||||||
- **中程度の問題:** Z
|
- **中程度の問題:** Z
|
||||||
- **低い問題:** W
|
- **低い問題:** W
|
||||||
- **リスクレベル:** 🔴 高 / 🟡 中 / 🟢 低
|
- **リスクレベル:** 高 / 中 / 低
|
||||||
|
|
||||||
## 重要な問題(即座に修正)
|
## 重要な問題(即座に修正)
|
||||||
|
|
||||||
@@ -380,7 +380,7 @@ console.log('User login:', {
|
|||||||
|
|
||||||
**修復:**
|
**修復:**
|
||||||
```javascript
|
```javascript
|
||||||
// ✅ 安全な実装
|
// PASS: 安全な実装
|
||||||
```
|
```
|
||||||
|
|
||||||
**参考資料:**
|
**参考資料:**
|
||||||
@@ -433,7 +433,7 @@ PRをレビューする際、インラインコメントを投稿:
|
|||||||
## セキュリティレビュー
|
## セキュリティレビュー
|
||||||
|
|
||||||
**レビューアー:** security-reviewer agent
|
**レビューアー:** security-reviewer agent
|
||||||
**リスクレベル:** 🔴 高 / 🟡 中 / 🟢 低
|
**リスクレベル:** 高 / 中 / 低
|
||||||
|
|
||||||
### ブロッキング問題
|
### ブロッキング問題
|
||||||
- [ ] **重要**: [説明] @ `file:line`
|
- [ ] **重要**: [説明] @ `file:line`
|
||||||
@@ -532,13 +532,13 @@ npm install --save-dev audit-ci
|
|||||||
## 成功指標
|
## 成功指標
|
||||||
|
|
||||||
セキュリティレビュー後:
|
セキュリティレビュー後:
|
||||||
- ✅ 重要な問題が見つからない
|
- PASS: 重要な問題が見つからない
|
||||||
- ✅ すべての高い問題が対処されている
|
- PASS: すべての高い問題が対処されている
|
||||||
- ✅ セキュリティチェックリストが完了
|
- PASS: セキュリティチェックリストが完了
|
||||||
- ✅ コードにシークレットがない
|
- PASS: コードにシークレットがない
|
||||||
- ✅ 依存関係が最新
|
- PASS: 依存関係が最新
|
||||||
- ✅ テストにセキュリティシナリオが含まれている
|
- PASS: テストにセキュリティシナリオが含まれている
|
||||||
- ✅ ドキュメントが更新されている
|
- PASS: ドキュメントが更新されている
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -220,26 +220,26 @@ jest.mock('@/lib/openai', () => ({
|
|||||||
|
|
||||||
## テストの悪臭(アンチパターン)
|
## テストの悪臭(アンチパターン)
|
||||||
|
|
||||||
### ❌ 実装の詳細をテスト
|
### FAIL: 実装の詳細をテスト
|
||||||
```typescript
|
```typescript
|
||||||
// 内部状態をテストしない
|
// 内部状態をテストしない
|
||||||
expect(component.state.count).toBe(5)
|
expect(component.state.count).toBe(5)
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ ユーザーに見える動作をテスト
|
### PASS: ユーザーに見える動作をテスト
|
||||||
```typescript
|
```typescript
|
||||||
// ユーザーが見るものをテストする
|
// ユーザーが見るものをテストする
|
||||||
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
||||||
```
|
```
|
||||||
|
|
||||||
### ❌ テストが互いに依存
|
### FAIL: テストが互いに依存
|
||||||
```typescript
|
```typescript
|
||||||
// 前のテストに依存しない
|
// 前のテストに依存しない
|
||||||
test('creates user', () => { /* ... */ })
|
test('creates user', () => { /* ... */ })
|
||||||
test('updates same user', () => { /* 前のテストが必要 */ })
|
test('updates same user', () => { /* 前のテストが必要 */ })
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ 独立したテスト
|
### PASS: 独立したテスト
|
||||||
```typescript
|
```typescript
|
||||||
// 各テストでデータをセットアップ
|
// 各テストでデータをセットアップ
|
||||||
test('updates user', () => {
|
test('updates user', () => {
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ Artifacts generated:
|
|||||||
╔══════════════════════════════════════════════════════════════╗
|
╔══════════════════════════════════════════════════════════════╗
|
||||||
║ E2Eテスト結果 ║
|
║ E2Eテスト結果 ║
|
||||||
╠══════════════════════════════════════════════════════════════╣
|
╠══════════════════════════════════════════════════════════════╣
|
||||||
║ ステータス: ✅ 全テスト合格 ║
|
║ ステータス: PASS: 全テスト合格 ║
|
||||||
║ 合計: 3テスト ║
|
║ 合計: 3テスト ║
|
||||||
║ 合格: 3 (100%) ║
|
║ 合格: 3 (100%) ║
|
||||||
║ 失敗: 0 ║
|
║ 失敗: 0 ║
|
||||||
@@ -192,15 +192,15 @@ Artifacts generated:
|
|||||||
╚══════════════════════════════════════════════════════════════╝
|
╚══════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
アーティファクト:
|
アーティファクト:
|
||||||
📸 スクリーンショット: 2ファイル
|
スクリーンショット: 2ファイル
|
||||||
📹 ビデオ: 0ファイル (失敗時のみ)
|
ビデオ: 0ファイル (失敗時のみ)
|
||||||
🔍 トレース: 0ファイル (失敗時のみ)
|
トレース: 0ファイル (失敗時のみ)
|
||||||
📊 HTMLレポート: playwright-report/index.html
|
HTMLレポート: playwright-report/index.html
|
||||||
|
|
||||||
レポート表示: npx playwright show-report
|
レポート表示: npx playwright show-report
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ E2E テストスイートは CI/CD 統合の準備ができました!
|
PASS: E2E テストスイートは CI/CD 統合の準備ができました!
|
||||||
|
|
||||||
````
|
````
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ open artifacts/search-results.png
|
|||||||
テストが断続的に失敗する場合:
|
テストが断続的に失敗する場合:
|
||||||
|
|
||||||
```
|
```
|
||||||
⚠️ FLAKY TEST DETECTED: tests/e2e/markets/trade.spec.ts
|
WARNING: FLAKY TEST DETECTED: tests/e2e/markets/trade.spec.ts
|
||||||
|
|
||||||
テストは10回中7回合格 (合格率70%)
|
テストは10回中7回合格 (合格率70%)
|
||||||
|
|
||||||
@@ -257,10 +257,10 @@ open artifacts/search-results.png
|
|||||||
|
|
||||||
デフォルトでは、テストは複数のブラウザで実行されます:
|
デフォルトでは、テストは複数のブラウザで実行されます:
|
||||||
|
|
||||||
* ✅ Chromium(デスクトップ Chrome)
|
* PASS: Chromium(デスクトップ Chrome)
|
||||||
* ✅ Firefox(デスクトップ)
|
* PASS: Firefox(デスクトップ)
|
||||||
* ✅ WebKit(デスクトップ Safari)
|
* PASS: WebKit(デスクトップ Safari)
|
||||||
* ✅ Mobile Chrome(オプション)
|
* PASS: Mobile Chrome(オプション)
|
||||||
|
|
||||||
`playwright.config.ts` で設定してブラウザを調整します。
|
`playwright.config.ts` で設定してブラウザを調整します。
|
||||||
|
|
||||||
@@ -288,7 +288,7 @@ CI パイプラインに追加:
|
|||||||
|
|
||||||
PMX の場合、以下の E2E テストを優先:
|
PMX の場合、以下の E2E テストを優先:
|
||||||
|
|
||||||
**🔴 重大(常に成功する必要):**
|
**重大(常に成功する必要):**
|
||||||
|
|
||||||
1. ユーザーがウォレットを接続できる
|
1. ユーザーがウォレットを接続できる
|
||||||
2. ユーザーが市場をブラウズできる
|
2. ユーザーが市場をブラウズできる
|
||||||
@@ -298,7 +298,7 @@ PMX の場合、以下の E2E テストを優先:
|
|||||||
6. 市場が正しく決済される
|
6. 市場が正しく決済される
|
||||||
7. ユーザーが資金を引き出せる
|
7. ユーザーが資金を引き出せる
|
||||||
|
|
||||||
**🟡 重要:**
|
**重要:**
|
||||||
|
|
||||||
1. 市場作成フロー
|
1. 市場作成フロー
|
||||||
2. ユーザープロフィール更新
|
2. ユーザープロフィール更新
|
||||||
@@ -311,21 +311,21 @@ PMX の場合、以下の E2E テストを優先:
|
|||||||
|
|
||||||
**すべき事:**
|
**すべき事:**
|
||||||
|
|
||||||
* ✅ 保守性を高めるためページオブジェクトモデルを使用します
|
* PASS: 保守性を高めるためページオブジェクトモデルを使用します
|
||||||
* ✅ セレクタとして data-testid 属性を使用します
|
* PASS: セレクタとして data-testid 属性を使用します
|
||||||
* ✅ 任意のタイムアウトではなく API レスポンスを待機
|
* PASS: 任意のタイムアウトではなく API レスポンスを待機
|
||||||
* ✅ 重要なユーザージャーニーのエンドツーエンドテスト
|
* PASS: 重要なユーザージャーニーのエンドツーエンドテスト
|
||||||
* ✅ main にマージする前にテストを実行
|
* PASS: main にマージする前にテストを実行
|
||||||
* ✅ テスト失敗時にアーティファクトをレビュー
|
* PASS: テスト失敗時にアーティファクトをレビュー
|
||||||
|
|
||||||
**すべきでない事:**
|
**すべきでない事:**
|
||||||
|
|
||||||
* ❌ 不安定なセレクタを使用します(CSS クラスは変わる可能性)
|
* FAIL: 不安定なセレクタを使用します(CSS クラスは変わる可能性)
|
||||||
* ❌ 実装の詳細をテスト
|
* FAIL: 実装の詳細をテスト
|
||||||
* ❌ 本番環境に対してテストを実行
|
* FAIL: 本番環境に対してテストを実行
|
||||||
* ❌ 不安定なテストを無視
|
* FAIL: 不安定なテストを無視
|
||||||
* ❌ 失敗時にアーティファクトレビューをスキップ
|
* FAIL: 失敗時にアーティファクトレビューをスキップ
|
||||||
* ❌ E2E テストですべてのエッジケースをテスト(単体テストを使用します)
|
* FAIL: E2E テストですべてのエッジケースをテスト(単体テストを使用します)
|
||||||
|
|
||||||
## 重要な注意事項
|
## 重要な注意事項
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ instinctsが分離の恩恵を受ける複雑な複数ステップのプロセ
|
|||||||
## 出力フォーマット
|
## 出力フォーマット
|
||||||
|
|
||||||
```
|
```
|
||||||
🧬 進化分析
|
進化分析
|
||||||
==================
|
==================
|
||||||
|
|
||||||
進化の準備ができた3つのクラスターを発見:
|
進化の準備ができた3つのクラスターを発見:
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ ok project/internal/handler 0.023s
|
|||||||
| 変更されたファイル | 2 |
|
| 変更されたファイル | 2 |
|
||||||
| 残存問題 | 0 |
|
| 残存問題 | 0 |
|
||||||
|
|
||||||
ビルドステータス: ✅ 成功
|
ビルドステータス: PASS: 成功
|
||||||
```
|
```
|
||||||
|
|
||||||
## 修正される一般的なエラー
|
## 修正される一般的なエラー
|
||||||
|
|||||||
@@ -124,16 +124,16 @@ return fmt.Errorf("get user %s: %w", userID, err)
|
|||||||
- HIGH: 1
|
- HIGH: 1
|
||||||
- MEDIUM: 0
|
- MEDIUM: 0
|
||||||
|
|
||||||
推奨: ❌ CRITICAL問題が修正されるまでマージをブロック
|
推奨: FAIL: CRITICAL問題が修正されるまでマージをブロック
|
||||||
```
|
```
|
||||||
|
|
||||||
## 承認基準
|
## 承認基準
|
||||||
|
|
||||||
| ステータス | 条件 |
|
| ステータス | 条件 |
|
||||||
|--------|-----------|
|
|--------|-----------|
|
||||||
| ✅ 承認 | CRITICALまたはHIGH問題なし |
|
| PASS: 承認 | CRITICALまたはHIGH問題なし |
|
||||||
| ⚠️ 警告 | MEDIUM問題のみ(注意してマージ) |
|
| WARNING: 警告 | MEDIUM問題のみ(注意してマージ) |
|
||||||
| ❌ ブロック | CRITICALまたはHIGH問題が発見された |
|
| FAIL: ブロック | CRITICALまたはHIGH問題が発見された |
|
||||||
|
|
||||||
## 他のコマンドとの統合
|
## 他のコマンドとの統合
|
||||||
|
|
||||||
|
|||||||
@@ -70,17 +70,17 @@ instincts:
|
|||||||
## プライバシーに関する考慮事項
|
## プライバシーに関する考慮事項
|
||||||
|
|
||||||
エクスポートに含まれる内容:
|
エクスポートに含まれる内容:
|
||||||
- ✅ トリガーパターン
|
- PASS: トリガーパターン
|
||||||
- ✅ アクション
|
- PASS: アクション
|
||||||
- ✅ 信頼度スコア
|
- PASS: 信頼度スコア
|
||||||
- ✅ ドメイン
|
- PASS: ドメイン
|
||||||
- ✅ 観察回数
|
- PASS: 観察回数
|
||||||
|
|
||||||
エクスポートに含まれない内容:
|
エクスポートに含まれない内容:
|
||||||
- ❌ 実際のコードスニペット
|
- FAIL: 実際のコードスニペット
|
||||||
- ❌ ファイルパス
|
- FAIL: ファイルパス
|
||||||
- ❌ セッション記録
|
- FAIL: セッション記録
|
||||||
- ❌ 個人識別情報
|
- FAIL: 個人識別情報
|
||||||
|
|
||||||
## フラグ
|
## フラグ
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py import <
|
|||||||
## インポートプロセス
|
## インポートプロセス
|
||||||
|
|
||||||
```
|
```
|
||||||
📥 instinctsをインポート中: team-instincts.yaml
|
instinctsをインポート中: team-instincts.yaml
|
||||||
================================================
|
================================================
|
||||||
|
|
||||||
12件のinstinctsが見つかりました。
|
12件のinstinctsが見つかりました。
|
||||||
@@ -61,19 +61,19 @@ python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py import <
|
|||||||
|
|
||||||
## 重複instincts (3)
|
## 重複instincts (3)
|
||||||
類似のinstinctsが既に存在:
|
類似のinstinctsが既に存在:
|
||||||
⚠️ prefer-functional-style
|
WARNING: prefer-functional-style
|
||||||
ローカル: 信頼度0.8, 12回の観測
|
ローカル: 信頼度0.8, 12回の観測
|
||||||
インポート: 信頼度0.7
|
インポート: 信頼度0.7
|
||||||
→ ローカルを保持 (信頼度が高い)
|
→ ローカルを保持 (信頼度が高い)
|
||||||
|
|
||||||
⚠️ test-first-workflow
|
WARNING: test-first-workflow
|
||||||
ローカル: 信頼度0.75
|
ローカル: 信頼度0.75
|
||||||
インポート: 信頼度0.9
|
インポート: 信頼度0.9
|
||||||
→ インポートに更新 (信頼度が高い)
|
→ インポートに更新 (信頼度が高い)
|
||||||
|
|
||||||
## 競合instincts (1)
|
## 競合instincts (1)
|
||||||
ローカルのinstinctsと矛盾:
|
ローカルのinstinctsと矛盾:
|
||||||
❌ use-classes-for-services
|
FAIL: use-classes-for-services
|
||||||
競合: avoid-classes
|
競合: avoid-classes
|
||||||
→ スキップ (手動解決が必要)
|
→ スキップ (手動解決が必要)
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ Skill Creatorからインポートする場合:
|
|||||||
|
|
||||||
インポート後:
|
インポート後:
|
||||||
```
|
```
|
||||||
✅ インポート完了!
|
PASS: インポート完了!
|
||||||
|
|
||||||
追加: 8件のinstincts
|
追加: 8件のinstincts
|
||||||
更新: 1件のinstinct
|
更新: 1件のinstinct
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status
|
|||||||
## 出力形式
|
## 出力形式
|
||||||
|
|
||||||
```
|
```
|
||||||
📊 instinctステータス
|
instinctステータス
|
||||||
==================
|
==================
|
||||||
|
|
||||||
## コードスタイル (4 instincts)
|
## コードスタイル (4 instincts)
|
||||||
|
|||||||
@@ -204,9 +204,9 @@ Claudeの統合計画での欠落リスクを減らすために、両方のモ
|
|||||||
3. **太字テキスト**でプロンプトを出力(**保存された実際のファイルパスを使用する必要があります**):
|
3. **太字テキスト**でプロンプトを出力(**保存された実際のファイルパスを使用する必要があります**):
|
||||||
|
|
||||||
---
|
---
|
||||||
**計画が生成され、`.claude/plan/actual-feature-name.md`に保存されました**
|
**計画が生成され、`.claude/plan/actual-feature-name.md`に保存されました**
|
||||||
|
|
||||||
**上記の計画をレビューしてください。以下のことができます:**
|
**上記の計画をレビューしてください。以下のことができます:**
|
||||||
- **計画を変更**: 調整が必要なことを教えてください、計画を更新します
|
- **計画を変更**: 調整が必要なことを教えてください、計画を更新します
|
||||||
- **計画を実行**: 以下のコマンドを新しいセッションにコピー
|
- **計画を実行**: 以下のコマンドを新しいセッションにコピー
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ Claudeの統合計画での欠落リスクを減らすために、両方のモ
|
|||||||
```
|
```
|
||||||
---
|
---
|
||||||
|
|
||||||
**注意**: 上記の`actual-feature-name.md`は実際に保存されたファイル名で置き換える必要があります!
|
**注意**: 上記の`actual-feature-name.md`は実際に保存されたファイル名で置き換える必要があります!
|
||||||
|
|
||||||
4. **現在のレスポンスを直ちに終了**(ここで停止。これ以上のツール呼び出しはありません。)
|
4. **現在のレスポンスを直ちに終了**(ここで停止。これ以上のツール呼び出しはありません。)
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ Agent:
|
|||||||
## 静的解析結果
|
## 静的解析結果
|
||||||
✓ ruff: 問題なし
|
✓ ruff: 問題なし
|
||||||
✓ mypy: エラーなし
|
✓ mypy: エラーなし
|
||||||
⚠️ black: 2ファイルが再フォーマット必要
|
WARNING: black: 2ファイルが再フォーマット必要
|
||||||
✓ bandit: セキュリティ問題なし
|
✓ bandit: セキュリティ問題なし
|
||||||
|
|
||||||
## 発見された問題
|
## 発見された問題
|
||||||
@@ -155,7 +155,7 @@ with open("config.json") as f: # 良い
|
|||||||
- HIGH: 1
|
- HIGH: 1
|
||||||
- MEDIUM: 2
|
- MEDIUM: 2
|
||||||
|
|
||||||
推奨: ❌ CRITICAL問題が修正されるまでマージをブロック
|
推奨: FAIL: CRITICAL問題が修正されるまでマージをブロック
|
||||||
|
|
||||||
## フォーマット必要
|
## フォーマット必要
|
||||||
実行: `black app/routes/user.py app/services/auth.py`
|
実行: `black app/routes/user.py app/services/auth.py`
|
||||||
@@ -165,9 +165,9 @@ with open("config.json") as f: # 良い
|
|||||||
|
|
||||||
| ステータス | 条件 |
|
| ステータス | 条件 |
|
||||||
|--------|-----------|
|
|--------|-----------|
|
||||||
| ✅ 承認 | CRITICALまたはHIGH問題なし |
|
| PASS: 承認 | CRITICALまたはHIGH問題なし |
|
||||||
| ⚠️ 警告 | MEDIUM問題のみ(注意してマージ) |
|
| WARNING: 警告 | MEDIUM問題のみ(注意してマージ) |
|
||||||
| ❌ ブロック | CRITICALまたはHIGH問題が発見された |
|
| FAIL: ブロック | CRITICALまたはHIGH問題が発見された |
|
||||||
|
|
||||||
## 他のコマンドとの統合
|
## 他のコマンドとの統合
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ FAIL lib/liquidity.test.ts
|
|||||||
1 test failed, 0 passed
|
1 test failed, 0 passed
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ テストは期待通りに失敗しました。実装の準備ができました。
|
PASS: テストは期待通りに失敗しました。実装の準備ができました。
|
||||||
|
|
||||||
## ステップ4: 最小限のコードを実装(GREEN)
|
## ステップ4: 最小限のコードを実装(GREEN)
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ PASS lib/liquidity.test.ts
|
|||||||
3 tests passed
|
3 tests passed
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ すべてのテストが合格しました!
|
PASS: すべてのテストが合格しました!
|
||||||
|
|
||||||
## ステップ6: リファクタリング(IMPROVE)
|
## ステップ6: リファクタリング(IMPROVE)
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ PASS lib/liquidity.test.ts
|
|||||||
3 tests passed
|
3 tests passed
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ リファクタリング完了、テストはまだ合格しています!
|
PASS: リファクタリング完了、テストはまだ合格しています!
|
||||||
|
|
||||||
## ステップ8: カバレッジの確認
|
## ステップ8: カバレッジの確認
|
||||||
|
|
||||||
@@ -247,29 +247,29 @@ File | % Stmts | % Branch | % Funcs | % Lines
|
|||||||
---------------|---------|----------|---------|--------
|
---------------|---------|----------|---------|--------
|
||||||
liquidity.ts | 100 | 100 | 100 | 100
|
liquidity.ts | 100 | 100 | 100 | 100
|
||||||
|
|
||||||
Coverage: 100% ✅ (Target: 80%)
|
Coverage: 100% PASS: (Target: 80%)
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ TDDセッション完了!
|
PASS: TDDセッション完了!
|
||||||
```
|
```
|
||||||
|
|
||||||
## TDDベストプラクティス
|
## TDDベストプラクティス
|
||||||
|
|
||||||
**すべきこと:**
|
**すべきこと:**
|
||||||
- ✅ 実装の前にまずテストを書く
|
- PASS: 実装の前にまずテストを書く
|
||||||
- ✅ テストを実行し、実装前に失敗することを確認
|
- PASS: テストを実行し、実装前に失敗することを確認
|
||||||
- ✅ テストに合格するための最小限のコードを書く
|
- PASS: テストに合格するための最小限のコードを書く
|
||||||
- ✅ テストが緑色になってからのみリファクタリング
|
- PASS: テストが緑色になってからのみリファクタリング
|
||||||
- ✅ エッジケースとエラーシナリオを追加
|
- PASS: エッジケースとエラーシナリオを追加
|
||||||
- ✅ 80%以上のカバレッジを目指す(重要なコードは100%)
|
- PASS: 80%以上のカバレッジを目指す(重要なコードは100%)
|
||||||
|
|
||||||
**してはいけないこと:**
|
**してはいけないこと:**
|
||||||
- ❌ テストの前に実装を書く
|
- FAIL: テストの前に実装を書く
|
||||||
- ❌ 各変更後のテスト実行をスキップ
|
- FAIL: 各変更後のテスト実行をスキップ
|
||||||
- ❌ 一度に多くのコードを書く
|
- FAIL: 一度に多くのコードを書く
|
||||||
- ❌ 失敗するテストを無視
|
- FAIL: 失敗するテストを無視
|
||||||
- ❌ 実装の詳細をテスト(動作をテスト)
|
- FAIL: 実装の詳細をテスト(動作をテスト)
|
||||||
- ❌ すべてをモック化(統合テストを優先)
|
- FAIL: すべてをモック化(統合テストを優先)
|
||||||
|
|
||||||
## 含めるべきテストタイプ
|
## 含めるべきテストタイプ
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ description: Backend architecture patterns, API design, database optimization, a
|
|||||||
### RESTful API構造
|
### RESTful API構造
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ リソースベースのURL
|
// PASS: リソースベースのURL
|
||||||
GET /api/markets # リソースのリスト
|
GET /api/markets # リソースのリスト
|
||||||
GET /api/markets/:id # 単一リソースの取得
|
GET /api/markets/:id # 単一リソースの取得
|
||||||
POST /api/markets # リソースの作成
|
POST /api/markets # リソースの作成
|
||||||
@@ -20,7 +20,7 @@ PUT /api/markets/:id # リソースの置換
|
|||||||
PATCH /api/markets/:id # リソースの更新
|
PATCH /api/markets/:id # リソースの更新
|
||||||
DELETE /api/markets/:id # リソースの削除
|
DELETE /api/markets/:id # リソースの削除
|
||||||
|
|
||||||
// ✅ フィルタリング、ソート、ページネーション用のクエリパラメータ
|
// PASS: フィルタリング、ソート、ページネーション用のクエリパラメータ
|
||||||
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ export default withAuth(async (req, res) => {
|
|||||||
### クエリ最適化
|
### クエリ最適化
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ 良い: 必要な列のみを選択
|
// PASS: 良い: 必要な列のみを選択
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('id, name, status, volume')
|
.select('id, name, status, volume')
|
||||||
@@ -128,7 +128,7 @@ const { data } = await supabase
|
|||||||
.order('volume', { ascending: false })
|
.order('volume', { ascending: false })
|
||||||
.limit(10)
|
.limit(10)
|
||||||
|
|
||||||
// ❌ 悪い: すべてを選択
|
// FAIL: 悪い: すべてを選択
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('*')
|
.select('*')
|
||||||
@@ -137,13 +137,13 @@ const { data } = await supabase
|
|||||||
### N+1クエリ防止
|
### N+1クエリ防止
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ 悪い: N+1クエリ問題
|
// FAIL: 悪い: N+1クエリ問題
|
||||||
const markets = await getMarkets()
|
const markets = await getMarkets()
|
||||||
for (const market of markets) {
|
for (const market of markets) {
|
||||||
market.creator = await getUser(market.creator_id) // Nクエリ
|
market.creator = await getUser(market.creator_id) // Nクエリ
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 良い: バッチフェッチ
|
// PASS: 良い: バッチフェッチ
|
||||||
const markets = await getMarkets()
|
const markets = await getMarkets()
|
||||||
const creatorIds = markets.map(m => m.creator_id)
|
const creatorIds = markets.map(m => m.creator_id)
|
||||||
const creators = await getUsers(creatorIds) // 1クエリ
|
const creators = await getUsers(creatorIds) // 1クエリ
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ ORDER BY hour DESC;
|
|||||||
### 効率的なフィルタリング
|
### 効率的なフィルタリング
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ✅ 良い: インデックス列を最初に使用
|
-- PASS: 良い: インデックス列を最初に使用
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM markets_analytics
|
FROM markets_analytics
|
||||||
WHERE date >= '2025-01-01'
|
WHERE date >= '2025-01-01'
|
||||||
@@ -95,7 +95,7 @@ WHERE date >= '2025-01-01'
|
|||||||
ORDER BY date DESC
|
ORDER BY date DESC
|
||||||
LIMIT 100;
|
LIMIT 100;
|
||||||
|
|
||||||
-- ❌ 悪い: インデックスのない列を最初にフィルタリング
|
-- FAIL: 悪い: インデックスのない列を最初にフィルタリング
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM markets_analytics
|
FROM markets_analytics
|
||||||
WHERE volume > 1000
|
WHERE volume > 1000
|
||||||
@@ -106,7 +106,7 @@ WHERE volume > 1000
|
|||||||
### 集計
|
### 集計
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ✅ 良い: ClickHouse固有の集計関数を使用
|
-- PASS: 良い: ClickHouse固有の集計関数を使用
|
||||||
SELECT
|
SELECT
|
||||||
toStartOfDay(created_at) AS day,
|
toStartOfDay(created_at) AS day,
|
||||||
market_id,
|
market_id,
|
||||||
@@ -119,7 +119,7 @@ WHERE created_at >= today() - INTERVAL 7 DAY
|
|||||||
GROUP BY day, market_id
|
GROUP BY day, market_id
|
||||||
ORDER BY day DESC, total_volume DESC;
|
ORDER BY day DESC, total_volume DESC;
|
||||||
|
|
||||||
-- ✅ パーセンタイルにはquantileを使用(percentileより効率的)
|
-- PASS: パーセンタイルにはquantileを使用(percentileより効率的)
|
||||||
SELECT
|
SELECT
|
||||||
quantile(0.50)(trade_size) AS median,
|
quantile(0.50)(trade_size) AS median,
|
||||||
quantile(0.95)(trade_size) AS p95,
|
quantile(0.95)(trade_size) AS p95,
|
||||||
@@ -162,7 +162,7 @@ const clickhouse = new ClickHouse({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// ✅ バッチ挿入(効率的)
|
// PASS: バッチ挿入(効率的)
|
||||||
async function bulkInsertTrades(trades: Trade[]) {
|
async function bulkInsertTrades(trades: Trade[]) {
|
||||||
const values = trades.map(trade => `(
|
const values = trades.map(trade => `(
|
||||||
'${trade.id}',
|
'${trade.id}',
|
||||||
@@ -178,7 +178,7 @@ async function bulkInsertTrades(trades: Trade[]) {
|
|||||||
`).toPromise()
|
`).toPromise()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ 個別挿入(低速)
|
// FAIL: 個別挿入(低速)
|
||||||
async function insertTrade(trade: Trade) {
|
async function insertTrade(trade: Trade) {
|
||||||
// ループ内でこれをしないでください!
|
// ループ内でこれをしないでください!
|
||||||
await clickhouse.query(`
|
await clickhouse.query(`
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ description: TypeScript、JavaScript、React、Node.js開発のための汎用
|
|||||||
### 変数の命名
|
### 変数の命名
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Descriptive names
|
// PASS: GOOD: Descriptive names
|
||||||
const marketSearchQuery = 'election'
|
const marketSearchQuery = 'election'
|
||||||
const isUserAuthenticated = true
|
const isUserAuthenticated = true
|
||||||
const totalRevenue = 1000
|
const totalRevenue = 1000
|
||||||
|
|
||||||
// ❌ BAD: Unclear names
|
// FAIL: BAD: Unclear names
|
||||||
const q = 'election'
|
const q = 'election'
|
||||||
const flag = true
|
const flag = true
|
||||||
const x = 1000
|
const x = 1000
|
||||||
@@ -56,12 +56,12 @@ const x = 1000
|
|||||||
### 関数の命名
|
### 関数の命名
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Verb-noun pattern
|
// PASS: GOOD: Verb-noun pattern
|
||||||
async function fetchMarketData(marketId: string) { }
|
async function fetchMarketData(marketId: string) { }
|
||||||
function calculateSimilarity(a: number[], b: number[]) { }
|
function calculateSimilarity(a: number[], b: number[]) { }
|
||||||
function isValidEmail(email: string): boolean { }
|
function isValidEmail(email: string): boolean { }
|
||||||
|
|
||||||
// ❌ BAD: Unclear or noun-only
|
// FAIL: BAD: Unclear or noun-only
|
||||||
async function market(id: string) { }
|
async function market(id: string) { }
|
||||||
function similarity(a, b) { }
|
function similarity(a, b) { }
|
||||||
function email(e) { }
|
function email(e) { }
|
||||||
@@ -70,7 +70,7 @@ function email(e) { }
|
|||||||
### 不変性パターン(重要)
|
### 不変性パターン(重要)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ ALWAYS use spread operator
|
// PASS: ALWAYS use spread operator
|
||||||
const updatedUser = {
|
const updatedUser = {
|
||||||
...user,
|
...user,
|
||||||
name: 'New Name'
|
name: 'New Name'
|
||||||
@@ -78,7 +78,7 @@ const updatedUser = {
|
|||||||
|
|
||||||
const updatedArray = [...items, newItem]
|
const updatedArray = [...items, newItem]
|
||||||
|
|
||||||
// ❌ NEVER mutate directly
|
// FAIL: NEVER mutate directly
|
||||||
user.name = 'New Name' // BAD
|
user.name = 'New Name' // BAD
|
||||||
items.push(newItem) // BAD
|
items.push(newItem) // BAD
|
||||||
```
|
```
|
||||||
@@ -86,7 +86,7 @@ items.push(newItem) // BAD
|
|||||||
### エラーハンドリング
|
### エラーハンドリング
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Comprehensive error handling
|
// PASS: GOOD: Comprehensive error handling
|
||||||
async function fetchData(url: string) {
|
async function fetchData(url: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
@@ -102,7 +102,7 @@ async function fetchData(url: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ BAD: No error handling
|
// FAIL: BAD: No error handling
|
||||||
async function fetchData(url) {
|
async function fetchData(url) {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
return response.json()
|
return response.json()
|
||||||
@@ -112,14 +112,14 @@ async function fetchData(url) {
|
|||||||
### Async/Awaitベストプラクティス
|
### Async/Awaitベストプラクティス
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Parallel execution when possible
|
// PASS: GOOD: Parallel execution when possible
|
||||||
const [users, markets, stats] = await Promise.all([
|
const [users, markets, stats] = await Promise.all([
|
||||||
fetchUsers(),
|
fetchUsers(),
|
||||||
fetchMarkets(),
|
fetchMarkets(),
|
||||||
fetchStats()
|
fetchStats()
|
||||||
])
|
])
|
||||||
|
|
||||||
// ❌ BAD: Sequential when unnecessary
|
// FAIL: BAD: Sequential when unnecessary
|
||||||
const users = await fetchUsers()
|
const users = await fetchUsers()
|
||||||
const markets = await fetchMarkets()
|
const markets = await fetchMarkets()
|
||||||
const stats = await fetchStats()
|
const stats = await fetchStats()
|
||||||
@@ -128,7 +128,7 @@ const stats = await fetchStats()
|
|||||||
### 型安全性
|
### 型安全性
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Proper types
|
// PASS: GOOD: Proper types
|
||||||
interface Market {
|
interface Market {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@@ -140,7 +140,7 @@ function getMarket(id: string): Promise<Market> {
|
|||||||
// Implementation
|
// Implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ BAD: Using 'any'
|
// FAIL: BAD: Using 'any'
|
||||||
function getMarket(id: any): Promise<any> {
|
function getMarket(id: any): Promise<any> {
|
||||||
// Implementation
|
// Implementation
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ function getMarket(id: any): Promise<any> {
|
|||||||
### コンポーネント構造
|
### コンポーネント構造
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Functional component with types
|
// PASS: GOOD: Functional component with types
|
||||||
interface ButtonProps {
|
interface ButtonProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
@@ -176,7 +176,7 @@ export function Button({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ BAD: No types, unclear structure
|
// FAIL: BAD: No types, unclear structure
|
||||||
export function Button(props) {
|
export function Button(props) {
|
||||||
return <button onClick={props.onClick}>{props.children}</button>
|
return <button onClick={props.onClick}>{props.children}</button>
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ export function Button(props) {
|
|||||||
### カスタムフック
|
### カスタムフック
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Reusable custom hook
|
// PASS: GOOD: Reusable custom hook
|
||||||
export function useDebounce<T>(value: T, delay: number): T {
|
export function useDebounce<T>(value: T, delay: number): T {
|
||||||
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
||||||
|
|
||||||
@@ -207,25 +207,25 @@ const debouncedQuery = useDebounce(searchQuery, 500)
|
|||||||
### 状態管理
|
### 状態管理
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Proper state updates
|
// PASS: GOOD: Proper state updates
|
||||||
const [count, setCount] = useState(0)
|
const [count, setCount] = useState(0)
|
||||||
|
|
||||||
// Functional update for state based on previous state
|
// Functional update for state based on previous state
|
||||||
setCount(prev => prev + 1)
|
setCount(prev => prev + 1)
|
||||||
|
|
||||||
// ❌ BAD: Direct state reference
|
// FAIL: BAD: Direct state reference
|
||||||
setCount(count + 1) // Can be stale in async scenarios
|
setCount(count + 1) // Can be stale in async scenarios
|
||||||
```
|
```
|
||||||
|
|
||||||
### 条件付きレンダリング
|
### 条件付きレンダリング
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Clear conditional rendering
|
// PASS: GOOD: Clear conditional rendering
|
||||||
{isLoading && <Spinner />}
|
{isLoading && <Spinner />}
|
||||||
{error && <ErrorMessage error={error} />}
|
{error && <ErrorMessage error={error} />}
|
||||||
{data && <DataDisplay data={data} />}
|
{data && <DataDisplay data={data} />}
|
||||||
|
|
||||||
// ❌ BAD: Ternary hell
|
// FAIL: BAD: Ternary hell
|
||||||
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ GET /api/markets?status=active&limit=10&offset=0
|
|||||||
### レスポンス形式
|
### レスポンス形式
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Consistent response structure
|
// PASS: GOOD: Consistent response structure
|
||||||
interface ApiResponse<T> {
|
interface ApiResponse<T> {
|
||||||
success: boolean
|
success: boolean
|
||||||
data?: T
|
data?: T
|
||||||
@@ -279,7 +279,7 @@ return NextResponse.json({
|
|||||||
```typescript
|
```typescript
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
// ✅ GOOD: Schema validation
|
// PASS: GOOD: Schema validation
|
||||||
const CreateMarketSchema = z.object({
|
const CreateMarketSchema = z.object({
|
||||||
name: z.string().min(1).max(200),
|
name: z.string().min(1).max(200),
|
||||||
description: z.string().min(1).max(2000),
|
description: z.string().min(1).max(2000),
|
||||||
@@ -342,14 +342,14 @@ types/market.types.ts # 型定義は .types サフィックス付き cam
|
|||||||
### コメントを追加するタイミング
|
### コメントを追加するタイミング
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Explain WHY, not WHAT
|
// PASS: GOOD: Explain WHY, not WHAT
|
||||||
// Use exponential backoff to avoid overwhelming the API during outages
|
// Use exponential backoff to avoid overwhelming the API during outages
|
||||||
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
||||||
|
|
||||||
// Deliberately using mutation here for performance with large arrays
|
// Deliberately using mutation here for performance with large arrays
|
||||||
items.push(newItem)
|
items.push(newItem)
|
||||||
|
|
||||||
// ❌ BAD: Stating the obvious
|
// FAIL: BAD: Stating the obvious
|
||||||
// Increment counter by 1
|
// Increment counter by 1
|
||||||
count++
|
count++
|
||||||
|
|
||||||
@@ -389,12 +389,12 @@ export async function searchMarkets(
|
|||||||
```typescript
|
```typescript
|
||||||
import { useMemo, useCallback } from 'react'
|
import { useMemo, useCallback } from 'react'
|
||||||
|
|
||||||
// ✅ GOOD: Memoize expensive computations
|
// PASS: GOOD: Memoize expensive computations
|
||||||
const sortedMarkets = useMemo(() => {
|
const sortedMarkets = useMemo(() => {
|
||||||
return markets.sort((a, b) => b.volume - a.volume)
|
return markets.sort((a, b) => b.volume - a.volume)
|
||||||
}, [markets])
|
}, [markets])
|
||||||
|
|
||||||
// ✅ GOOD: Memoize callbacks
|
// PASS: GOOD: Memoize callbacks
|
||||||
const handleSearch = useCallback((query: string) => {
|
const handleSearch = useCallback((query: string) => {
|
||||||
setSearchQuery(query)
|
setSearchQuery(query)
|
||||||
}, [])
|
}, [])
|
||||||
@@ -405,7 +405,7 @@ const handleSearch = useCallback((query: string) => {
|
|||||||
```typescript
|
```typescript
|
||||||
import { lazy, Suspense } from 'react'
|
import { lazy, Suspense } from 'react'
|
||||||
|
|
||||||
// ✅ GOOD: Lazy load heavy components
|
// PASS: GOOD: Lazy load heavy components
|
||||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
@@ -420,13 +420,13 @@ export function Dashboard() {
|
|||||||
### データベースクエリ
|
### データベースクエリ
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Select only needed columns
|
// PASS: GOOD: Select only needed columns
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('id, name, status')
|
.select('id, name, status')
|
||||||
.limit(10)
|
.limit(10)
|
||||||
|
|
||||||
// ❌ BAD: Select everything
|
// FAIL: BAD: Select everything
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('*')
|
.select('*')
|
||||||
@@ -453,12 +453,12 @@ test('calculates similarity correctly', () => {
|
|||||||
### テストの命名
|
### テストの命名
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Descriptive test names
|
// PASS: GOOD: Descriptive test names
|
||||||
test('returns empty array when no markets match query', () => { })
|
test('returns empty array when no markets match query', () => { })
|
||||||
test('throws error when OpenAI API key is missing', () => { })
|
test('throws error when OpenAI API key is missing', () => { })
|
||||||
test('falls back to substring search when Redis unavailable', () => { })
|
test('falls back to substring search when Redis unavailable', () => { })
|
||||||
|
|
||||||
// ❌ BAD: Vague test names
|
// FAIL: BAD: Vague test names
|
||||||
test('works', () => { })
|
test('works', () => { })
|
||||||
test('test search', () => { })
|
test('test search', () => { })
|
||||||
```
|
```
|
||||||
@@ -470,12 +470,12 @@ test('test search', () => { })
|
|||||||
### 1. 長い関数
|
### 1. 長い関数
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: Function > 50 lines
|
// FAIL: BAD: Function > 50 lines
|
||||||
function processMarketData() {
|
function processMarketData() {
|
||||||
// 100 lines of code
|
// 100 lines of code
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ GOOD: Split into smaller functions
|
// PASS: GOOD: Split into smaller functions
|
||||||
function processMarketData() {
|
function processMarketData() {
|
||||||
const validated = validateData()
|
const validated = validateData()
|
||||||
const transformed = transformData(validated)
|
const transformed = transformData(validated)
|
||||||
@@ -486,7 +486,7 @@ function processMarketData() {
|
|||||||
### 2. 深いネスト
|
### 2. 深いネスト
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: 5+ levels of nesting
|
// FAIL: BAD: 5+ levels of nesting
|
||||||
if (user) {
|
if (user) {
|
||||||
if (user.isAdmin) {
|
if (user.isAdmin) {
|
||||||
if (market) {
|
if (market) {
|
||||||
@@ -499,7 +499,7 @@ if (user) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ GOOD: Early returns
|
// PASS: GOOD: Early returns
|
||||||
if (!user) return
|
if (!user) return
|
||||||
if (!user.isAdmin) return
|
if (!user.isAdmin) return
|
||||||
if (!market) return
|
if (!market) return
|
||||||
@@ -512,11 +512,11 @@ if (!hasPermission) return
|
|||||||
### 3. マジックナンバー
|
### 3. マジックナンバー
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: Unexplained numbers
|
// FAIL: BAD: Unexplained numbers
|
||||||
if (retryCount > 3) { }
|
if (retryCount > 3) { }
|
||||||
setTimeout(callback, 500)
|
setTimeout(callback, 500)
|
||||||
|
|
||||||
// ✅ GOOD: Named constants
|
// PASS: GOOD: Named constants
|
||||||
const MAX_RETRIES = 3
|
const MAX_RETRIES = 3
|
||||||
const DEBOUNCE_DELAY_MS = 500
|
const DEBOUNCE_DELAY_MS = 500
|
||||||
|
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ DJANGO 検証レポート
|
|||||||
✓ ハードコードされたシークレットなし
|
✓ ハードコードされたシークレットなし
|
||||||
✓ マイグレーションが含まれる
|
✓ マイグレーションが含まれる
|
||||||
|
|
||||||
推奨: ⚠️ デプロイ前にpip-auditの脆弱性を修正してください
|
推奨: WARNING: デプロイ前にpip-auditの脆弱性を修正してください
|
||||||
|
|
||||||
次のステップ:
|
次のステップ:
|
||||||
1. 脆弱な依存関係を更新
|
1. 脆弱な依存関係を更新
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ React、Next.js、高性能ユーザーインターフェースのためのモ
|
|||||||
### 継承よりコンポジション
|
### 継承よりコンポジション
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Component composition
|
// PASS: GOOD: Component composition
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
variant?: 'default' | 'outlined'
|
variant?: 'default' | 'outlined'
|
||||||
@@ -283,17 +283,17 @@ export function useMarkets() {
|
|||||||
### メモ化
|
### メモ化
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ useMemo for expensive computations
|
// PASS: useMemo for expensive computations
|
||||||
const sortedMarkets = useMemo(() => {
|
const sortedMarkets = useMemo(() => {
|
||||||
return markets.sort((a, b) => b.volume - a.volume)
|
return markets.sort((a, b) => b.volume - a.volume)
|
||||||
}, [markets])
|
}, [markets])
|
||||||
|
|
||||||
// ✅ useCallback for functions passed to children
|
// PASS: useCallback for functions passed to children
|
||||||
const handleSearch = useCallback((query: string) => {
|
const handleSearch = useCallback((query: string) => {
|
||||||
setSearchQuery(query)
|
setSearchQuery(query)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// ✅ React.memo for pure components
|
// PASS: React.memo for pure components
|
||||||
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||||
return (
|
return (
|
||||||
<div className="market-card">
|
<div className="market-card">
|
||||||
@@ -309,7 +309,7 @@ export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
|||||||
```typescript
|
```typescript
|
||||||
import { lazy, Suspense } from 'react'
|
import { lazy, Suspense } from 'react'
|
||||||
|
|
||||||
// ✅ Lazy load heavy components
|
// PASS: Lazy load heavy components
|
||||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||||
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
||||||
|
|
||||||
@@ -504,7 +504,7 @@ export class ErrorBoundary extends React.Component<
|
|||||||
```typescript
|
```typescript
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
|
|
||||||
// ✅ List animations
|
// PASS: List animations
|
||||||
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@@ -523,7 +523,7 @@ export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Modal animations
|
// PASS: Modal animations
|
||||||
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ description: サブエージェントのコンテキスト問題を解決する
|
|||||||
┌─────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────┐
|
||||||
│ │
|
│ │
|
||||||
│ ┌──────────┐ ┌──────────┐ │
|
│ ┌──────────┐ ┌──────────┐ │
|
||||||
│ │ DISPATCH │─────▶│ EVALUATE │ │
|
│ │ DISPATCH │─────│ EVALUATE │ │
|
||||||
│ └──────────┘ └──────────┘ │
|
│ └──────────┘ └──────────┘ │
|
||||||
│ ▲ │ │
|
│ ▲ │ │
|
||||||
│ │ ▼ │
|
│ │ ▼ │
|
||||||
│ ┌──────────┐ ┌──────────┐ │
|
│ ┌──────────┐ ┌──────────┐ │
|
||||||
│ │ LOOP │◀─────│ REFINE │ │
|
│ │ LOOP │─────│ REFINE │ │
|
||||||
│ └──────────┘ └──────────┘ │
|
│ └──────────┘ └──────────┘ │
|
||||||
│ │
|
│ │
|
||||||
│ 最大3サイクル、その後続行 │
|
│ 最大3サイクル、その後続行 │
|
||||||
|
|||||||
@@ -17,22 +17,22 @@ Spring Bootサービスにおける読みやすく保守可能なJava(17+)コー
|
|||||||
## 命名
|
## 命名
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// ✅ クラス/レコード: PascalCase
|
// PASS: クラス/レコード: PascalCase
|
||||||
public class MarketService {}
|
public class MarketService {}
|
||||||
public record Money(BigDecimal amount, Currency currency) {}
|
public record Money(BigDecimal amount, Currency currency) {}
|
||||||
|
|
||||||
// ✅ メソッド/フィールド: camelCase
|
// PASS: メソッド/フィールド: camelCase
|
||||||
private final MarketRepository marketRepository;
|
private final MarketRepository marketRepository;
|
||||||
public Market findBySlug(String slug) {}
|
public Market findBySlug(String slug) {}
|
||||||
|
|
||||||
// ✅ 定数: UPPER_SNAKE_CASE
|
// PASS: 定数: UPPER_SNAKE_CASE
|
||||||
private static final int MAX_PAGE_SIZE = 100;
|
private static final int MAX_PAGE_SIZE = 100;
|
||||||
```
|
```
|
||||||
|
|
||||||
## 不変性
|
## 不変性
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// ✅ recordとfinalフィールドを優先
|
// PASS: recordとfinalフィールドを優先
|
||||||
public record MarketDto(Long id, String name, MarketStatus status) {}
|
public record MarketDto(Long id, String name, MarketStatus status) {}
|
||||||
|
|
||||||
public class Market {
|
public class Market {
|
||||||
@@ -45,10 +45,10 @@ public class Market {
|
|||||||
## Optionalの使用
|
## Optionalの使用
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// ✅ find*メソッドからOptionalを返す
|
// PASS: find*メソッドからOptionalを返す
|
||||||
Optional<Market> market = marketRepository.findBySlug(slug);
|
Optional<Market> market = marketRepository.findBySlug(slug);
|
||||||
|
|
||||||
// ✅ get()の代わりにmap/flatMapを使用
|
// PASS: get()の代わりにmap/flatMapを使用
|
||||||
return market
|
return market
|
||||||
.map(MarketResponse::from)
|
.map(MarketResponse::from)
|
||||||
.orElseThrow(() -> new EntityNotFoundException("Market not found"));
|
.orElseThrow(() -> new EntityNotFoundException("Market not found"));
|
||||||
@@ -57,13 +57,13 @@ return market
|
|||||||
## ストリームのベストプラクティス
|
## ストリームのベストプラクティス
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// ✅ 変換にストリームを使用し、パイプラインを短く保つ
|
// PASS: 変換にストリームを使用し、パイプラインを短く保つ
|
||||||
List<String> names = markets.stream()
|
List<String> names = markets.stream()
|
||||||
.map(Market::name)
|
.map(Market::name)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// ❌ 複雑なネストされたストリームを避ける; 明確性のためにループを優先
|
// FAIL: 複雑なネストされたストリームを避ける; 明確性のためにループを優先
|
||||||
```
|
```
|
||||||
|
|
||||||
## 例外
|
## 例外
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ description: 認証の追加、ユーザー入力の処理、シークレット
|
|||||||
|
|
||||||
### 1. シークレット管理
|
### 1. シークレット管理
|
||||||
|
|
||||||
#### ❌ 絶対にしないこと
|
#### FAIL: 絶対にしないこと
|
||||||
```typescript
|
```typescript
|
||||||
const apiKey = "sk-proj-xxxxx" // ハードコードされたシークレット
|
const apiKey = "sk-proj-xxxxx" // ハードコードされたシークレット
|
||||||
const dbPassword = "password123" // ソースコード内
|
const dbPassword = "password123" // ソースコード内
|
||||||
```
|
```
|
||||||
|
|
||||||
#### ✅ 常にすること
|
#### PASS: 常にすること
|
||||||
```typescript
|
```typescript
|
||||||
const apiKey = process.env.OPENAI_API_KEY
|
const apiKey = process.env.OPENAI_API_KEY
|
||||||
const dbUrl = process.env.DATABASE_URL
|
const dbUrl = process.env.DATABASE_URL
|
||||||
@@ -107,14 +107,14 @@ function validateFileUpload(file: File) {
|
|||||||
|
|
||||||
### 3. SQLインジェクション防止
|
### 3. SQLインジェクション防止
|
||||||
|
|
||||||
#### ❌ 絶対にSQLを連結しない
|
#### FAIL: 絶対にSQLを連結しない
|
||||||
```typescript
|
```typescript
|
||||||
// 危険 - SQLインジェクションの脆弱性
|
// 危険 - SQLインジェクションの脆弱性
|
||||||
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
|
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
|
||||||
await db.query(query)
|
await db.query(query)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### ✅ 常にパラメータ化されたクエリを使用
|
#### PASS: 常にパラメータ化されたクエリを使用
|
||||||
```typescript
|
```typescript
|
||||||
// 安全 - パラメータ化されたクエリ
|
// 安全 - パラメータ化されたクエリ
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
@@ -139,10 +139,10 @@ await db.query(
|
|||||||
|
|
||||||
#### JWTトークン処理
|
#### JWTトークン処理
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ 誤り:localStorage(XSSに脆弱)
|
// FAIL: 誤り:localStorage(XSSに脆弱)
|
||||||
localStorage.setItem('token', token)
|
localStorage.setItem('token', token)
|
||||||
|
|
||||||
// ✅ 正解:httpOnly Cookie
|
// PASS: 正解:httpOnly Cookie
|
||||||
res.setHeader('Set-Cookie',
|
res.setHeader('Set-Cookie',
|
||||||
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
||||||
```
|
```
|
||||||
@@ -299,18 +299,18 @@ app.use('/api/search', searchLimiter)
|
|||||||
|
|
||||||
#### ロギング
|
#### ロギング
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ 誤り:機密データをログに記録
|
// FAIL: 誤り:機密データをログに記録
|
||||||
console.log('User login:', { email, password })
|
console.log('User login:', { email, password })
|
||||||
console.log('Payment:', { cardNumber, cvv })
|
console.log('Payment:', { cardNumber, cvv })
|
||||||
|
|
||||||
// ✅ 正解:機密データを編集
|
// PASS: 正解:機密データを編集
|
||||||
console.log('User login:', { email, userId })
|
console.log('User login:', { email, userId })
|
||||||
console.log('Payment:', { last4: card.last4, userId })
|
console.log('Payment:', { last4: card.last4, userId })
|
||||||
```
|
```
|
||||||
|
|
||||||
#### エラーメッセージ
|
#### エラーメッセージ
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ 誤り:内部詳細を露出
|
// FAIL: 誤り:内部詳細を露出
|
||||||
catch (error) {
|
catch (error) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: error.message, stack: error.stack },
|
{ error: error.message, stack: error.stack },
|
||||||
@@ -318,7 +318,7 @@ catch (error) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 正解:一般的なエラーメッセージ
|
// PASS: 正解:一般的なエラーメッセージ
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('Internal error:', error)
|
console.error('Internal error:', error)
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
#### 最小権限の原則
|
#### 最小権限の原則
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# ✅ 正解:最小限の権限
|
# PASS: 正解:最小限の権限
|
||||||
iam_role:
|
iam_role:
|
||||||
permissions:
|
permissions:
|
||||||
- s3:GetObject # 読み取りアクセスのみ
|
- s3:GetObject # 読み取りアクセスのみ
|
||||||
@@ -32,7 +32,7 @@ iam_role:
|
|||||||
resources:
|
resources:
|
||||||
- arn:aws:s3:::my-bucket/* # 特定のバケットのみ
|
- arn:aws:s3:::my-bucket/* # 特定のバケットのみ
|
||||||
|
|
||||||
# ❌ 誤り:過度に広範な権限
|
# FAIL: 誤り:過度に広範な権限
|
||||||
iam_role:
|
iam_role:
|
||||||
permissions:
|
permissions:
|
||||||
- s3:* # すべてのS3アクション
|
- s3:* # すべてのS3アクション
|
||||||
@@ -65,14 +65,14 @@ aws iam enable-mfa-device \
|
|||||||
#### クラウドシークレットマネージャー
|
#### クラウドシークレットマネージャー
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ 正解:クラウドシークレットマネージャーを使用
|
// PASS: 正解:クラウドシークレットマネージャーを使用
|
||||||
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
|
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
|
||||||
|
|
||||||
const client = new SecretsManager({ region: 'us-east-1' });
|
const client = new SecretsManager({ region: 'us-east-1' });
|
||||||
const secret = await client.getSecretValue({ SecretId: 'prod/api-key' });
|
const secret = await client.getSecretValue({ SecretId: 'prod/api-key' });
|
||||||
const apiKey = JSON.parse(secret.SecretString).key;
|
const apiKey = JSON.parse(secret.SecretString).key;
|
||||||
|
|
||||||
// ❌ 誤り:ハードコードまたは環境変数のみ
|
// FAIL: 誤り:ハードコードまたは環境変数のみ
|
||||||
const apiKey = process.env.API_KEY; // ローテーションされず、監査されない
|
const apiKey = process.env.API_KEY; // ローテーションされず、監査されない
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ aws secretsmanager rotate-secret \
|
|||||||
#### VPCとファイアウォール設定
|
#### VPCとファイアウォール設定
|
||||||
|
|
||||||
```terraform
|
```terraform
|
||||||
# ✅ 正解:制限されたセキュリティグループ
|
# PASS: 正解:制限されたセキュリティグループ
|
||||||
resource "aws_security_group" "app" {
|
resource "aws_security_group" "app" {
|
||||||
name = "app-sg"
|
name = "app-sg"
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ resource "aws_security_group" "app" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ❌ 誤り:インターネットに公開
|
# FAIL: 誤り:インターネットに公開
|
||||||
resource "aws_security_group" "bad" {
|
resource "aws_security_group" "bad" {
|
||||||
ingress {
|
ingress {
|
||||||
from_port = 0
|
from_port = 0
|
||||||
@@ -142,7 +142,7 @@ resource "aws_security_group" "bad" {
|
|||||||
#### CloudWatch/ロギング設定
|
#### CloudWatch/ロギング設定
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ 正解:包括的なロギング
|
// PASS: 正解:包括的なロギング
|
||||||
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
|
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
|
||||||
|
|
||||||
const logSecurityEvent = async (event: SecurityEvent) => {
|
const logSecurityEvent = async (event: SecurityEvent) => {
|
||||||
@@ -177,7 +177,7 @@ const logSecurityEvent = async (event: SecurityEvent) => {
|
|||||||
#### 安全なパイプライン設定
|
#### 安全なパイプライン設定
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# ✅ 正解:安全なGitHub Actionsワークフロー
|
# PASS: 正解:安全なGitHub Actionsワークフロー
|
||||||
name: Deploy
|
name: Deploy
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -237,7 +237,7 @@ jobs:
|
|||||||
#### Cloudflareセキュリティ設定
|
#### Cloudflareセキュリティ設定
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ 正解:セキュリティヘッダー付きCloudflare Workers
|
// PASS: 正解:セキュリティヘッダー付きCloudflare Workers
|
||||||
export default {
|
export default {
|
||||||
async fetch(request: Request): Promise<Response> {
|
async fetch(request: Request): Promise<Response> {
|
||||||
const response = await fetch(request);
|
const response = await fetch(request);
|
||||||
@@ -281,7 +281,7 @@ export default {
|
|||||||
#### 自動バックアップ
|
#### 自動バックアップ
|
||||||
|
|
||||||
```terraform
|
```terraform
|
||||||
# ✅ 正解:自動RDSバックアップ
|
# PASS: 正解:自動RDSバックアップ
|
||||||
resource "aws_db_instance" "main" {
|
resource "aws_db_instance" "main" {
|
||||||
allocated_storage = 20
|
allocated_storage = 20
|
||||||
engine = "postgres"
|
engine = "postgres"
|
||||||
@@ -327,10 +327,10 @@ resource "aws_db_instance" "main" {
|
|||||||
### S3バケットの露出
|
### S3バケットの露出
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# ❌ 誤り:公開バケット
|
# FAIL: 誤り:公開バケット
|
||||||
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
|
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
|
||||||
|
|
||||||
# ✅ 正解:特定のアクセス付きプライベートバケット
|
# PASS: 正解:特定のアクセス付きプライベートバケット
|
||||||
aws s3api put-bucket-acl --bucket my-bucket --acl private
|
aws s3api put-bucket-acl --bucket my-bucket --acl private
|
||||||
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
||||||
```
|
```
|
||||||
@@ -338,12 +338,12 @@ aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
|||||||
### RDS公開アクセス
|
### RDS公開アクセス
|
||||||
|
|
||||||
```terraform
|
```terraform
|
||||||
# ❌ 誤り
|
# FAIL: 誤り
|
||||||
resource "aws_db_instance" "bad" {
|
resource "aws_db_instance" "bad" {
|
||||||
publicly_accessible = true # 絶対にこれをしない!
|
publicly_accessible = true # 絶対にこれをしない!
|
||||||
}
|
}
|
||||||
|
|
||||||
# ✅ 正解
|
# PASS: 正解
|
||||||
resource "aws_db_instance" "good" {
|
resource "aws_db_instance" "good" {
|
||||||
publicly_accessible = false
|
publicly_accessible = false
|
||||||
vpc_security_group_ids = [aws_security_group.db.id]
|
vpc_security_group_ids = [aws_security_group.db.id]
|
||||||
|
|||||||
@@ -313,39 +313,39 @@ npm run test:coverage
|
|||||||
|
|
||||||
## 避けるべき一般的なテストの誤り
|
## 避けるべき一般的なテストの誤り
|
||||||
|
|
||||||
### ❌ 誤り:実装の詳細をテスト
|
### FAIL: 誤り:実装の詳細をテスト
|
||||||
```typescript
|
```typescript
|
||||||
// 内部状態をテストしない
|
// 内部状態をテストしない
|
||||||
expect(component.state.count).toBe(5)
|
expect(component.state.count).toBe(5)
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ 正解:ユーザーに見える動作をテスト
|
### PASS: 正解:ユーザーに見える動作をテスト
|
||||||
```typescript
|
```typescript
|
||||||
// ユーザーが見るものをテスト
|
// ユーザーが見るものをテスト
|
||||||
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
||||||
```
|
```
|
||||||
|
|
||||||
### ❌ 誤り:脆弱なセレクタ
|
### FAIL: 誤り:脆弱なセレクタ
|
||||||
```typescript
|
```typescript
|
||||||
// 簡単に壊れる
|
// 簡単に壊れる
|
||||||
await page.click('.css-class-xyz')
|
await page.click('.css-class-xyz')
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ 正解:セマンティックセレクタ
|
### PASS: 正解:セマンティックセレクタ
|
||||||
```typescript
|
```typescript
|
||||||
// 変更に強い
|
// 変更に強い
|
||||||
await page.click('button:has-text("Submit")')
|
await page.click('button:has-text("Submit")')
|
||||||
await page.click('[data-testid="submit-button"]')
|
await page.click('[data-testid="submit-button"]')
|
||||||
```
|
```
|
||||||
|
|
||||||
### ❌ 誤り:テストの分離なし
|
### FAIL: 誤り:テストの分離なし
|
||||||
```typescript
|
```typescript
|
||||||
// テストが互いに依存
|
// テストが互いに依存
|
||||||
test('creates user', () => { /* ... */ })
|
test('creates user', () => { /* ... */ })
|
||||||
test('updates same user', () => { /* 前のテストに依存 */ })
|
test('updates same user', () => { /* 前のテストに依存 */ })
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ 正解:独立したテスト
|
### PASS: 正解:独立したテスト
|
||||||
```typescript
|
```typescript
|
||||||
// 各テストが独自のデータをセットアップ
|
// 各テストが独自のデータをセットアップ
|
||||||
test('creates user', () => {
|
test('creates user', () => {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
**🌐 Language / 语言 / 語言 / 언어**
|
**Language / 语言 / 語言 / 언어**
|
||||||
|
|
||||||
[**English**](../../README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](README.md)
|
[**English**](../../README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](README.md)
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 빠른 시작
|
## 빠른 시작
|
||||||
|
|
||||||
2분 안에 설정 완료:
|
2분 안에 설정 완료:
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
|
|
||||||
### 2단계: 룰 설치 (필수)
|
### 2단계: 룰 설치 (필수)
|
||||||
|
|
||||||
> ⚠️ **중요:** Claude Code 플러그인은 `rules`를 자동으로 배포할 수 없습니다. 수동으로 설치해야 합니다:
|
> WARNING: **중요:** Claude Code 플러그인은 `rules`를 자동으로 배포할 수 없습니다. 수동으로 설치해야 합니다:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 먼저 저장소 클론
|
# 먼저 저장소 클론
|
||||||
@@ -150,11 +150,11 @@ cd everything-claude-code
|
|||||||
/plugin list everything-claude-code@everything-claude-code
|
/plugin list everything-claude-code@everything-claude-code
|
||||||
```
|
```
|
||||||
|
|
||||||
✨ **끝!** 이제 16개 에이전트, 65개 스킬, 40개 커맨드를 사용할 수 있습니다.
|
**끝!** 이제 16개 에이전트, 65개 스킬, 40개 커맨드를 사용할 수 있습니다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🌐 크로스 플랫폼 지원
|
## 크로스 플랫폼 지원
|
||||||
|
|
||||||
이 플러그인은 **Windows, macOS, Linux**를 완벽하게 지원하며, 주요 IDE(Cursor, OpenCode, Antigravity) 및 CLI 하네스와 긴밀하게 통합됩니다. 모든 훅과 스크립트는 최대 호환성을 위해 Node.js로 작성되었습니다.
|
이 플러그인은 **Windows, macOS, Linux**를 완벽하게 지원하며, 주요 IDE(Cursor, OpenCode, Antigravity) 및 CLI 하네스와 긴밀하게 통합됩니다. 모든 훅과 스크립트는 최대 호환성을 위해 Node.js로 작성되었습니다.
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck"
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📦 구성 요소
|
## 구성 요소
|
||||||
|
|
||||||
이 저장소는 **Claude Code 플러그인**입니다 - 직접 설치하거나 컴포넌트를 수동으로 복사할 수 있습니다.
|
이 저장소는 **Claude Code 플러그인**입니다 - 직접 설치하거나 컴포넌트를 수동으로 복사할 수 있습니다.
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ everything-claude-code/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠️ 에코시스템 도구
|
## 에코시스템 도구
|
||||||
|
|
||||||
### Skill Creator
|
### Skill Creator
|
||||||
|
|
||||||
@@ -314,7 +314,7 @@ Claude Code에서 `/security-scan`을 사용하거나, [GitHub Action](https://g
|
|||||||
|
|
||||||
[GitHub](https://github.com/affaan-m/agentshield) | [npm](https://www.npmjs.com/package/ecc-agentshield)
|
[GitHub](https://github.com/affaan-m/agentshield) | [npm](https://www.npmjs.com/package/ecc-agentshield)
|
||||||
|
|
||||||
### 🧠 지속적 학습 v2
|
### 지속적 학습 v2
|
||||||
|
|
||||||
직관(Instinct) 기반 학습 시스템이 여러분의 패턴을 자동으로 학습합니다:
|
직관(Instinct) 기반 학습 시스템이 여러분의 패턴을 자동으로 학습합니다:
|
||||||
|
|
||||||
@@ -329,7 +329,7 @@ Claude Code에서 `/security-scan`을 사용하거나, [GitHub Action](https://g
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 요구 사항
|
## 요구 사항
|
||||||
|
|
||||||
### Claude Code CLI 버전
|
### Claude Code CLI 버전
|
||||||
|
|
||||||
@@ -344,13 +344,13 @@ claude --version
|
|||||||
|
|
||||||
### 중요: 훅 자동 로딩 동작
|
### 중요: 훅 자동 로딩 동작
|
||||||
|
|
||||||
> ⚠️ **기여자 참고:** `.claude-plugin/plugin.json`에 `"hooks"` 필드를 추가하지 **마세요**. 회귀 테스트로 이를 강제합니다.
|
> WARNING: **기여자 참고:** `.claude-plugin/plugin.json`에 `"hooks"` 필드를 추가하지 **마세요**. 회귀 테스트로 이를 강제합니다.
|
||||||
|
|
||||||
Claude Code v2.1+는 설치된 플러그인의 `hooks/hooks.json`을 **자동으로 로드**합니다. 명시적으로 선언하면 중복 감지 오류가 발생합니다.
|
Claude Code v2.1+는 설치된 플러그인의 `hooks/hooks.json`을 **자동으로 로드**합니다. 명시적으로 선언하면 중복 감지 오류가 발생합니다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📥 설치
|
## 설치
|
||||||
|
|
||||||
### 옵션 1: 플러그인으로 설치 (권장)
|
### 옵션 1: 플러그인으로 설치 (권장)
|
||||||
|
|
||||||
@@ -397,7 +397,7 @@ Claude Code v2.1+는 설치된 플러그인의 `hooks/hooks.json`을 **자동으
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 🔧 옵션 2: 수동 설치
|
### 옵션 2: 수동 설치
|
||||||
|
|
||||||
설치할 항목을 직접 선택하고 싶다면:
|
설치할 항목을 직접 선택하고 싶다면:
|
||||||
|
|
||||||
@@ -422,7 +422,7 @@ cp -r everything-claude-code/skills/search-first ~/.claude/skills/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 핵심 개념
|
## 핵심 개념
|
||||||
|
|
||||||
### 에이전트
|
### 에이전트
|
||||||
|
|
||||||
@@ -483,7 +483,7 @@ rules/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🗺️ 어떤 에이전트를 사용해야 할까?
|
## 어떤 에이전트를 사용해야 할까?
|
||||||
|
|
||||||
어디서 시작해야 할지 모르겠다면 이 참고표를 보세요:
|
어디서 시작해야 할지 모르겠다면 이 참고표를 보세요:
|
||||||
|
|
||||||
@@ -529,7 +529,7 @@ rules/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ❓ FAQ
|
## FAQ
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>설치된 에이전트/커맨드 확인은 어떻게 하나요?</b></summary>
|
<summary><b>설치된 에이전트/커맨드 확인은 어떻게 하나요?</b></summary>
|
||||||
@@ -602,7 +602,7 @@ cp -r everything-claude-code/rules/common/* ~/.claude/rules/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧪 테스트 실행
|
## 테스트 실행
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 모든 테스트 실행
|
# 모든 테스트 실행
|
||||||
@@ -616,7 +616,7 @@ node tests/hooks/hooks.test.js
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🤝 기여하기
|
## 기여하기
|
||||||
|
|
||||||
**기여를 환영합니다.**
|
**기여를 환영합니다.**
|
||||||
|
|
||||||
@@ -687,7 +687,7 @@ Claude Code 사용 비용이 부담된다면 토큰 소비를 관리해야 합
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚠️ 중요 참고 사항
|
## WARNING: 중요 참고 사항
|
||||||
|
|
||||||
### 커스터마이징
|
### 커스터마이징
|
||||||
|
|
||||||
@@ -699,7 +699,7 @@ Claude Code 사용 비용이 부담된다면 토큰 소비를 관리해야 합
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 💜 스폰서
|
## 스폰서
|
||||||
|
|
||||||
이 프로젝트는 무료 오픈소스입니다. 스폰서의 지원으로 유지보수와 성장이 이루어집니다.
|
이 프로젝트는 무료 오픈소스입니다. 스폰서의 지원으로 유지보수와 성장이 이루어집니다.
|
||||||
|
|
||||||
@@ -707,13 +707,13 @@ Claude Code 사용 비용이 부담된다면 토큰 소비를 관리해야 합
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🌟 Star 히스토리
|
## Star 히스토리
|
||||||
|
|
||||||
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
|
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔗 링크
|
## 링크
|
||||||
|
|
||||||
- **요약 가이드 (여기서 시작):** [The Shorthand Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2012378465664745795)
|
- **요약 가이드 (여기서 시작):** [The Shorthand Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2012378465664745795)
|
||||||
- **상세 가이드 (고급):** [The Longform Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2014040193557471352)
|
- **상세 가이드 (고급):** [The Longform Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2014040193557471352)
|
||||||
@@ -722,7 +722,7 @@ Claude Code 사용 비용이 부담된다면 토큰 소비를 관리해야 합
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📄 라이선스
|
## 라이선스
|
||||||
|
|
||||||
MIT - 자유롭게 사용하고, 필요에 따라 수정하고, 가능하다면 기여해 주세요.
|
MIT - 자유롭게 사용하고, 필요에 따라 수정하고, 가능하다면 기여해 주세요.
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ Running 3 tests using 3 workers
|
|||||||
╔══════════════════════════════════════════════════════════════╗
|
╔══════════════════════════════════════════════════════════════╗
|
||||||
║ E2E 테스트 결과 ║
|
║ E2E 테스트 결과 ║
|
||||||
╠══════════════════════════════════════════════════════════════╣
|
╠══════════════════════════════════════════════════════════════╣
|
||||||
║ 상태: ✅ 모든 테스트 통과 ║
|
║ 상태: PASS: 모든 테스트 통과 ║
|
||||||
║ 전체: 3개 테스트 ║
|
║ 전체: 3개 테스트 ║
|
||||||
║ 통과: 3 (100%) ║
|
║ 통과: 3 (100%) ║
|
||||||
║ 실패: 0 ║
|
║ 실패: 0 ║
|
||||||
@@ -191,15 +191,15 @@ Running 3 tests using 3 workers
|
|||||||
╚══════════════════════════════════════════════════════════════╝
|
╚══════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
아티팩트:
|
아티팩트:
|
||||||
📸 스크린샷: 2개 파일
|
스크린샷: 2개 파일
|
||||||
📹 비디오: 0개 파일 (실패 시에만)
|
비디오: 0개 파일 (실패 시에만)
|
||||||
🔍 트레이스: 0개 파일 (실패 시에만)
|
트레이스: 0개 파일 (실패 시에만)
|
||||||
📊 HTML 보고서: playwright-report/index.html
|
HTML 보고서: playwright-report/index.html
|
||||||
|
|
||||||
보고서 확인: npx playwright show-report
|
보고서 확인: npx playwright show-report
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ CI/CD 통합 준비가 완료된 E2E 테스트 모음!
|
PASS: CI/CD 통합 준비가 완료된 E2E 테스트 모음!
|
||||||
````
|
````
|
||||||
|
|
||||||
## 테스트 아티팩트
|
## 테스트 아티팩트
|
||||||
@@ -235,7 +235,7 @@ open artifacts/search-results.png
|
|||||||
테스트가 간헐적으로 실패하는 경우:
|
테스트가 간헐적으로 실패하는 경우:
|
||||||
|
|
||||||
```
|
```
|
||||||
⚠️ 불안정한 테스트 감지됨: tests/e2e/markets/trade.spec.ts
|
WARNING: 불안정한 테스트 감지됨: tests/e2e/markets/trade.spec.ts
|
||||||
|
|
||||||
테스트가 10회 중 7회 통과 (70% 통과율)
|
테스트가 10회 중 7회 통과 (70% 통과율)
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ ok project/internal/handler 0.023s
|
|||||||
| 수정된 파일 | 2 |
|
| 수정된 파일 | 2 |
|
||||||
| 남은 이슈 | 0 |
|
| 남은 이슈 | 0 |
|
||||||
|
|
||||||
Build 상태: ✅ 성공
|
Build 상태: PASS: 성공
|
||||||
````
|
````
|
||||||
|
|
||||||
## 자주 발생하는 에러
|
## 자주 발생하는 에러
|
||||||
|
|||||||
@@ -124,16 +124,16 @@ return fmt.Errorf("get user %s: %w", userID, err)
|
|||||||
- HIGH: 1
|
- HIGH: 1
|
||||||
- MEDIUM: 0
|
- MEDIUM: 0
|
||||||
|
|
||||||
권장: ❌ CRITICAL 이슈가 수정될 때까지 merge 차단
|
권장: FAIL: CRITICAL 이슈가 수정될 때까지 merge 차단
|
||||||
````
|
````
|
||||||
|
|
||||||
## 승인 기준
|
## 승인 기준
|
||||||
|
|
||||||
| 상태 | 조건 |
|
| 상태 | 조건 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| ✅ 승인 | CRITICAL 또는 HIGH 이슈 없음 |
|
| PASS: 승인 | CRITICAL 또는 HIGH 이슈 없음 |
|
||||||
| ⚠️ 경고 | MEDIUM 이슈만 있음 (주의하여 merge) |
|
| WARNING: 경고 | MEDIUM 이슈만 있음 (주의하여 merge) |
|
||||||
| ❌ 차단 | CRITICAL 또는 HIGH 이슈 발견 |
|
| FAIL: 차단 | CRITICAL 또는 HIGH 이슈 발견 |
|
||||||
|
|
||||||
## 다른 커맨드와의 연동
|
## 다른 커맨드와의 연동
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ Dead Code Cleanup
|
|||||||
건너뜀: 항목 2개 (테스트 실패)
|
건너뜀: 항목 2개 (테스트 실패)
|
||||||
절감: 약 450줄 제거
|
절감: 약 450줄 제거
|
||||||
──────────────────────────────
|
──────────────────────────────
|
||||||
모든 테스트 통과 ✅
|
모든 테스트 통과 PASS:
|
||||||
```
|
```
|
||||||
|
|
||||||
## 규칙
|
## 규칙
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ FAIL lib/liquidity.test.ts
|
|||||||
1 test failed, 0 passed
|
1 test failed, 0 passed
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ 예상대로 테스트 실패. 구현 준비 완료.
|
PASS: 예상대로 테스트 실패. 구현 준비 완료.
|
||||||
|
|
||||||
## 4단계: 최소한의 코드 구현 (GREEN)
|
## 4단계: 최소한의 코드 구현 (GREEN)
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ PASS lib/liquidity.test.ts
|
|||||||
3 tests passed
|
3 tests passed
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ 모든 테스트 통과!
|
PASS: 모든 테스트 통과!
|
||||||
|
|
||||||
## 6단계: 리팩토링 (IMPROVE)
|
## 6단계: 리팩토링 (IMPROVE)
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ PASS lib/liquidity.test.ts
|
|||||||
3 tests passed
|
3 tests passed
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ 리팩토링 완료, 테스트 여전히 통과!
|
PASS: 리팩토링 완료, 테스트 여전히 통과!
|
||||||
|
|
||||||
## 8단계: 커버리지 확인
|
## 8단계: 커버리지 확인
|
||||||
|
|
||||||
@@ -247,10 +247,10 @@ File | % Stmts | % Branch | % Funcs | % Lines
|
|||||||
---------------|---------|----------|---------|--------
|
---------------|---------|----------|---------|--------
|
||||||
liquidity.ts | 100 | 100 | 100 | 100
|
liquidity.ts | 100 | 100 | 100 | 100
|
||||||
|
|
||||||
Coverage: 100% ✅ (목표: 80%)
|
Coverage: 100% PASS: (목표: 80%)
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ TDD 세션 완료!
|
PASS: TDD 세션 완료!
|
||||||
````
|
````
|
||||||
|
|
||||||
## TDD 모범 사례
|
## TDD 모범 사례
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ description: 테스트 커버리지를 분석하고, 80% 이상을 목표로 누
|
|||||||
src/services/auth.ts 45% 88%
|
src/services/auth.ts 45% 88%
|
||||||
src/utils/validation.ts 32% 82%
|
src/utils/validation.ts 32% 82%
|
||||||
──────────────────────────────
|
──────────────────────────────
|
||||||
전체: 67% 84% ✅
|
전체: 67% 84% PASS:
|
||||||
```
|
```
|
||||||
|
|
||||||
## 집중 영역
|
## 집중 영역
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ origin: ECC
|
|||||||
### RESTful API 구조
|
### RESTful API 구조
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ Resource-based URLs
|
// PASS: Resource-based URLs
|
||||||
GET /api/markets # List resources
|
GET /api/markets # List resources
|
||||||
GET /api/markets/:id # Get single resource
|
GET /api/markets/:id # Get single resource
|
||||||
POST /api/markets # Create resource
|
POST /api/markets # Create resource
|
||||||
@@ -31,7 +31,7 @@ PUT /api/markets/:id # Replace resource
|
|||||||
PATCH /api/markets/:id # Update resource
|
PATCH /api/markets/:id # Update resource
|
||||||
DELETE /api/markets/:id # Delete resource
|
DELETE /api/markets/:id # Delete resource
|
||||||
|
|
||||||
// ✅ Query parameters for filtering, sorting, pagination
|
// PASS: Query parameters for filtering, sorting, pagination
|
||||||
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ export default withAuth(async (req, res) => {
|
|||||||
### 쿼리 최적화
|
### 쿼리 최적화
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Select only needed columns
|
// PASS: GOOD: Select only needed columns
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('id, name, status, volume')
|
.select('id, name, status, volume')
|
||||||
@@ -140,7 +140,7 @@ const { data } = await supabase
|
|||||||
.order('volume', { ascending: false })
|
.order('volume', { ascending: false })
|
||||||
.limit(10)
|
.limit(10)
|
||||||
|
|
||||||
// ❌ BAD: Select everything
|
// FAIL: BAD: Select everything
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('*')
|
.select('*')
|
||||||
@@ -149,13 +149,13 @@ const { data } = await supabase
|
|||||||
### N+1 쿼리 방지
|
### N+1 쿼리 방지
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: N+1 query problem
|
// FAIL: BAD: N+1 query problem
|
||||||
const markets = await getMarkets()
|
const markets = await getMarkets()
|
||||||
for (const market of markets) {
|
for (const market of markets) {
|
||||||
market.creator = await getUser(market.creator_id) // N queries
|
market.creator = await getUser(market.creator_id) // N queries
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ GOOD: Batch fetch
|
// PASS: GOOD: Batch fetch
|
||||||
const markets = await getMarkets()
|
const markets = await getMarkets()
|
||||||
const creatorIds = markets.map(m => m.creator_id)
|
const creatorIds = markets.map(m => m.creator_id)
|
||||||
const creators = await getUsers(creatorIds) // 1 query
|
const creators = await getUsers(creatorIds) // 1 query
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ ORDER BY hour DESC;
|
|||||||
### 효율적인 필터링
|
### 효율적인 필터링
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ✅ 좋음: 인덱스된 컬럼을 먼저 사용
|
-- PASS: 좋음: 인덱스된 컬럼을 먼저 사용
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM markets_analytics
|
FROM markets_analytics
|
||||||
WHERE date >= '2025-01-01'
|
WHERE date >= '2025-01-01'
|
||||||
@@ -105,7 +105,7 @@ WHERE date >= '2025-01-01'
|
|||||||
ORDER BY date DESC
|
ORDER BY date DESC
|
||||||
LIMIT 100;
|
LIMIT 100;
|
||||||
|
|
||||||
-- ❌ 나쁨: 비인덱스 컬럼을 먼저 필터링
|
-- FAIL: 나쁨: 비인덱스 컬럼을 먼저 필터링
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM markets_analytics
|
FROM markets_analytics
|
||||||
WHERE volume > 1000
|
WHERE volume > 1000
|
||||||
@@ -116,7 +116,7 @@ WHERE volume > 1000
|
|||||||
### 집계
|
### 집계
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- ✅ 좋음: ClickHouse 전용 집계 함수를 사용
|
-- PASS: 좋음: ClickHouse 전용 집계 함수를 사용
|
||||||
SELECT
|
SELECT
|
||||||
toStartOfDay(created_at) AS day,
|
toStartOfDay(created_at) AS day,
|
||||||
market_id,
|
market_id,
|
||||||
@@ -129,7 +129,7 @@ WHERE created_at >= today() - INTERVAL 7 DAY
|
|||||||
GROUP BY day, market_id
|
GROUP BY day, market_id
|
||||||
ORDER BY day DESC, total_volume DESC;
|
ORDER BY day DESC, total_volume DESC;
|
||||||
|
|
||||||
-- ✅ 백분위수에는 quantile 사용 (percentile보다 효율적)
|
-- PASS: 백분위수에는 quantile 사용 (percentile보다 효율적)
|
||||||
SELECT
|
SELECT
|
||||||
quantile(0.50)(trade_size) AS median,
|
quantile(0.50)(trade_size) AS median,
|
||||||
quantile(0.95)(trade_size) AS p95,
|
quantile(0.95)(trade_size) AS p95,
|
||||||
@@ -172,7 +172,7 @@ const clickhouse = new ClickHouse({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// ✅ 배치 삽입 (효율적)
|
// PASS: 배치 삽입 (효율적)
|
||||||
async function bulkInsertTrades(trades: Trade[]) {
|
async function bulkInsertTrades(trades: Trade[]) {
|
||||||
const rows = trades.map(trade => ({
|
const rows = trades.map(trade => ({
|
||||||
id: trade.id,
|
id: trade.id,
|
||||||
@@ -185,7 +185,7 @@ async function bulkInsertTrades(trades: Trade[]) {
|
|||||||
await clickhouse.insert('trades', rows)
|
await clickhouse.insert('trades', rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ 개별 삽입 (느림)
|
// FAIL: 개별 삽입 (느림)
|
||||||
async function insertTrade(trade: Trade) {
|
async function insertTrade(trade: Trade) {
|
||||||
// 루프 안에서 이렇게 하지 마세요!
|
// 루프 안에서 이렇게 하지 마세요!
|
||||||
await clickhouse.query(`
|
await clickhouse.query(`
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ origin: ECC
|
|||||||
### 변수 네이밍
|
### 변수 네이밍
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Descriptive names
|
// PASS: GOOD: Descriptive names
|
||||||
const marketSearchQuery = 'election'
|
const marketSearchQuery = 'election'
|
||||||
const isUserAuthenticated = true
|
const isUserAuthenticated = true
|
||||||
const totalRevenue = 1000
|
const totalRevenue = 1000
|
||||||
|
|
||||||
// ❌ BAD: Unclear names
|
// FAIL: BAD: Unclear names
|
||||||
const q = 'election'
|
const q = 'election'
|
||||||
const flag = true
|
const flag = true
|
||||||
const x = 1000
|
const x = 1000
|
||||||
@@ -62,12 +62,12 @@ const x = 1000
|
|||||||
### 함수 네이밍
|
### 함수 네이밍
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Verb-noun pattern
|
// PASS: GOOD: Verb-noun pattern
|
||||||
async function fetchMarketData(marketId: string) { }
|
async function fetchMarketData(marketId: string) { }
|
||||||
function calculateSimilarity(a: number[], b: number[]) { }
|
function calculateSimilarity(a: number[], b: number[]) { }
|
||||||
function isValidEmail(email: string): boolean { }
|
function isValidEmail(email: string): boolean { }
|
||||||
|
|
||||||
// ❌ BAD: Unclear or noun-only
|
// FAIL: BAD: Unclear or noun-only
|
||||||
async function market(id: string) { }
|
async function market(id: string) { }
|
||||||
function similarity(a, b) { }
|
function similarity(a, b) { }
|
||||||
function email(e) { }
|
function email(e) { }
|
||||||
@@ -76,7 +76,7 @@ function email(e) { }
|
|||||||
### 불변성 패턴 (필수)
|
### 불변성 패턴 (필수)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ ALWAYS use spread operator
|
// PASS: ALWAYS use spread operator
|
||||||
const updatedUser = {
|
const updatedUser = {
|
||||||
...user,
|
...user,
|
||||||
name: 'New Name'
|
name: 'New Name'
|
||||||
@@ -84,7 +84,7 @@ const updatedUser = {
|
|||||||
|
|
||||||
const updatedArray = [...items, newItem]
|
const updatedArray = [...items, newItem]
|
||||||
|
|
||||||
// ❌ NEVER mutate directly
|
// FAIL: NEVER mutate directly
|
||||||
user.name = 'New Name' // BAD
|
user.name = 'New Name' // BAD
|
||||||
items.push(newItem) // BAD
|
items.push(newItem) // BAD
|
||||||
```
|
```
|
||||||
@@ -92,7 +92,7 @@ items.push(newItem) // BAD
|
|||||||
### 에러 처리
|
### 에러 처리
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Comprehensive error handling
|
// PASS: GOOD: Comprehensive error handling
|
||||||
async function fetchData(url: string) {
|
async function fetchData(url: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
@@ -108,7 +108,7 @@ async function fetchData(url: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ BAD: No error handling
|
// FAIL: BAD: No error handling
|
||||||
async function fetchData(url) {
|
async function fetchData(url) {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
return response.json()
|
return response.json()
|
||||||
@@ -118,14 +118,14 @@ async function fetchData(url) {
|
|||||||
### Async/Await 모범 사례
|
### Async/Await 모범 사례
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Parallel execution when possible
|
// PASS: GOOD: Parallel execution when possible
|
||||||
const [users, markets, stats] = await Promise.all([
|
const [users, markets, stats] = await Promise.all([
|
||||||
fetchUsers(),
|
fetchUsers(),
|
||||||
fetchMarkets(),
|
fetchMarkets(),
|
||||||
fetchStats()
|
fetchStats()
|
||||||
])
|
])
|
||||||
|
|
||||||
// ❌ BAD: Sequential when unnecessary
|
// FAIL: BAD: Sequential when unnecessary
|
||||||
const users = await fetchUsers()
|
const users = await fetchUsers()
|
||||||
const markets = await fetchMarkets()
|
const markets = await fetchMarkets()
|
||||||
const stats = await fetchStats()
|
const stats = await fetchStats()
|
||||||
@@ -134,7 +134,7 @@ const stats = await fetchStats()
|
|||||||
### 타입 안전성
|
### 타입 안전성
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Proper types
|
// PASS: GOOD: Proper types
|
||||||
interface Market {
|
interface Market {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@@ -146,7 +146,7 @@ function getMarket(id: string): Promise<Market> {
|
|||||||
// Implementation
|
// Implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ BAD: Using 'any'
|
// FAIL: BAD: Using 'any'
|
||||||
function getMarket(id: any): Promise<any> {
|
function getMarket(id: any): Promise<any> {
|
||||||
// Implementation
|
// Implementation
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ function getMarket(id: any): Promise<any> {
|
|||||||
### 컴포넌트 구조
|
### 컴포넌트 구조
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Functional component with types
|
// PASS: GOOD: Functional component with types
|
||||||
interface ButtonProps {
|
interface ButtonProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
@@ -182,7 +182,7 @@ export function Button({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ BAD: No types, unclear structure
|
// FAIL: BAD: No types, unclear structure
|
||||||
export function Button(props) {
|
export function Button(props) {
|
||||||
return <button onClick={props.onClick}>{props.children}</button>
|
return <button onClick={props.onClick}>{props.children}</button>
|
||||||
}
|
}
|
||||||
@@ -191,7 +191,7 @@ export function Button(props) {
|
|||||||
### 커스텀 Hook
|
### 커스텀 Hook
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Reusable custom hook
|
// PASS: GOOD: Reusable custom hook
|
||||||
export function useDebounce<T>(value: T, delay: number): T {
|
export function useDebounce<T>(value: T, delay: number): T {
|
||||||
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
||||||
|
|
||||||
@@ -213,25 +213,25 @@ const debouncedQuery = useDebounce(searchQuery, 500)
|
|||||||
### 상태 관리
|
### 상태 관리
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Proper state updates
|
// PASS: GOOD: Proper state updates
|
||||||
const [count, setCount] = useState(0)
|
const [count, setCount] = useState(0)
|
||||||
|
|
||||||
// Functional update for state based on previous state
|
// Functional update for state based on previous state
|
||||||
setCount(prev => prev + 1)
|
setCount(prev => prev + 1)
|
||||||
|
|
||||||
// ❌ BAD: Direct state reference
|
// FAIL: BAD: Direct state reference
|
||||||
setCount(count + 1) // Can be stale in async scenarios
|
setCount(count + 1) // Can be stale in async scenarios
|
||||||
```
|
```
|
||||||
|
|
||||||
### 조건부 렌더링
|
### 조건부 렌더링
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Clear conditional rendering
|
// PASS: GOOD: Clear conditional rendering
|
||||||
{isLoading && <Spinner />}
|
{isLoading && <Spinner />}
|
||||||
{error && <ErrorMessage error={error} />}
|
{error && <ErrorMessage error={error} />}
|
||||||
{data && <DataDisplay data={data} />}
|
{data && <DataDisplay data={data} />}
|
||||||
|
|
||||||
// ❌ BAD: Ternary hell
|
// FAIL: BAD: Ternary hell
|
||||||
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -254,7 +254,7 @@ GET /api/markets?status=active&limit=10&offset=0
|
|||||||
### 응답 형식
|
### 응답 형식
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Consistent response structure
|
// PASS: GOOD: Consistent response structure
|
||||||
interface ApiResponse<T> {
|
interface ApiResponse<T> {
|
||||||
success: boolean
|
success: boolean
|
||||||
data?: T
|
data?: T
|
||||||
@@ -285,7 +285,7 @@ return NextResponse.json({
|
|||||||
```typescript
|
```typescript
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
// ✅ GOOD: Schema validation
|
// PASS: GOOD: Schema validation
|
||||||
const CreateMarketSchema = z.object({
|
const CreateMarketSchema = z.object({
|
||||||
name: z.string().min(1).max(200),
|
name: z.string().min(1).max(200),
|
||||||
description: z.string().min(1).max(2000),
|
description: z.string().min(1).max(2000),
|
||||||
@@ -348,14 +348,14 @@ types/market.types.ts # camelCase with .types suffix
|
|||||||
### 주석을 작성해야 하는 경우
|
### 주석을 작성해야 하는 경우
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Explain WHY, not WHAT
|
// PASS: GOOD: Explain WHY, not WHAT
|
||||||
// Use exponential backoff to avoid overwhelming the API during outages
|
// Use exponential backoff to avoid overwhelming the API during outages
|
||||||
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
||||||
|
|
||||||
// Deliberately using mutation here for performance with large arrays
|
// Deliberately using mutation here for performance with large arrays
|
||||||
items.push(newItem)
|
items.push(newItem)
|
||||||
|
|
||||||
// ❌ BAD: Stating the obvious
|
// FAIL: BAD: Stating the obvious
|
||||||
// Increment counter by 1
|
// Increment counter by 1
|
||||||
count++
|
count++
|
||||||
|
|
||||||
@@ -395,12 +395,12 @@ export async function searchMarkets(
|
|||||||
```typescript
|
```typescript
|
||||||
import { useMemo, useCallback } from 'react'
|
import { useMemo, useCallback } from 'react'
|
||||||
|
|
||||||
// ✅ GOOD: Memoize expensive computations
|
// PASS: GOOD: Memoize expensive computations
|
||||||
const sortedMarkets = useMemo(() => {
|
const sortedMarkets = useMemo(() => {
|
||||||
return [...markets].sort((a, b) => b.volume - a.volume)
|
return [...markets].sort((a, b) => b.volume - a.volume)
|
||||||
}, [markets])
|
}, [markets])
|
||||||
|
|
||||||
// ✅ GOOD: Memoize callbacks
|
// PASS: GOOD: Memoize callbacks
|
||||||
const handleSearch = useCallback((query: string) => {
|
const handleSearch = useCallback((query: string) => {
|
||||||
setSearchQuery(query)
|
setSearchQuery(query)
|
||||||
}, [])
|
}, [])
|
||||||
@@ -411,7 +411,7 @@ const handleSearch = useCallback((query: string) => {
|
|||||||
```typescript
|
```typescript
|
||||||
import { lazy, Suspense } from 'react'
|
import { lazy, Suspense } from 'react'
|
||||||
|
|
||||||
// ✅ GOOD: Lazy load heavy components
|
// PASS: GOOD: Lazy load heavy components
|
||||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
@@ -426,13 +426,13 @@ export function Dashboard() {
|
|||||||
### 데이터베이스 쿼리
|
### 데이터베이스 쿼리
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Select only needed columns
|
// PASS: GOOD: Select only needed columns
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('id, name, status')
|
.select('id, name, status')
|
||||||
.limit(10)
|
.limit(10)
|
||||||
|
|
||||||
// ❌ BAD: Select everything
|
// FAIL: BAD: Select everything
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('markets')
|
.from('markets')
|
||||||
.select('*')
|
.select('*')
|
||||||
@@ -459,12 +459,12 @@ test('calculates similarity correctly', () => {
|
|||||||
### 테스트 네이밍
|
### 테스트 네이밍
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Descriptive test names
|
// PASS: GOOD: Descriptive test names
|
||||||
test('returns empty array when no markets match query', () => { })
|
test('returns empty array when no markets match query', () => { })
|
||||||
test('throws error when OpenAI API key is missing', () => { })
|
test('throws error when OpenAI API key is missing', () => { })
|
||||||
test('falls back to substring search when Redis unavailable', () => { })
|
test('falls back to substring search when Redis unavailable', () => { })
|
||||||
|
|
||||||
// ❌ BAD: Vague test names
|
// FAIL: BAD: Vague test names
|
||||||
test('works', () => { })
|
test('works', () => { })
|
||||||
test('test search', () => { })
|
test('test search', () => { })
|
||||||
```
|
```
|
||||||
@@ -475,12 +475,12 @@ test('test search', () => { })
|
|||||||
|
|
||||||
### 1. 긴 함수
|
### 1. 긴 함수
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: Function > 50 lines
|
// FAIL: BAD: Function > 50 lines
|
||||||
function processMarketData() {
|
function processMarketData() {
|
||||||
// 100 lines of code
|
// 100 lines of code
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ GOOD: Split into smaller functions
|
// PASS: GOOD: Split into smaller functions
|
||||||
function processMarketData() {
|
function processMarketData() {
|
||||||
const validated = validateData()
|
const validated = validateData()
|
||||||
const transformed = transformData(validated)
|
const transformed = transformData(validated)
|
||||||
@@ -490,7 +490,7 @@ function processMarketData() {
|
|||||||
|
|
||||||
### 2. 깊은 중첩
|
### 2. 깊은 중첩
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: 5+ levels of nesting
|
// FAIL: BAD: 5+ levels of nesting
|
||||||
if (user) {
|
if (user) {
|
||||||
if (user.isAdmin) {
|
if (user.isAdmin) {
|
||||||
if (market) {
|
if (market) {
|
||||||
@@ -503,7 +503,7 @@ if (user) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ GOOD: Early returns
|
// PASS: GOOD: Early returns
|
||||||
if (!user) return
|
if (!user) return
|
||||||
if (!user.isAdmin) return
|
if (!user.isAdmin) return
|
||||||
if (!market) return
|
if (!market) return
|
||||||
@@ -515,11 +515,11 @@ if (!hasPermission) return
|
|||||||
|
|
||||||
### 3. 매직 넘버
|
### 3. 매직 넘버
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ BAD: Unexplained numbers
|
// FAIL: BAD: Unexplained numbers
|
||||||
if (retryCount > 3) { }
|
if (retryCount > 3) { }
|
||||||
setTimeout(callback, 500)
|
setTimeout(callback, 500)
|
||||||
|
|
||||||
// ✅ GOOD: Named constants
|
// PASS: GOOD: Named constants
|
||||||
const MAX_RETRIES = 3
|
const MAX_RETRIES = 3
|
||||||
const DEBOUNCE_DELAY_MS = 500
|
const DEBOUNCE_DELAY_MS = 500
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ React, Next.js 및 고성능 사용자 인터페이스를 위한 모던 프론
|
|||||||
### 상속보다 합성
|
### 상속보다 합성
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ GOOD: Component composition
|
// PASS: GOOD: Component composition
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
variant?: 'default' | 'outlined'
|
variant?: 'default' | 'outlined'
|
||||||
@@ -304,17 +304,17 @@ export function useMarkets() {
|
|||||||
### 메모이제이션
|
### 메모이제이션
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ useMemo for expensive computations
|
// PASS: useMemo for expensive computations
|
||||||
const sortedMarkets = useMemo(() => {
|
const sortedMarkets = useMemo(() => {
|
||||||
return [...markets].sort((a, b) => b.volume - a.volume)
|
return [...markets].sort((a, b) => b.volume - a.volume)
|
||||||
}, [markets])
|
}, [markets])
|
||||||
|
|
||||||
// ✅ useCallback for functions passed to children
|
// PASS: useCallback for functions passed to children
|
||||||
const handleSearch = useCallback((query: string) => {
|
const handleSearch = useCallback((query: string) => {
|
||||||
setSearchQuery(query)
|
setSearchQuery(query)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// ✅ React.memo for pure components
|
// PASS: React.memo for pure components
|
||||||
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||||
return (
|
return (
|
||||||
<div className="market-card">
|
<div className="market-card">
|
||||||
@@ -330,7 +330,7 @@ export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
|||||||
```typescript
|
```typescript
|
||||||
import { lazy, Suspense } from 'react'
|
import { lazy, Suspense } from 'react'
|
||||||
|
|
||||||
// ✅ Lazy load heavy components
|
// PASS: Lazy load heavy components
|
||||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||||
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
||||||
|
|
||||||
@@ -525,7 +525,7 @@ export class ErrorBoundary extends React.Component<
|
|||||||
```typescript
|
```typescript
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
|
|
||||||
// ✅ List animations
|
// PASS: List animations
|
||||||
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@@ -544,7 +544,7 @@ export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Modal animations
|
// PASS: Modal animations
|
||||||
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ origin: ECC
|
|||||||
┌─────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────┐
|
||||||
│ │
|
│ │
|
||||||
│ ┌──────────┐ ┌──────────┐ │
|
│ ┌──────────┐ ┌──────────┐ │
|
||||||
│ │ DISPATCH │─────▶│ EVALUATE │ │
|
│ │ DISPATCH │─────│ EVALUATE │ │
|
||||||
│ └──────────┘ └──────────┘ │
|
│ └──────────┘ └──────────┘ │
|
||||||
│ ▲ │ │
|
│ ▲ │ │
|
||||||
│ │ ▼ │
|
│ │ ▼ │
|
||||||
│ ┌──────────┐ ┌──────────┐ │
|
│ ┌──────────┐ ┌──────────┐ │
|
||||||
│ │ LOOP │◀─────│ REFINE │ │
|
│ │ LOOP │─────│ REFINE │ │
|
||||||
│ └──────────┘ └──────────┘ │
|
│ └──────────┘ └──────────┘ │
|
||||||
│ │
|
│ │
|
||||||
│ Max 3 cycles, then proceed │
|
│ Max 3 cycles, then proceed │
|
||||||
|
|||||||
@@ -140,10 +140,10 @@ await db.query(
|
|||||||
|
|
||||||
#### JWT 토큰 처리
|
#### JWT 토큰 처리
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ WRONG: localStorage (vulnerable to XSS)
|
// FAIL: WRONG: localStorage (vulnerable to XSS)
|
||||||
localStorage.setItem('token', token)
|
localStorage.setItem('token', token)
|
||||||
|
|
||||||
// ✅ CORRECT: httpOnly cookies
|
// PASS: CORRECT: httpOnly cookies
|
||||||
res.setHeader('Set-Cookie',
|
res.setHeader('Set-Cookie',
|
||||||
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
||||||
```
|
```
|
||||||
@@ -302,18 +302,18 @@ app.use('/api/search', searchLimiter)
|
|||||||
|
|
||||||
#### 로깅
|
#### 로깅
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ WRONG: Logging sensitive data
|
// FAIL: WRONG: Logging sensitive data
|
||||||
console.log('User login:', { email, password })
|
console.log('User login:', { email, password })
|
||||||
console.log('Payment:', { cardNumber, cvv })
|
console.log('Payment:', { cardNumber, cvv })
|
||||||
|
|
||||||
// ✅ CORRECT: Redact sensitive data
|
// PASS: CORRECT: Redact sensitive data
|
||||||
console.log('User login:', { email, userId })
|
console.log('User login:', { email, userId })
|
||||||
console.log('Payment:', { last4: card.last4, userId })
|
console.log('Payment:', { last4: card.last4, userId })
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 에러 메시지
|
#### 에러 메시지
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ WRONG: Exposing internal details
|
// FAIL: WRONG: Exposing internal details
|
||||||
catch (error) {
|
catch (error) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: error.message, stack: error.stack },
|
{ error: error.message, stack: error.stack },
|
||||||
@@ -321,7 +321,7 @@ catch (error) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ CORRECT: Generic error messages
|
// PASS: CORRECT: Generic error messages
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('Internal error:', error)
|
console.error('Internal error:', error)
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
#### 최소 권한 원칙
|
#### 최소 권한 원칙
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# ✅ CORRECT: Minimal permissions
|
# PASS: CORRECT: Minimal permissions
|
||||||
iam_role:
|
iam_role:
|
||||||
permissions:
|
permissions:
|
||||||
- s3:GetObject # Only read access
|
- s3:GetObject # Only read access
|
||||||
@@ -32,7 +32,7 @@ iam_role:
|
|||||||
resources:
|
resources:
|
||||||
- arn:aws:s3:::my-bucket/* # Specific bucket only
|
- arn:aws:s3:::my-bucket/* # Specific bucket only
|
||||||
|
|
||||||
# ❌ WRONG: Overly broad permissions
|
# FAIL: WRONG: Overly broad permissions
|
||||||
iam_role:
|
iam_role:
|
||||||
permissions:
|
permissions:
|
||||||
- s3:* # All S3 actions
|
- s3:* # All S3 actions
|
||||||
@@ -65,14 +65,14 @@ aws iam enable-mfa-device \
|
|||||||
#### 클라우드 시크릿 매니저
|
#### 클라우드 시크릿 매니저
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ CORRECT: Use cloud secrets manager
|
// PASS: CORRECT: Use cloud secrets manager
|
||||||
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
|
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
|
||||||
|
|
||||||
const client = new SecretsManager({ region: 'us-east-1' });
|
const client = new SecretsManager({ region: 'us-east-1' });
|
||||||
const secret = await client.getSecretValue({ SecretId: 'prod/api-key' });
|
const secret = await client.getSecretValue({ SecretId: 'prod/api-key' });
|
||||||
const apiKey = JSON.parse(secret.SecretString).key;
|
const apiKey = JSON.parse(secret.SecretString).key;
|
||||||
|
|
||||||
// ❌ WRONG: Hardcoded or in environment variables only
|
// FAIL: WRONG: Hardcoded or in environment variables only
|
||||||
const apiKey = process.env.API_KEY; // Not rotated, not audited
|
const apiKey = process.env.API_KEY; // Not rotated, not audited
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ aws secretsmanager rotate-secret \
|
|||||||
#### VPC 및 방화벽 구성
|
#### VPC 및 방화벽 구성
|
||||||
|
|
||||||
```terraform
|
```terraform
|
||||||
# ✅ CORRECT: Restricted security group
|
# PASS: CORRECT: Restricted security group
|
||||||
resource "aws_security_group" "app" {
|
resource "aws_security_group" "app" {
|
||||||
name = "app-sg"
|
name = "app-sg"
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ resource "aws_security_group" "app" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ❌ WRONG: Open to the internet
|
# FAIL: WRONG: Open to the internet
|
||||||
resource "aws_security_group" "bad" {
|
resource "aws_security_group" "bad" {
|
||||||
ingress {
|
ingress {
|
||||||
from_port = 0
|
from_port = 0
|
||||||
@@ -142,7 +142,7 @@ resource "aws_security_group" "bad" {
|
|||||||
#### CloudWatch/로깅 구성
|
#### CloudWatch/로깅 구성
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ CORRECT: Comprehensive logging
|
// PASS: CORRECT: Comprehensive logging
|
||||||
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
|
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
|
||||||
|
|
||||||
const logSecurityEvent = async (event: SecurityEvent) => {
|
const logSecurityEvent = async (event: SecurityEvent) => {
|
||||||
@@ -177,7 +177,7 @@ const logSecurityEvent = async (event: SecurityEvent) => {
|
|||||||
#### 보안 파이프라인 구성
|
#### 보안 파이프라인 구성
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# ✅ CORRECT: Secure GitHub Actions workflow
|
# PASS: CORRECT: Secure GitHub Actions workflow
|
||||||
name: Deploy
|
name: Deploy
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -237,7 +237,7 @@ jobs:
|
|||||||
#### Cloudflare 보안 구성
|
#### Cloudflare 보안 구성
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ CORRECT: Cloudflare Workers with security headers
|
// PASS: CORRECT: Cloudflare Workers with security headers
|
||||||
export default {
|
export default {
|
||||||
async fetch(request: Request): Promise<Response> {
|
async fetch(request: Request): Promise<Response> {
|
||||||
const response = await fetch(request);
|
const response = await fetch(request);
|
||||||
@@ -281,7 +281,7 @@ export default {
|
|||||||
#### 자동 백업
|
#### 자동 백업
|
||||||
|
|
||||||
```terraform
|
```terraform
|
||||||
# ✅ CORRECT: Automated RDS backups
|
# PASS: CORRECT: Automated RDS backups
|
||||||
resource "aws_db_instance" "main" {
|
resource "aws_db_instance" "main" {
|
||||||
allocated_storage = 20
|
allocated_storage = 20
|
||||||
engine = "postgres"
|
engine = "postgres"
|
||||||
@@ -327,10 +327,10 @@ resource "aws_db_instance" "main" {
|
|||||||
### S3 버킷 노출
|
### S3 버킷 노출
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# ❌ WRONG: Public bucket
|
# FAIL: WRONG: Public bucket
|
||||||
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
|
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
|
||||||
|
|
||||||
# ✅ CORRECT: Private bucket with specific access
|
# PASS: CORRECT: Private bucket with specific access
|
||||||
aws s3api put-bucket-acl --bucket my-bucket --acl private
|
aws s3api put-bucket-acl --bucket my-bucket --acl private
|
||||||
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
||||||
```
|
```
|
||||||
@@ -338,12 +338,12 @@ aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
|||||||
### RDS 공개 접근
|
### RDS 공개 접근
|
||||||
|
|
||||||
```terraform
|
```terraform
|
||||||
# ❌ WRONG
|
# FAIL: WRONG
|
||||||
resource "aws_db_instance" "bad" {
|
resource "aws_db_instance" "bad" {
|
||||||
publicly_accessible = true # NEVER do this!
|
publicly_accessible = true # NEVER do this!
|
||||||
}
|
}
|
||||||
|
|
||||||
# ✅ CORRECT
|
# PASS: CORRECT
|
||||||
resource "aws_db_instance" "good" {
|
resource "aws_db_instance" "good" {
|
||||||
publicly_accessible = false
|
publicly_accessible = false
|
||||||
vpc_security_group_ids = [aws_security_group.db.id]
|
vpc_security_group_ids = [aws_security_group.db.id]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user