diff --git a/internal/policy/manager.go b/internal/policy/manager.go index 0515f66..52777c8 100644 --- a/internal/policy/manager.go +++ b/internal/policy/manager.go @@ -92,18 +92,18 @@ func ValidatePolicy(policy *schema.UserPolicy) error { } // Validate rules - ruleNumbers := make(map[int]bool) + ruleIDs := make(map[string]bool) for i, rule := range policy.Rules { if rule.Say == "" { return fmt.Errorf("rule %d: 'say' field is required", i+1) } - if rule.No <= 0 { - return fmt.Errorf("rule %d: 'no' field must be positive", i+1) + if rule.ID == "" { + return fmt.Errorf("rule %d: 'id' field is required", i+1) } - if ruleNumbers[rule.No] { - return fmt.Errorf("duplicate rule number: %d", rule.No) + if ruleIDs[rule.ID] { + return fmt.Errorf("duplicate rule id: %s", rule.ID) } - ruleNumbers[rule.No] = true + ruleIDs[rule.ID] = true } // Validate RBAC roles diff --git a/internal/policy/templates/go-template.json b/internal/policy/templates/go-template.json index 4c01723..884c81f 100644 --- a/internal/policy/templates/go-template.json +++ b/internal/policy/templates/go-template.json @@ -15,54 +15,54 @@ }, "rules": [ { - "no": 1, + "id": "1", "say": "패키지 이름은 소문자 한 단어로 작성합니다 (예: http, json, user)", "category": "naming", "example": "// ✅ 좋은 예:\npackage user\npackage database\npackage httputil\n\n// ❌ 나쁜 예:\npackage UserManagement\npackage data_base\npackage HTTP_Util" }, { - "no": 2, + "id": "2", "say": "변수와 함수 이름은 camelCase 또는 PascalCase를 사용합니다 (public은 대문자 시작)", "category": "naming" }, { - "no": 3, + "id": "3", "say": "에러는 항상 처리하고, 무시하는 경우 명시적으로 _ 를 사용합니다", "category": "error_handling", "example": "// ✅ 좋은 예:\nfile, err := os.Open(\"config.json\")\nif err != nil {\n return fmt.Errorf(\"failed to open config: %w\", err)\n}\ndefer file.Close()\n\n// ❌ 나쁜 예:\nfile, _ := os.Open(\"config.json\")\ndefer file.Close()" }, { - "no": 4, + "id": "4", "say": "함수는 에러를 마지막 반환값으로 반환합니다", "category": "error_handling" }, { - "no": 5, + "id": "5", "say": "인터페이스 이름은 -er 접미사를 사용합니다 (예: Reader, Writer, Handler)", "category": "naming" }, { - "no": 6, + "id": "6", "say": "gofmt로 코드를 포맷팅합니다", "category": "formatting" }, { - "no": 7, + "id": "7", "say": "공개(exported) 함수와 타입에는 주석을 작성합니다", "category": "documentation" }, { - "no": 8, + "id": "8", "say": "context.Context를 함수의 첫 번째 파라미터로 전달합니다", "category": "error_handling" }, { - "no": 9, + "id": "9", "say": "goroutine 사용 시 적절한 동기화(sync, channel)를 사용합니다", "category": "error_handling" }, { - "no": 10, + "id": "10", "say": "defer를 사용하여 리소스 정리를 보장합니다", "category": "error_handling" } diff --git a/internal/policy/templates/node-template.json b/internal/policy/templates/node-template.json index d8ef657..43166ca 100644 --- a/internal/policy/templates/node-template.json +++ b/internal/policy/templates/node-template.json @@ -19,55 +19,55 @@ }, "rules": [ { - "no": 1, + "id": "1", "say": "환경 변수는 process.env가 아닌 설정 파일(config.js)을 통해 접근합니다", "category": "security" }, { - "no": 2, + "id": "2", "say": "민감한 정보(API 키, 비밀번호)는 절대 하드코딩하지 않습니다", "category": "security", "example": "// ✅ 좋은 예:\nconst apiKey = process.env.API_KEY;\nconst dbPassword = config.get('database.password');\n\n// ❌ 나쁜 예:\nconst apiKey = 'sk-1234567890abcdef';\nconst dbPassword = 'mySecretPassword123';" }, { - "no": 3, + "id": "3", "say": "모든 비동기 함수는 try-catch 블록으로 에러를 처리합니다", "category": "error_handling", "example": "// ✅ 좋은 예:\nasync function fetchUser(id) {\n try {\n const user = await db.users.findById(id);\n return user;\n } catch (error) {\n logger.error('Failed to fetch user:', error);\n throw new ApiError('User not found', 404);\n }\n}\n\n// ❌ 나쁜 예:\nasync function fetchUser(id) {\n const user = await db.users.findById(id);\n return user;\n}" }, { - "no": 4, + "id": "4", "say": "API 라우트 핸들러는 항상 적절한 HTTP 상태 코드를 반환합니다", "category": "error_handling" }, { - "no": 5, + "id": "5", "say": "데이터베이스 쿼리는 SQL injection을 방지하기 위해 parameterized query를 사용합니다", "category": "security", "example": "// ✅ 좋은 예:\nconst result = await db.query('SELECT * FROM users WHERE id = ?', [userId]);\n\n// ❌ 나쁜 예:\nconst result = await db.query(`SELECT * FROM users WHERE id = ${userId}`);" }, { - "no": 6, + "id": "6", "say": "파일 이름은 kebab-case를 사용합니다 (예: user-controller.js, auth-service.js)", "category": "naming" }, { - "no": 7, + "id": "7", "say": "클래스 이름은 PascalCase, 함수/변수는 camelCase를 사용합니다", "category": "naming" }, { - "no": 8, + "id": "8", "say": "미들웨어 함수는 next()를 명시적으로 호출하거나 응답을 보냅니다", "category": "error_handling" }, { - "no": 9, + "id": "9", "say": "큰 의존성 패키지는 필요한 부분만 import합니다 (tree-shaking)", "category": "performance" }, { - "no": 10, + "id": "10", "say": "로그 메시지는 적절한 레벨(debug, info, warn, error)을 사용합니다", "category": "documentation" } diff --git a/internal/policy/templates/python-template.json b/internal/policy/templates/python-template.json index b89596f..af94337 100644 --- a/internal/policy/templates/python-template.json +++ b/internal/policy/templates/python-template.json @@ -19,55 +19,55 @@ }, "rules": [ { - "no": 1, + "id": "1", "say": "PEP 8 스타일 가이드를 준수합니다 (들여쓰기 4칸, 최대 라인 길이 79자)", "category": "formatting" }, { - "no": 2, + "id": "2", "say": "함수와 변수 이름은 snake_case를 사용합니다 (예: calculate_total, user_name)", "category": "naming", "example": "# ✅ 좋은 예:\ndef calculate_total_price(items):\n total_price = sum(item.price for item in items)\n return total_price\n\n# ❌ 나쁜 예:\ndef calculateTotalPrice(items):\n totalPrice = sum(item.price for item in items)\n return totalPrice" }, { - "no": 3, + "id": "3", "say": "클래스 이름은 PascalCase를 사용합니다 (예: UserModel, DataProcessor)", "category": "naming", "example": "# ✅ 좋은 예:\nclass UserProfile:\n def __init__(self, name):\n self.name = name\n\n# ❌ 나쁜 예:\nclass user_profile:\n def __init__(self, name):\n self.name = name" }, { - "no": 4, + "id": "4", "say": "상수는 대문자와 언더스코어를 사용합니다 (예: MAX_SIZE, API_KEY)", "category": "naming" }, { - "no": 5, + "id": "5", "say": "모든 함수와 클래스는 docstring을 작성합니다", "category": "documentation" }, { - "no": 6, + "id": "6", "say": "타입 힌트를 사용하여 함수 시그니처를 명확히 합니다", "category": "documentation" }, { - "no": 7, + "id": "7", "say": "예외는 구체적인 타입으로 처리합니다 (Exception보다는 ValueError, TypeError 등)", "category": "error_handling", "example": "# ✅ 좋은 예:\ntry:\n result = int(user_input)\nexcept ValueError as e:\n logger.error(f\"Invalid input: {e}\")\n result = 0\n\n# ❌ 나쁜 예:\ntry:\n result = int(user_input)\nexcept:\n result = 0" }, { - "no": 8, + "id": "8", "say": "리소스(파일, 데이터베이스)는 with 문으로 관리합니다", "category": "error_handling" }, { - "no": 9, + "id": "9", "say": "민감한 정보는 환경 변수나 설정 파일을 통해 관리합니다", "category": "security" }, { - "no": 10, + "id": "10", "say": "리스트 컴프리헨션은 3줄 이내로 유지하고, 복잡한 경우 일반 for문을 사용합니다", "category": "formatting" } diff --git a/internal/policy/templates/react-template.json b/internal/policy/templates/react-template.json index 3c16a26..82d0d6b 100644 --- a/internal/policy/templates/react-template.json +++ b/internal/policy/templates/react-template.json @@ -19,60 +19,60 @@ }, "rules": [ { - "no": 1, + "id": "1", "say": "컴포넌트 이름은 PascalCase를 사용합니다 (예: UserProfile, NavBar)", "category": "naming", "languages": ["javascript", "typescript", "jsx", "tsx"], "example": "// ✅ 좋은 예:\nfunction UserProfile() { ... }\nfunction NavBar() { ... }\n\n// ❌ 나쁜 예:\nfunction userProfile() { ... }\nfunction nav_bar() { ... }" }, { - "no": 2, + "id": "2", "say": "React Hooks는 함수 컴포넌트 최상단에서만 호출합니다", "category": "error_handling", "languages": ["javascript", "typescript", "jsx", "tsx"], "example": "// ✅ 좋은 예:\nfunction MyComponent() {\n const [count, setCount] = useState(0);\n const value = useMemo(() => count * 2, [count]);\n return