From 08107e25216ada1626359277945f423df966a6fa Mon Sep 17 00:00:00 2001 From: ikjeong Date: Wed, 10 Dec 2025 07:02:00 +0000 Subject: [PATCH 1/5] chore: add CLAUDE.md and AGENTS.md to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 08bba7b..0cc1bfc 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ coverage.txt !npm/bin/ !npm/bin/*.js npm/bin/sym-* + +CLAUDE.md +AGENTS.md From b98ae450b722aea9de07b150f079a1faff0ef861 Mon Sep 17 00:00:00 2001 From: ikjeong Date: Wed, 10 Dec 2025 07:04:08 +0000 Subject: [PATCH 2/5] feat: add CategoryDef type and Category field to UserPolicy - Add CategoryDef struct with name and description fields - Add Category field to UserPolicy for user-defined categories --- pkg/schema/types.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/schema/types.go b/pkg/schema/types.go index 2b26c08..39ee11f 100644 --- a/pkg/schema/types.go +++ b/pkg/schema/types.go @@ -1,8 +1,15 @@ package schema +// CategoryDef represents a category definition with description +type CategoryDef struct { + Name string `json:"name"` // Category identifier (e.g., "security") + Description string `json:"description"` // Natural language description (1-2 lines) +} + // UserPolicy represents the user-friendly policy schema (A schema) type UserPolicy struct { Version string `json:"version,omitempty"` + Category []CategoryDef `json:"category,omitempty"` // User-defined categories with descriptions RBAC *UserRBAC `json:"rbac,omitempty"` Defaults *UserDefaults `json:"defaults,omitempty"` Rules []UserRule `json:"rules"` From 71f1e25f0976a83a16d0b0249c4622269b928d8c Mon Sep 17 00:00:00 2001 From: ikjeong Date: Wed, 10 Dec 2025 07:04:28 +0000 Subject: [PATCH 3/5] feat: add sym category command and list_category MCP tool - Add sym category CLI command to list categories - Add list_category MCP tool for AI tools integration - Add default categories (7) to sym init --- internal/cmd/category.go | 52 ++++++++++++++++++++++++++++++++++++++++ internal/cmd/init.go | 11 ++++++++- internal/mcp/server.go | 49 ++++++++++++++++++++++++++++++++++++- 3 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 internal/cmd/category.go diff --git a/internal/cmd/category.go b/internal/cmd/category.go new file mode 100644 index 0000000..332904d --- /dev/null +++ b/internal/cmd/category.go @@ -0,0 +1,52 @@ +package cmd + +import ( + "fmt" + + "github.com/DevSymphony/sym-cli/internal/roles" + "github.com/spf13/cobra" +) + +var categoryCmd = &cobra.Command{ + Use: "category", + Short: "List all available convention categories", + Long: `List all convention categories with their descriptions. + +Categories are defined in user-policy.json and can be customized by the user. +Run 'sym init' to create default categories (security, style, documentation, +error_handling, architecture, performance, testing). + +You can add, remove, or modify categories directly in user-policy.json.`, + RunE: runCategory, +} + +func init() { + rootCmd.AddCommand(categoryCmd) +} + +func runCategory(cmd *cobra.Command, args []string) error { + // Load categories from user-policy.json + userPolicy, err := roles.LoadUserPolicyFromRepo() + if err != nil { + printWarn("Failed to load user-policy.json") + fmt.Println("Run 'sym init' to create default categories") + return nil + } + + categories := userPolicy.Category + if len(categories) == 0 { + printWarn("No categories defined in user-policy.json") + fmt.Println("Run 'sym init' to create default categories") + return nil + } + + printTitle("Convention Categories", fmt.Sprintf("%d categories available", len(categories))) + fmt.Println() + + for _, cat := range categories { + fmt.Printf(" %s %s\n", colorize(bold, "•"), colorize(cyan, cat.Name)) + fmt.Printf(" %s\n\n", cat.Description) + } + + return nil +} diff --git a/internal/cmd/init.go b/internal/cmd/init.go index a083cfd..15778ff 100644 --- a/internal/cmd/init.go +++ b/internal/cmd/init.go @@ -146,9 +146,18 @@ func createDefaultPolicy() error { return nil } - // Create default policy with admin, developer, viewer RBAC roles + // Create default policy with categories and RBAC roles defaultPolicy := &schema.UserPolicy{ Version: "1.0.0", + Category: []schema.CategoryDef{ + {Name: "security", Description: "Security rules (authentication, authorization, vulnerability prevention, etc.)"}, + {Name: "style", Description: "Code style and formatting rules"}, + {Name: "documentation", Description: "Documentation rules (comments, docstrings, etc.)"}, + {Name: "error_handling", Description: "Error handling and exception management rules"}, + {Name: "architecture", Description: "Code structure and architecture rules"}, + {Name: "performance", Description: "Performance optimization rules"}, + {Name: "testing", Description: "Testing rules (coverage, test patterns, etc.)"}, + }, RBAC: &schema.UserRBAC{ Roles: map[string]schema.UserRole{ "admin": { diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 73d19aa..7fe4149 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -174,7 +174,7 @@ func (s *Server) Start() error { } fmt.Fprintln(os.Stderr, "Symphony MCP server started (stdio mode)") - fmt.Fprintln(os.Stderr, "Available tools: query_conventions, validate_code") + fmt.Fprintln(os.Stderr, "Available tools: query_conventions, validate_code, list_category") // Use official MCP go-sdk for stdio to ensure spec-compliant framing and lifecycle return s.runStdioWithSDK(context.Background()) @@ -197,6 +197,11 @@ type ValidateCodeInput struct { Role string `json:"role,omitempty" jsonschema:"RBAC role for validation (optional)"` } +// ListCategoryInput represents the input schema for the list_category tool (go-sdk). +type ListCategoryInput struct { + // No parameters - returns all categories +} + // runStdioWithSDK runs a spec-compliant MCP server over stdio using the official go-sdk. func (s *Server) runStdioWithSDK(ctx context.Context) error { server := sdkmcp.NewServer(&sdkmcp.Implementation{ @@ -236,6 +241,18 @@ func (s *Server) runStdioWithSDK(ctx context.Context) error { return nil, result.(map[string]any), nil }) + // Tool: list_category + sdkmcp.AddTool(server, &sdkmcp.Tool{ + Name: "list_category", + Description: "List all available convention categories with descriptions. Use this to discover what categories exist before querying conventions.", + }, func(ctx context.Context, req *sdkmcp.CallToolRequest, input ListCategoryInput) (*sdkmcp.CallToolResult, map[string]any, error) { + result, rpcErr := s.handleListCategory() + if rpcErr != nil { + return &sdkmcp.CallToolResult{IsError: true}, nil, fmt.Errorf("%s", rpcErr.Message) + } + return nil, result.(map[string]any), nil + }) + // Run the server over stdio until the client disconnects return server.Run(ctx, &sdkmcp.StdioTransport{}) } @@ -808,3 +825,33 @@ func (s *Server) saveValidationResults(result *validator.ValidationResult, viola fmt.Fprintf(os.Stderr, "✓ Validation results saved to %s\n", resultsPath) return nil } + +// handleListCategory handles category listing requests. +func (s *Server) handleListCategory() (interface{}, *RPCError) { + category := s.getCategory() + + var textContent string + if len(category) == 0 { + textContent = "No categories defined in user-policy.json.\n\nRun 'sym init' to create default categories." + } else { + textContent = fmt.Sprintf("Available categories (%d):\n\n", len(category)) + for _, cat := range category { + textContent += fmt.Sprintf("• %s\n %s\n\n", cat.Name, cat.Description) + } + textContent += "Use query_conventions with a specific category to get rules for that category." + } + + return map[string]interface{}{ + "content": []map[string]interface{}{ + {"type": "text", "text": textContent}, + }, + }, nil +} + +// getCategory returns categories from user-policy.json. +func (s *Server) getCategory() []schema.CategoryDef { + if s.userPolicy != nil { + return s.userPolicy.Category + } + return nil +} From 785d8906b19ef78f1ebee985bd52120d07201905 Mon Sep 17 00:00:00 2001 From: ikjeong Date: Wed, 10 Dec 2025 07:04:37 +0000 Subject: [PATCH 4/5] test: add TestListCategory tests for MCP server --- internal/mcp/server_test.go | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/internal/mcp/server_test.go b/internal/mcp/server_test.go index a8686e6..e8b3f5d 100644 --- a/internal/mcp/server_test.go +++ b/internal/mcp/server_test.go @@ -208,3 +208,71 @@ func TestQueryConventions(t *testing.T) { assert.NotContains(t, text, "DOC-001") }) } + +func TestListCategory(t *testing.T) { + t.Run("returns no categories message when no user policy", func(t *testing.T) { + server := &Server{ + loader: policy.NewLoader(false), + } + + result, rpcErr := server.handleListCategory() + require.Nil(t, rpcErr) + require.NotNil(t, result) + + resultMap := result.(map[string]interface{}) + content := resultMap["content"].([]map[string]interface{}) + text := content[0]["text"].(string) + + t.Logf("Result: %s", text) + + // Should show no categories message (categories are now only from user-policy.json) + assert.Contains(t, text, "No categories defined in user-policy.json") + assert.Contains(t, text, "Run 'sym init' to create default categories") + }) + + t.Run("returns only user-defined categories from user-policy.json", func(t *testing.T) { + // Setup: Create a temporary user policy with custom categories + tmpDir := t.TempDir() + userPolicyPath := filepath.Join(tmpDir, "user-policy.json") + + userPolicyJSON := `{ + "version": "1.0.0", + "category": [ + {"name": "security", "description": "Custom security description"}, + {"name": "naming", "description": "Naming convention rules"} + ], + "rules": [] +}` + + err := os.WriteFile(userPolicyPath, []byte(userPolicyJSON), 0644) + require.NoError(t, err) + + server := &Server{ + configPath: userPolicyPath, + loader: policy.NewLoader(false), + } + + // Load user policy + userPolicy, err := server.loader.LoadUserPolicy(userPolicyPath) + require.NoError(t, err) + server.userPolicy = userPolicy + + result, rpcErr := server.handleListCategory() + require.Nil(t, rpcErr) + require.NotNil(t, result) + + resultMap := result.(map[string]interface{}) + content := resultMap["content"].([]map[string]interface{}) + text := content[0]["text"].(string) + + t.Logf("Result: %s", text) + + // Should include user-defined categories only (no merging with defaults) + assert.Contains(t, text, "security") + assert.Contains(t, text, "Custom security description") + assert.Contains(t, text, "naming") + assert.Contains(t, text, "Naming convention rules") + // Should have only 2 categories (user-defined only) + assert.Contains(t, text, "Available categories (2)") + }) +} From fcb94a3f42d63aa5d3951db6e831e3a8311a5db5 Mon Sep 17 00:00:00 2001 From: ikjeong Date: Wed, 10 Dec 2025 07:04:46 +0000 Subject: [PATCH 5/5] docs: update documentation for category feature - Update sym category and list_category descriptions - Add list_category to MCP tool lists - Add CategoryDef type documentation --- docs/ARCHITECTURE.md | 1 + docs/COMMAND.md | 106 ++++++++++++++++++++++++++++++++++++++++- internal/cmd/README.md | 3 ++ internal/mcp/README.md | 3 +- pkg/schema/README.md | 19 +++++++- 5 files changed, 129 insertions(+), 3 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b1088ff..aa811c7 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -140,6 +140,7 @@ AI 코딩 도구(Claude Code, Cursor 등)와 stdio를 통해 통신합니다. |------|-------------| | `query_conventions` | 프로젝트 컨벤션 조회 | | `validate_code` | 코드 변경사항 검증 | +| `list_category` | 카테고리 목록 조회 | #### HTTP Server (`internal/server`) diff --git a/docs/COMMAND.md b/docs/COMMAND.md index fd2dbeb..99891e5 100644 --- a/docs/COMMAND.md +++ b/docs/COMMAND.md @@ -21,6 +21,7 @@ Symphony (`sym`)는 코드 컨벤션 관리와 RBAC(역할 기반 접근 제어) - [sym policy validate](#sym-policy-validate) - [sym convert](#sym-convert) - [sym validate](#sym-validate) + - [sym category](#sym-category) - [sym mcp](#sym-mcp) - [sym llm](#sym-llm) - [sym llm status](#sym-llm-status) @@ -39,6 +40,7 @@ Symphony (`sym`)는 코드 컨벤션 관리와 RBAC(역할 기반 접근 제어) - [MCP 도구 스키마](#mcp-도구-스키마) - [query\_conventions](#query_conventions) - [validate\_code](#validate_code) + - [list\_category](#list_category) - [등록 방법](#등록-방법) - [LLM 프로바이더](#llm-프로바이더) - [지원 프로바이더](#지원-프로바이더) @@ -104,6 +106,7 @@ sym │ └── validate # 정책 파일 유효성 검사 ├── convert # 정책 → 린터 설정 변환 ├── validate # Git 변경사항 검증 +├── category # 카테고리 목록 조회 ├── mcp # MCP 서버 실행 ├── llm # LLM 프로바이더 관리 │ ├── status # 현재 설정 확인 @@ -124,7 +127,7 @@ sym **수행 작업**: 1. `.sym/roles.json` 생성 (기본 역할: admin, developer, viewer) -2. `.sym/user-policy.json` 생성 (기본 RBAC 설정) +2. `.sym/user-policy.json` 생성 (기본 카테고리 7개 + RBAC 설정) 3. `.sym/config.json` 생성 (기본 설정) 4. 역할을 admin으로 설정 (대시보드에서 변경 가능) 5. MCP 서버 등록 (선택적) @@ -367,6 +370,68 @@ sym validate --timeout 60 --- +### sym category + +**설명**: 사용 가능한 모든 컨벤션 카테고리와 설명을 표시합니다. + +user-policy.json에 정의된 카테고리를 표시합니다. `sym init` 실행 시 7개의 기본 카테고리(security, style, documentation, error_handling, architecture, performance, testing)가 생성됩니다. 사용자는 이 카테고리를 수정, 삭제하거나 새로운 카테고리를 추가할 수 있습니다. + +**문법**: +``` +sym category +``` + +**예시**: +```bash +# 카테고리 목록 조회 +sym category +``` + +**출력 예시**: +``` +[Convention Categories] 7 categories available + + • security + Security rules (authentication, authorization, vulnerability prevention, etc.) + + • style + Code style and formatting rules + + • documentation + Documentation rules (comments, docstrings, etc.) + + • error_handling + Error handling and exception management rules + + • architecture + Code structure and architecture rules + + • performance + Performance optimization rules + + • testing + Testing rules (coverage, test patterns, etc.) +``` + +**사용자 정의 카테고리**: + +user-policy.json에 `category` 필드를 추가하여 사용자 정의 카테고리를 추가하거나 기존 카테고리 설명을 변경할 수 있습니다: + +```json +{ + "version": "1.0", + "category": [ + {"name": "security", "description": "보안 관련 규칙 (인증, 인가, 취약점 방지 등)"}, + {"name": "naming", "description": "네이밍 컨벤션 규칙 (변수, 함수, 클래스 등)"} + ], + "rules": [...] +} +``` + +**관련 파일**: `internal/cmd/category.go` + +--- + ### sym mcp **설명**: MCP(Model Context Protocol) 서버를 시작합니다. LLM 기반 코딩 도구가 stdio를 통해 컨벤션을 쿼리하고 코드를 검증할 수 있습니다. @@ -374,6 +439,7 @@ sym validate --timeout 60 **제공되는 MCP 도구**: - `query_conventions`: 주어진 컨텍스트에 대한 컨벤션 쿼리 - `validate_code`: 코드의 컨벤션 준수 여부 검증 +- `list_category`: 사용 가능한 카테고리 목록 조회 **통신 방식**: stdio (Claude Desktop, Claude Code, Cursor 등 MCP 클라이언트와 통합) @@ -622,6 +688,44 @@ Git 변경사항을 프로젝트 컨벤션에 대해 검증합니다. |----------|------|------|------| | `role` | string | 아니오 | 검증용 RBAC 역할 (선택) | +#### list_category + +사용 가능한 모든 카테고리와 설명을 반환합니다. + +user-policy.json에 정의된 카테고리를 반환합니다. 카테고리가 없으면 `sym init`을 실행하라는 안내 메시지를 반환합니다. + +**입력 스키마**: + +파라미터 없음 (모든 카테고리 반환) + +**출력 예시**: +``` +Available categories (7): + +• security + Security rules (authentication, authorization, vulnerability prevention, etc.) + +• style + Code style and formatting rules + +• documentation + Documentation rules (comments, docstrings, etc.) + +• error_handling + Error handling and exception management rules + +• architecture + Code structure and architecture rules + +• performance + Performance optimization rules + +• testing + Testing rules (coverage, test patterns, etc.) + +Use query_conventions with a specific category to get rules for that category. +``` + ### 등록 방법 ```bash diff --git a/internal/cmd/README.md b/internal/cmd/README.md index 256ab9d..1c82baf 100644 --- a/internal/cmd/README.md +++ b/internal/cmd/README.md @@ -20,6 +20,7 @@ cmd/ ├── llm.go # sym llm status|test|setup 명령어 (LLM 관리) ├── mcp.go # sym mcp 명령어 (MCP 서버) ├── mcp_register.go # MCP 서버 등록 헬퍼 함수 +├── category.go # sym category 명령어 (카테고리 목록) ├── survey_templates.go # 커스텀 survey UI 템플릿 └── README.md ``` @@ -84,6 +85,7 @@ cmd/ | `llmTestCmd` | llm.go:40 | llm test 명령어 | | `llmSetupCmd` | llm.go:47 | llm setup 명령어 | | `mcpCmd` | mcp.go:15 | mcp 명령어 | +| `categoryCmd` | category.go:10 | category 명령어 | #### 명령어 실행 함수 @@ -100,6 +102,7 @@ cmd/ | `runLLMTest(cmd, args)` | llm.go:107 | llm test 실행 | | `runLLMSetup(cmd, args)` | llm.go:142 | llm setup 실행 | | `runMCP(cmd, args)` | mcp.go:37 | mcp 실행 | +| `runCategory(cmd, args)` | category.go:27 | category 실행 | #### 헬퍼 함수 - 초기화 diff --git a/internal/mcp/README.md b/internal/mcp/README.md index c2154a1..739f728 100644 --- a/internal/mcp/README.md +++ b/internal/mcp/README.md @@ -53,6 +53,7 @@ mcp/ | `RPCError` | server.go:184 | JSON-RPC 에러 타입 | | `QueryConventionsInput` | server.go:190 | query_conventions 입력 스키마 | | `ValidateCodeInput` | server.go:196 | validate_code 입력 스키마 | +| `ListCategoryInput` | server.go:202 | list_category 입력 스키마 | | `QueryConventionsRequest` | server.go:244 | 컨벤션 조회 요청 | | `ConventionItem` | server.go:250 | 컨벤션 항목 | | `ValidateCodeRequest` | server.go:411 | 검증 요청 | @@ -92,5 +93,5 @@ mcp/ ## 참고 문헌 -- [MCP 도구 스키마](../../docs/COMMAND.md#mcp-도구-스키마) - query_conventions, validate_code 입력/출력 스펙 +- [MCP 도구 스키마](../../docs/COMMAND.md#mcp-도구-스키마) - query_conventions, validate_code, list_category 입력/출력 스펙 - [MCP 통합 가이드](../../docs/COMMAND.md#mcp-통합) - 지원 도구 및 등록 방법 diff --git a/pkg/schema/README.md b/pkg/schema/README.md index 287d1b6..3f4f0f3 100644 --- a/pkg/schema/README.md +++ b/pkg/schema/README.md @@ -36,7 +36,8 @@ pkg/schema/ ``` ┌─────────────────────────────────────────────────────────────────┐ │ A Schema (사용자 친화적) │ -│ UserPolicy ──┬── UserRBAC ─── UserRole │ +│ UserPolicy ──┬── CategoryDef[] │ +│ ├── UserRBAC ─── UserRole │ │ ├── UserDefaults │ │ └── UserRule[] │ └────────────────────────────┬────────────────────────────────────┘ @@ -59,6 +60,17 @@ pkg/schema/ 사용자가 자연어로 컨벤션을 정의하기 위한 간소화된 스키마입니다. +### CategoryDef + +카테고리 정의 구조체입니다. + +```go +type CategoryDef struct { + Name string `json:"name"` // 카테고리 식별자 (예: "security") + Description string `json:"description"` // 자연어 설명 (1-2줄) +} +``` + ### UserPolicy 메인 정책 구조체입니다. @@ -66,6 +78,7 @@ pkg/schema/ ```go type UserPolicy struct { Version string `json:"version,omitempty"` // 정책 버전 (예: "1.0.0") + Category []CategoryDef `json:"category,omitempty"` // 카테고리 정의 목록 RBAC *UserRBAC `json:"rbac,omitempty"` // 역할 기반 접근 제어 Defaults *UserDefaults `json:"defaults,omitempty"` // 규칙 기본값 Rules []UserRule `json:"rules"` // 자연어 규칙 목록 @@ -265,6 +278,10 @@ type RBACEnforce struct { ```json { "version": "1.0.0", + "category": [ + {"name": "security", "description": "보안 관련 규칙"}, + {"name": "naming", "description": "네이밍 컨벤션 규칙"} + ], "rbac": { "roles": { "admin": { "allowWrite": ["**/*"], "canEditPolicy": true },