mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-14 22:13:41 +08:00
docs: address Korean translation review feedback
This commit is contained in:
@@ -42,6 +42,7 @@ GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
||||
interface MarketRepository {
|
||||
findAll(filters?: MarketFilters): Promise<Market[]>
|
||||
findById(id: string): Promise<Market | null>
|
||||
findByIds(ids: string[]): Promise<Market[]>
|
||||
create(data: CreateMarketDto): Promise<Market>
|
||||
update(id: string, data: UpdateMarketDto): Promise<Market>
|
||||
delete(id: string): Promise<void>
|
||||
@@ -85,7 +86,7 @@ class MarketService {
|
||||
const markets = await this.marketRepo.findByIds(results.map(r => r.id))
|
||||
|
||||
// Sort by similarity
|
||||
return markets.sort((a, b) => {
|
||||
return [...markets].sort((a, b) => {
|
||||
const scoreA = results.find(r => r.id === a.id)?.score || 0
|
||||
const scoreB = results.find(r => r.id === b.id)?.score || 0
|
||||
return scoreA - scoreB
|
||||
@@ -320,7 +321,7 @@ async function fetchWithRetry<T>(
|
||||
fn: () => Promise<T>,
|
||||
maxRetries = 3
|
||||
): Promise<T> {
|
||||
let lastError: Error
|
||||
let lastError: Error = new Error('Retry attempts exhausted')
|
||||
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
|
||||
@@ -51,7 +51,7 @@ SETTINGS index_granularity = 8192;
|
||||
### ReplacingMergeTree (중복 제거)
|
||||
|
||||
```sql
|
||||
-- For data that may have duplicates (e.g., from multiple sources)
|
||||
-- 중복이 있을 수 있는 데이터용 (예: 여러 소스에서 수집된 경우)
|
||||
CREATE TABLE user_events (
|
||||
event_id String,
|
||||
user_id String,
|
||||
@@ -67,7 +67,7 @@ PRIMARY KEY (user_id, event_id);
|
||||
### AggregatingMergeTree (사전 집계)
|
||||
|
||||
```sql
|
||||
-- For maintaining aggregated metrics
|
||||
-- 집계 메트릭을 유지하기 위한 용도
|
||||
CREATE TABLE market_stats_hourly (
|
||||
hour DateTime,
|
||||
market_id String,
|
||||
@@ -78,7 +78,7 @@ CREATE TABLE market_stats_hourly (
|
||||
PARTITION BY toYYYYMM(hour)
|
||||
ORDER BY (hour, market_id);
|
||||
|
||||
-- Query aggregated data
|
||||
-- 집계된 데이터 조회
|
||||
SELECT
|
||||
hour,
|
||||
market_id,
|
||||
@@ -96,7 +96,7 @@ ORDER BY hour DESC;
|
||||
### 효율적인 필터링
|
||||
|
||||
```sql
|
||||
-- ✅ GOOD: Use indexed columns first
|
||||
-- ✅ 좋음: 인덱스된 컬럼을 먼저 사용
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE date >= '2025-01-01'
|
||||
@@ -105,7 +105,7 @@ WHERE date >= '2025-01-01'
|
||||
ORDER BY date DESC
|
||||
LIMIT 100;
|
||||
|
||||
-- ❌ BAD: Filter on non-indexed columns first
|
||||
-- ❌ 나쁨: 비인덱스 컬럼을 먼저 필터링
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE volume > 1000
|
||||
@@ -116,7 +116,7 @@ WHERE volume > 1000
|
||||
### 집계
|
||||
|
||||
```sql
|
||||
-- ✅ GOOD: Use ClickHouse-specific aggregation functions
|
||||
-- ✅ 좋음: ClickHouse 전용 집계 함수를 사용
|
||||
SELECT
|
||||
toStartOfDay(created_at) AS day,
|
||||
market_id,
|
||||
@@ -129,7 +129,7 @@ WHERE created_at >= today() - INTERVAL 7 DAY
|
||||
GROUP BY day, market_id
|
||||
ORDER BY day DESC, total_volume DESC;
|
||||
|
||||
-- ✅ Use quantile for percentiles (more efficient than percentile)
|
||||
-- ✅ 백분위수에는 quantile 사용 (percentile보다 효율적)
|
||||
SELECT
|
||||
quantile(0.50)(trade_size) AS median,
|
||||
quantile(0.95)(trade_size) AS p95,
|
||||
@@ -141,7 +141,7 @@ WHERE created_at >= now() - INTERVAL 1 HOUR;
|
||||
### 윈도우 함수
|
||||
|
||||
```sql
|
||||
-- Calculate running totals
|
||||
-- 누적 합계 계산
|
||||
SELECT
|
||||
date,
|
||||
market_id,
|
||||
@@ -172,25 +172,22 @@ const clickhouse = new ClickHouse({
|
||||
}
|
||||
})
|
||||
|
||||
// ✅ Batch insert (efficient)
|
||||
// ✅ 배치 삽입 (효율적)
|
||||
async function bulkInsertTrades(trades: Trade[]) {
|
||||
const values = trades.map(trade => `(
|
||||
'${trade.id}',
|
||||
'${trade.market_id}',
|
||||
'${trade.user_id}',
|
||||
${trade.amount},
|
||||
'${trade.timestamp.toISOString()}'
|
||||
)`).join(',')
|
||||
const rows = trades.map(trade => ({
|
||||
id: trade.id,
|
||||
market_id: trade.market_id,
|
||||
user_id: trade.user_id,
|
||||
amount: trade.amount,
|
||||
timestamp: trade.timestamp.toISOString()
|
||||
}))
|
||||
|
||||
await clickhouse.query(`
|
||||
INSERT INTO trades (id, market_id, user_id, amount, timestamp)
|
||||
VALUES ${values}
|
||||
`).toPromise()
|
||||
await clickhouse.insert('trades', rows)
|
||||
}
|
||||
|
||||
// ❌ Individual inserts (slow)
|
||||
// ❌ 개별 삽입 (느림)
|
||||
async function insertTrade(trade: Trade) {
|
||||
// Don't do this in a loop!
|
||||
// 루프 안에서 이렇게 하지 마세요!
|
||||
await clickhouse.query(`
|
||||
INSERT INTO trades VALUES ('${trade.id}', ...)
|
||||
`).toPromise()
|
||||
@@ -200,7 +197,7 @@ async function insertTrade(trade: Trade) {
|
||||
### 스트리밍 삽입
|
||||
|
||||
```typescript
|
||||
// For continuous data ingestion
|
||||
// 연속적인 데이터 수집용
|
||||
import { createWriteStream } from 'fs'
|
||||
import { pipeline } from 'stream/promises'
|
||||
|
||||
@@ -220,7 +217,7 @@ async function streamInserts() {
|
||||
### 실시간 집계
|
||||
|
||||
```sql
|
||||
-- Create materialized view for hourly stats
|
||||
-- 시간별 통계를 위한 materialized view 생성
|
||||
CREATE MATERIALIZED VIEW market_stats_hourly_mv
|
||||
TO market_stats_hourly
|
||||
AS SELECT
|
||||
@@ -232,7 +229,7 @@ AS SELECT
|
||||
FROM trades
|
||||
GROUP BY hour, market_id;
|
||||
|
||||
-- Query the materialized view
|
||||
-- materialized view 조회
|
||||
SELECT
|
||||
hour,
|
||||
market_id,
|
||||
@@ -249,7 +246,7 @@ GROUP BY hour, market_id;
|
||||
### 쿼리 성능
|
||||
|
||||
```sql
|
||||
-- Check slow queries
|
||||
-- 느린 쿼리 확인
|
||||
SELECT
|
||||
query_id,
|
||||
user,
|
||||
@@ -269,7 +266,7 @@ LIMIT 10;
|
||||
### 테이블 통계
|
||||
|
||||
```sql
|
||||
-- Check table sizes
|
||||
-- 테이블 크기 확인
|
||||
SELECT
|
||||
database,
|
||||
table,
|
||||
@@ -287,7 +284,7 @@ ORDER BY sum(bytes) DESC;
|
||||
### 시계열 분석
|
||||
|
||||
```sql
|
||||
-- Daily active users
|
||||
-- 일간 활성 사용자
|
||||
SELECT
|
||||
toDate(timestamp) AS date,
|
||||
uniq(user_id) AS daily_active_users
|
||||
@@ -296,7 +293,7 @@ WHERE timestamp >= today() - INTERVAL 30 DAY
|
||||
GROUP BY date
|
||||
ORDER BY date;
|
||||
|
||||
-- Retention analysis
|
||||
-- 리텐션 분석
|
||||
SELECT
|
||||
signup_date,
|
||||
countIf(days_since_signup = 0) AS day_0,
|
||||
@@ -319,7 +316,7 @@ ORDER BY signup_date DESC;
|
||||
### 퍼널 분석
|
||||
|
||||
```sql
|
||||
-- Conversion funnel
|
||||
-- 전환 퍼널
|
||||
SELECT
|
||||
countIf(step = 'viewed_market') AS viewed,
|
||||
countIf(step = 'clicked_trade') AS clicked,
|
||||
@@ -340,7 +337,7 @@ GROUP BY session_id;
|
||||
### 코호트 분석
|
||||
|
||||
```sql
|
||||
-- User cohorts by signup month
|
||||
-- 가입 월별 사용자 코호트
|
||||
SELECT
|
||||
toStartOfMonth(signup_date) AS cohort,
|
||||
toStartOfMonth(activity_date) AS month,
|
||||
@@ -362,12 +359,12 @@ ORDER BY cohort, months_since_signup;
|
||||
### ETL 패턴
|
||||
|
||||
```typescript
|
||||
// Extract, Transform, Load
|
||||
// 추출, 변환, 적재(ETL)
|
||||
async function etlPipeline() {
|
||||
// 1. Extract from source
|
||||
// 1. 소스에서 추출
|
||||
const rawData = await extractFromPostgres()
|
||||
|
||||
// 2. Transform
|
||||
// 2. 변환
|
||||
const transformed = rawData.map(row => ({
|
||||
date: new Date(row.created_at).toISOString().split('T')[0],
|
||||
market_id: row.market_slug,
|
||||
@@ -375,18 +372,29 @@ async function etlPipeline() {
|
||||
trades: parseInt(row.trade_count)
|
||||
}))
|
||||
|
||||
// 3. Load to ClickHouse
|
||||
// 3. ClickHouse에 적재
|
||||
await bulkInsertToClickHouse(transformed)
|
||||
}
|
||||
|
||||
// Run periodically
|
||||
setInterval(etlPipeline, 60 * 60 * 1000) // Every hour
|
||||
// 주기적으로 실행
|
||||
let etlRunning = false
|
||||
|
||||
setInterval(async () => {
|
||||
if (etlRunning) return
|
||||
|
||||
etlRunning = true
|
||||
try {
|
||||
await etlPipeline()
|
||||
} finally {
|
||||
etlRunning = false
|
||||
}
|
||||
}, 60 * 60 * 1000) // Every hour
|
||||
```
|
||||
|
||||
### 변경 데이터 캡처 (CDC)
|
||||
|
||||
```typescript
|
||||
// Listen to PostgreSQL changes and sync to ClickHouse
|
||||
// PostgreSQL 변경을 수신하고 ClickHouse와 동기화
|
||||
import { Client } from 'pg'
|
||||
|
||||
const pgClient = new Client({ connectionString: process.env.DATABASE_URL })
|
||||
|
||||
@@ -397,7 +397,7 @@ import { useMemo, useCallback } from 'react'
|
||||
|
||||
// ✅ GOOD: Memoize expensive computations
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return markets.sort((a, b) => b.volume - a.volume)
|
||||
return [...markets].sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ GOOD: Memoize callbacks
|
||||
|
||||
@@ -77,6 +77,35 @@ Claude Code 세션 종료 시 자동으로 평가하여 학습된 스킬로 저
|
||||
}
|
||||
```
|
||||
|
||||
## 예시
|
||||
|
||||
### 자동 패턴 추출 설정 예시
|
||||
|
||||
```json
|
||||
{
|
||||
"min_session_length": 10,
|
||||
"extraction_threshold": "medium",
|
||||
"auto_approve": false,
|
||||
"learned_skills_path": "~/.claude/skills/learned/"
|
||||
}
|
||||
```
|
||||
|
||||
### Stop Hook 연결 예시
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"Stop": [{
|
||||
"matcher": "*",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "~/.claude/skills/continuous-learning/evaluate-session.sh"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Stop Hook을 사용하는 이유
|
||||
|
||||
- **경량**: 세션 종료 시 한 번만 실행
|
||||
@@ -116,4 +145,4 @@ Homunculus v2는 더 정교한 접근법을 취합니다:
|
||||
4. **도메인 태깅** - code-style, testing, git, debugging 등
|
||||
5. **진화 경로** - 관련 본능을 스킬/명령어로 클러스터링
|
||||
|
||||
자세한 사양은 `docs/continuous-learning-v2-spec.md`를 참조하세요.
|
||||
자세한 사양은 [`continuous-learning-v2-spec.md`](../../../continuous-learning-v2-spec.md)를 참조하세요.
|
||||
|
||||
@@ -154,6 +154,8 @@ const [isOpen, toggleOpen] = useToggle()
|
||||
### 비동기 데이터 페칭 Hook
|
||||
|
||||
```typescript
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
interface UseQueryOptions<T> {
|
||||
onSuccess?: (data: T) => void
|
||||
onError?: (error: Error) => void
|
||||
@@ -168,6 +170,14 @@ export function useQuery<T>(
|
||||
const [data, setData] = useState<T | null>(null)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const successRef = useRef(options?.onSuccess)
|
||||
const errorRef = useRef(options?.onError)
|
||||
const enabled = options?.enabled !== false
|
||||
|
||||
useEffect(() => {
|
||||
successRef.current = options?.onSuccess
|
||||
errorRef.current = options?.onError
|
||||
}, [options?.onSuccess, options?.onError])
|
||||
|
||||
const refetch = useCallback(async () => {
|
||||
setLoading(true)
|
||||
@@ -176,21 +186,21 @@ export function useQuery<T>(
|
||||
try {
|
||||
const result = await fetcher()
|
||||
setData(result)
|
||||
options?.onSuccess?.(result)
|
||||
successRef.current?.(result)
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
setError(error)
|
||||
options?.onError?.(error)
|
||||
errorRef.current?.(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [fetcher, options])
|
||||
}, [fetcher])
|
||||
|
||||
useEffect(() => {
|
||||
if (options?.enabled !== false) {
|
||||
if (enabled) {
|
||||
refetch()
|
||||
}
|
||||
}, [key, refetch, options?.enabled])
|
||||
}, [key, enabled, refetch])
|
||||
|
||||
return { data, error, loading, refetch }
|
||||
}
|
||||
@@ -296,7 +306,7 @@ export function useMarkets() {
|
||||
```typescript
|
||||
// ✅ useMemo for expensive computations
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return markets.sort((a, b) => b.volume - a.volume)
|
||||
return [...markets].sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ useCallback for functions passed to children
|
||||
|
||||
@@ -533,7 +533,8 @@ func ProcessRequest(data []byte) []byte {
|
||||
|
||||
buf.Write(data)
|
||||
// Process...
|
||||
return buf.Bytes()
|
||||
out := append([]byte(nil), buf.Bytes()...)
|
||||
return out
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -215,8 +215,8 @@ const securityHeaders = [
|
||||
key: 'Content-Security-Policy',
|
||||
value: `
|
||||
default-src 'self';
|
||||
script-src 'self' 'unsafe-eval' 'unsafe-inline';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
script-src 'self' 'nonce-{nonce}';
|
||||
style-src 'self' 'nonce-{nonce}';
|
||||
img-src 'self' data: https:;
|
||||
font-src 'self';
|
||||
connect-src 'self' https://api.example.com;
|
||||
@@ -225,6 +225,8 @@ const securityHeaders = [
|
||||
]
|
||||
```
|
||||
|
||||
`{nonce}`는 요청마다 새로 생성하고, 헤더와 인라인 `<script>`/`<style>` 태그에 동일하게 주입해야 합니다.
|
||||
|
||||
#### 확인 단계
|
||||
- [ ] 사용자 제공 HTML이 새니타이징됨
|
||||
- [ ] CSP 헤더가 구성됨
|
||||
@@ -339,7 +341,9 @@ catch (error) {
|
||||
|
||||
#### 지갑 검증
|
||||
```typescript
|
||||
import { verify } from '@solana/web3.js'
|
||||
import nacl from 'tweetnacl'
|
||||
import bs58 from 'bs58'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
|
||||
async function verifyWalletOwnership(
|
||||
publicKey: string,
|
||||
@@ -347,18 +351,23 @@ async function verifyWalletOwnership(
|
||||
message: string
|
||||
) {
|
||||
try {
|
||||
const isValid = verify(
|
||||
Buffer.from(message),
|
||||
Buffer.from(signature, 'base64'),
|
||||
Buffer.from(publicKey, 'base64')
|
||||
const publicKeyBytes = new PublicKey(publicKey).toBytes()
|
||||
const signatureBytes = bs58.decode(signature)
|
||||
const messageBytes = new TextEncoder().encode(message)
|
||||
|
||||
return nacl.sign.detached.verify(
|
||||
messageBytes,
|
||||
signatureBytes,
|
||||
publicKeyBytes
|
||||
)
|
||||
return isValid
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
참고: Solana 공개 키와 서명은 일반적으로 base64가 아니라 base58로 인코딩됩니다.
|
||||
|
||||
#### 트랜잭션 검증
|
||||
```typescript
|
||||
async function verifyTransaction(transaction: Transaction) {
|
||||
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
|
||||
# Scan for secrets
|
||||
- name: Secret scanning
|
||||
uses: trufflesecurity/trufflehog@main
|
||||
uses: trufflesecurity/trufflehog@6c05c4a00b91aa542267d8e32a8254774799d68d
|
||||
|
||||
# Dependency audit
|
||||
- name: Audit dependencies
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
// package.json - Use lock files and integrity checks
|
||||
{
|
||||
"scripts": {
|
||||
"install": "npm ci", // Use ci for reproducible builds
|
||||
"deps:install": "npm ci", // Use ci for reproducible builds
|
||||
"audit": "npm audit --audit-level=moderate",
|
||||
"check": "npm outdated"
|
||||
}
|
||||
|
||||
@@ -45,12 +45,14 @@ origin: ECC
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Edit",
|
||||
"hooks": [{ "type": "command", "command": "node ~/.claude/skills/strategic-compact/suggest-compact.js" }]
|
||||
},
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [{ "type": "command", "command": "node ~/.claude/skills/strategic-compact/suggest-compact.js" }]
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"pre:edit-write:suggest-compact\" \"scripts/hooks/suggest-compact.js\" \"standard,strict\""
|
||||
}
|
||||
],
|
||||
"description": "Suggest manual compaction at logical intervals"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -191,11 +191,9 @@ test('user can search and filter markets', async ({ page }) => {
|
||||
// Search for markets
|
||||
await page.fill('input[placeholder="Search markets"]', 'election')
|
||||
|
||||
// Wait for debounce and results
|
||||
await page.waitForTimeout(600)
|
||||
|
||||
// Verify search results displayed
|
||||
// Wait for stable search results instead of sleeping
|
||||
const results = page.locator('[data-testid="market-card"]')
|
||||
await expect(results.first()).toBeVisible({ timeout: 5000 })
|
||||
await expect(results).toHaveCount(5, { timeout: 5000 })
|
||||
|
||||
// Verify results contain search term
|
||||
@@ -300,7 +298,7 @@ npm run test:coverage
|
||||
```json
|
||||
{
|
||||
"jest": {
|
||||
"coverageThresholds": {
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 80,
|
||||
"functions": 80,
|
||||
|
||||
@@ -77,7 +77,8 @@ grep -rn "console.log" --include="*.ts" --include="*.tsx" src/ 2>/dev/null | hea
|
||||
```bash
|
||||
# Show what changed
|
||||
git diff --stat
|
||||
git diff HEAD~1 --name-only
|
||||
git diff --name-only
|
||||
git diff --cached --name-only
|
||||
```
|
||||
|
||||
각 변경된 파일에서 다음을 검토합니다:
|
||||
|
||||
Reference in New Issue
Block a user