Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 10 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
**LLM-Friendly Convention Linter for AI Coding Tools**

Symphony는 AI 개발환경(IDE, MCP 기반 LLM Tooling)을 위한 정책 기반 코드 컨벤션 검사기입니다.
간단한 설정만으로 프로젝트 규칙을 일관되게 적용하고, LLM 코드 생성 품질을 극대화할 수 있습니다.
간단한 설정만으로 프로젝트 규칙을 일관되게 적용하고, LLM 코드 생성 품질을 향상시킬 수 있습니다.

---

Expand All @@ -27,7 +27,6 @@ Symphony는 AI 개발환경(IDE, MCP 기반 LLM Tooling)을 위한 정책 기반
- [`edit_convention`](#edit_convention)
- [`remove_convention`](#remove_convention)
- [`convert`](#convert)
- [컨벤션 파일](#컨벤션-파일)
- [요구사항](#요구사항)
- [지원 플랫폼](#지원-플랫폼)
- [라이선스](#라이선스)
Expand All @@ -52,6 +51,12 @@ npm install -g @dev-symphony/sym

# 2. 프로젝트 초기화 (.sym/ 폴더 생성 + MCP 설정)
sym init

# 3. 컨벤션 관리
# LLM IDE를 통해 기존 문서나 자연어로 컨벤션을 생성, 관리합니다. '컨벤션 관리' 부분을 참고해 주세요.

# 4. 컨벤션 적용
# LLM IDE에서 작업하면, 작업 전 컨벤션을 자동으로 가져오고, 작업 후 컨벤션을 자동으로 검증합니다.
```

---
Expand All @@ -60,13 +65,13 @@ sym init

컨벤션은 아래 3가지 방식으로 관리할 수 있습니다:

- **CLI 명령어**: `sym category|convention|import|convert`
- **MCP 도구(권장)**: `list_*`, `add_*`, `edit_*`, `remove_*`, `import_convention`, `convert`
- **Dashboard**: `sym dash`로 웹에서 편집
- **CLI 명령어**: `sym category|convention|import|convert`

권장사항: **LLM IDE(Cursor/Claude Code 등)를 사용한다면 MCP 기반 관리**를 권장합니다(조회/편집/변환/검증을 일관된 플로우로 자동화 가능).
권장사항: **LLM IDE(Cursor/Claude Code 등)를 사용한다면 MCP 기반 관리**를 권장합니다.

예시 문장: `docs/team-standards.md`를 컨벤션에 반영해줘.
예시 문장: "`docs/team-standards.md`를 컨벤션에 반영해줘."

자세한 내용은 [`docs/CONVENTION_MANAGEMENT.md`](docs/CONVENTION_MANAGEMENT.md)를 참고하세요.

Expand Down Expand Up @@ -150,44 +155,6 @@ sym init

---

## 컨벤션 파일

Symphony는 프로젝트 컨벤션을 **정책 파일(`.sym/user-policy.json`)**로 관리합니다.
아래 명령으로 대시보드를 열어 쉽게 편집할 수 있습니다.

```bash
sym dashboard
```

컨벤션/카테고리를 수정한 후에는 아래 명령으로 린터 설정을 갱신하세요:

```bash
sym convert
```

자세한 관리 방법은 문서에서 확인할 수 있습니다: [`docs/CONVENTION_MANAGEMENT.md`](docs/CONVENTION_MANAGEMENT.md)

예시 정책 파일:

```json
{
"version": "1.0.0",
"rules": [
{
"say": "Functions should be documented",
"category": "documentation"
},
{
"say": "Lines should be less than 100 characters",
"category": "formatting",
"params": { "max": 100 }
}
]
}
```

---

## 요구사항

- Node.js >= 16.0.0
Expand Down
131 changes: 106 additions & 25 deletions docs/CONVENTION_MANAGEMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

- **MCP 방식**: Cursor/Claude Code 등 LLM 도구가 MCP 도구를 호출해 규칙/카테고리를 조회·변경
- **Dashboard 방식**: 로컬 웹 대시보드에서 직접 편집
- **Import 방식**: 기존 문서(마크다운/텍스트/코드)에서 LLM이 컨벤션을 추출해 정책에 병합
- **CLI 방식**: `sym` command를 사용해 규칙/카테고리를 조회·변경

> 중요: 컨벤션/카테고리를 추가/편집/삭제한 뒤에는 **변환(`convert`)을 실행해** `.sym/code-policy.json` 및 린터 설정 파일을 갱신하는 것을 권장합니다.

Expand Down Expand Up @@ -156,12 +156,6 @@ sym dashboard
sym dash
```

이 저장소에서 빌드된 바이너리를 사용한다면 다음도 가능합니다:

```bash
./bin/sym dash
```

브라우저에서 `http://localhost:8787`로 접속합니다.

### 카테고리 관리
Expand All @@ -173,40 +167,114 @@ sym dash
### 컨벤션(규칙) 관리

- **추가**: 규칙 ID, 설명(say), 카테고리, 언어, 심각도 등을 입력
- **편집**: 기존 규칙의 속성 수정(언어/카테고리 변경 포함)
- **편집**: 기존 규칙의 속성 수정
- **삭제**: 규칙 삭제

### 변환

대시보드에서 `user-policy.json`을 수정한 뒤, 저장을 누르면 convert할지 요청을 합니다.
대시보드에서 저장을 누르면 convert할지 요청을 합니다.

---

## 3) Import로 컨벤션 관리 (문서 → 정책 병합)
## 3) CLI를 사용한 컨벤션/카테고리 관리

Import는 팀 가이드/보안 규칙/코딩 표준 문서처럼 **이미 존재하는 문서**를 기준으로, LLM이 컨벤션을 추출해 `.sym/user-policy.json`에 병합하는 방식입니다.
CLI 방식은 `sym` 명령어로 **카테고리/컨벤션(규칙)을 조회·추가·편집·삭제**하고, 마지막에 `convert`/`validate`로 파생 산출물 생성 및 검증까지 한 번에 처리하는 방법입니다.

### 권장 흐름(사용자 관점)
### 준비

1. 문서 준비: 예) `docs/team-standards.md`
2. Import 실행(아래 3가지 방법 중 택1)
3. `convert` 실행(파생 정책/린터 설정 갱신)
4. (선택) `validate` 또는 MCP `validate_code`로 Git 변경사항 검증
- 프로젝트가 초기화돼 있어야 합니다.

### (A) CLI로 Import (`sym import`)
```bash
sym init
```

- (선택) 현재 사용 중인 정책 파일 경로를 확인합니다.

```bash
# 기본: append (기존 유지 + 새 항목 추가)
sym import docs/team-standards.md
sym policy path
```

# 기존 컨벤션을 비우고 새로 구성하려면 (주의)
sym import docs/team-standards.md --mode clear
### 기본 흐름(권장)

# Import 이후 파생 정책/린터 설정 갱신
1. 카테고리 조회/정리
2. 컨벤션(규칙) 조회/정리
3. **`sym convert` 실행**(파생 정책/린터 설정 갱신)
4. (선택) **`sym validate` 실행**(Git 변경사항 검증)

### 카테고리 관리

```bash
# 목록
sym category list

# 추가
sym category add accessibility "Accessibility rules (WCAG, ARIA, etc.)"

# 편집(이름/설명)
sym category edit security --description "Updated security rules"
sym category edit old-name --name new-name

# 삭제(참조 중인 규칙이 있으면 삭제 불가)
sym category remove deprecated-category
```

> 팁: 여러 항목을 한 번에 처리해야 한다면 `-f <file.json>` 형태의 파일 입력 옵션을 사용할 수 있습니다.

### 컨벤션(규칙) 관리

```bash
# 목록(필터)
sym convention list
sym convention list --category security
sym convention list --language go

# 추가
sym convention add SEC-001 "Use parameterized queries" --category security --languages go,python --severity error

# 편집
sym convention edit SEC-001 --severity warning
sym convention edit SEC-001 --new-id SEC-001-v2

# 삭제
sym convention remove SEC-001
```

### 변환/검증

컨벤션/카테고리를 변경한 뒤에는 파생 정책 및 린터 설정이 최신이 되도록 변환을 실행하는 것을 권장합니다.

```bash
# 파생 정책/린터 설정 갱신
sym convert

# 정책 파일을 직접 지정하거나, 출력 디렉토리를 바꾸고 싶다면
sym convert -i user-policy.json
sym convert -i user-policy.json -o ./custom-dir
```

Git 변경사항 기준으로 정책 위반을 검증하려면:

```bash
sym validate
sym validate --staged
sym validate --policy custom-policy.json
sym validate --timeout 60
```

### (B) MCP로 Import (`import_convention`)
---

## Import로 컨벤션 관리 (문서 → 정책 병합)

Import는 팀 가이드/보안 규칙/코딩 표준 문서처럼 **이미 존재하는 문서**를 기준으로, LLM이 컨벤션을 추출해 `.sym/user-policy.json`에 병합하는 방식입니다.

### 권장 흐름(사용자 관점)

1. 문서 준비: 예) `docs/team-standards.md`
2. Import 실행(아래 3가지 방법 중 택1)
3. `convert` 실행(파생 정책/린터 설정 갱신)
4. (선택) `validate` 또는 MCP `validate_code`로 Git 변경사항 검증

### (A) MCP로 Import (`import_convention`)

LLM IDE(Cursor/Claude Code 등)에서 Symphony MCP 도구를 사용할 수 있다면 다음 흐름을 권장합니다:

Expand All @@ -225,7 +293,20 @@ LLM IDE(Cursor/Claude Code 등)에서 Symphony MCP 도구를 사용할 수 있

> 예: “`docs/team-standards.md`를 컨벤션으로 반영해줘”라고 요청하면, LLM이 `import_convention` → `convert` → (필요 시) `validate_code` 순으로 실행하는 형태를 기대할 수 있습니다.

### (C) Dashboard에서 Import
### (B) Dashboard에서 Import

대시보드(`sym dash` 또는 `./bin/sym dash`)에서도 Import UI를 통해 문서를 선택해 정책에 병합할 수 있습니다.
Import 후에는 저장을 누르면 convert할지 요청을 합니다.
Import 후에는 저장을 누르면 convert할지 사용자에게 요청을 합니다.

### (C) CLI로 Import (`sym import`)

```bash
# 기본: append (기존 유지 + 새 항목 추가)
sym import docs/team-standards.md

# 기존 컨벤션을 비우고 새로 구성하려면 (주의)
sym import docs/team-standards.md --mode clear

# Import 이후 파생 정책/린터 설정 갱신
sym convert
```
76 changes: 72 additions & 4 deletions internal/cmd/convention.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,17 @@ Available subcommands:
var conventionListCmd = &cobra.Command{
Use: "list",
Short: "List all conventions",
Long: `List all conventions with their ID, category, description, and severity.
Long: `List conventions with their ID, category, description, and severity.

Conventions are defined in user-policy.json and can be customized by the user.
Run 'sym init' to create default conventions.`,
Run 'sym init' to create default conventions.

Examples:
sym convention list
sym convention list --category security
sym convention list --languages go
sym convention list --languages go,javascript
sym convention list --category style --languages typescript`,
RunE: runConventionList,
}

Expand Down Expand Up @@ -168,9 +175,22 @@ func init() {

// Remove command flags
conventionRemoveCmd.Flags().StringP("file", "f", "", "JSON file with convention IDs to remove")

// List command flags
conventionListCmd.Flags().String("category", "", "Filter by category (e.g., security, style, documentation)")
conventionListCmd.Flags().StringSlice("languages", nil, "Filter by programming languages (comma-separated)")
}

func runConventionList(cmd *cobra.Command, args []string) error {
// Get filter flags
categoryFilter, _ := cmd.Flags().GetString("category")
languagesFilter, _ := cmd.Flags().GetStringSlice("languages")

// Normalize category: "all" or empty means no filtering
if strings.EqualFold(categoryFilter, "all") {
categoryFilter = ""
}

// Load conventions from user-policy.json
userPolicy, err := roles.LoadUserPolicyFromRepo()
if err != nil {
Expand All @@ -186,10 +206,46 @@ func runConventionList(cmd *cobra.Command, args []string) error {
return nil
}

printTitle("Conventions", fmt.Sprintf("%d conventions available", len(rules)))
// Filter rules
var filteredRules []schema.UserRule
for _, rule := range rules {
// Category filter
if categoryFilter != "" && rule.Category != categoryFilter {
continue
}

// Languages filter: if both request and rule have languages, check intersection
if len(languagesFilter) > 0 && len(rule.Languages) > 0 {
if !containsAny(rule.Languages, languagesFilter) {
continue
}
}

filteredRules = append(filteredRules, rule)
}

// Build filter description for output
filterDesc := ""
if categoryFilter != "" || len(languagesFilter) > 0 {
parts := []string{}
if categoryFilter != "" {
parts = append(parts, fmt.Sprintf("category=%s", categoryFilter))
}
if len(languagesFilter) > 0 {
parts = append(parts, fmt.Sprintf("languages=%s", strings.Join(languagesFilter, ",")))
}
filterDesc = fmt.Sprintf(" (filtered: %s)", strings.Join(parts, ", "))
}

if len(filteredRules) == 0 {
printWarn(fmt.Sprintf("No conventions found%s", filterDesc))
return nil
}

printTitle("Conventions", fmt.Sprintf("%d conventions available%s", len(filteredRules), filterDesc))
fmt.Println()

for _, rule := range rules {
for _, rule := range filteredRules {
// Format: ID [CATEGORY] (languages): description
languages := ""
if len(rule.Languages) > 0 {
Expand Down Expand Up @@ -569,6 +625,18 @@ func runConventionRemove(cmd *cobra.Command, args []string) error {
return nil
}

// containsAny checks if haystack contains any of the needles.
func containsAny(haystack, needles []string) bool {
for _, needle := range needles {
for _, hay := range haystack {
if hay == needle {
return true
}
}
}
return false
}

// printConventionBatchResult prints the result of a batch operation for conventions.
func printConventionBatchResult(action string, succeeded, failed []string) {
if len(failed) == 0 && len(succeeded) > 0 {
Expand Down
Loading