docs: address Korean translation review feedback

This commit is contained in:
Affaan Mustafa
2026-03-13 00:17:54 -07:00
parent 526a9070e6
commit fb7b73a962
36 changed files with 383 additions and 132 deletions

View File

@@ -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 {

View File

@@ -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 })

View File

@@ -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

View File

@@ -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)를 참조하세요.

View File

@@ -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

View File

@@ -533,7 +533,8 @@ func ProcessRequest(data []byte) []byte {
buf.Write(data)
// Process...
return buf.Bytes()
out := append([]byte(nil), buf.Bytes()...)
return out
}
```

View File

@@ -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) {

View File

@@ -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"
}

View File

@@ -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"
}
]
}

View File

@@ -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,

View File

@@ -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
```
각 변경된 파일에서 다음을 검토합니다: