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
14 changes: 0 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

자연어 기반 코딩 컨벤션 관리 및 검증 도구

[![Test Coverage](https://img.shields.io/badge/coverage-view%20report-blue)](https://devsymphony.github.io/sym-cli/coverage.html)

## 빠른 시작

### 1. 설치
Expand All @@ -15,15 +13,10 @@ npm i -g @dev-symphony/sym
### 2. 초기화

```bash
# GitHub OAuth 로그인
sym login

# 프로젝트 초기화 (.sym/ 폴더 생성, MCP 자동 설정)
sym init
```

> **참고**: LLM 기반 컨벤션 변환을 사용하려면 `OPENAI_API_KEY` 환경변수가 필요합니다.

### 3. 사용

**웹 대시보드로 컨벤션 편집:**
Expand Down Expand Up @@ -61,7 +54,6 @@ sym dashboard

| 명령어 | 설명 |
|--------|------|
| `sym login` | GitHub OAuth 로그인 |
| `sym init` | 프로젝트 초기화 (.sym/ 생성) |
| `sym dashboard` | 웹 대시보드 실행 (포트 8787) |
| `sym my-role` | 내 역할 확인 |
Expand Down Expand Up @@ -109,12 +101,6 @@ make lint # 린트

**필수 도구:** Go 1.21+, Node.js 18+

## 문서

- [Convert 기능 가이드](docs/CONVERT_FEATURE.md)
- [Convert 사용법](docs/CONVERT_USAGE.md)
- [Linter 검증](docs/LINTER_VALIDATION.md)

## 라이선스

MIT License
Expand Down
7 changes: 4 additions & 3 deletions internal/cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"path/filepath"

"github.com/DevSymphony/sym-cli/internal/git"
"github.com/DevSymphony/sym-cli/internal/llm"
"github.com/DevSymphony/sym-cli/internal/ui"
"github.com/DevSymphony/sym-cli/internal/validator"
Expand Down Expand Up @@ -80,15 +81,15 @@ func runValidate(cmd *cobra.Command, args []string) error {
return fmt.Errorf("no available LLM backend for validate: %w\nTip: configure provider in .sym/config.json", err)
}

var changes []validator.GitChange
var changes []git.Change
if validateStaged {
changes, err = validator.GetStagedChanges()
changes, err = git.GetStagedChanges()
if err != nil {
return fmt.Errorf("failed to get staged changes: %w", err)
}
fmt.Println("Validating staged changes...")
} else {
changes, err = validator.GetGitChanges()
changes, err = git.GetChanges()
if err != nil {
return fmt.Errorf("failed to get git changes: %w", err)
}
Expand Down
53 changes: 49 additions & 4 deletions internal/git/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,53 @@
# git

Git 저장소 작업을 위한 유틸리티를 제공합니다.
Git 저장소의 변경 사항 조회 및 저장소 정보를 제공하는 패키지.

저장소 루트 디렉토리 탐색 및 Git 상태 조회 기능을 제공합니다.
## 패키지 구조

**사용자**: cmd, mcp, policy, roles, server
**의존성**: 없음
```
git/
├── changes.go # 변경 사항 조회 및 diff 처리
├── changes_test.go # 테스트
├── repo.go # 저장소 정보 조회
└── README.md
```

## 의존성

### 패키지 사용자

| 패키지 | 용도 |
|--------|------|
| `internal/validator` | 검증 대상 파일 및 diff 획득 |
| `internal/cmd/validate.go` | CLI validate 명령어 |
| `internal/mcp/server.go` | MCP validate_code 도구 |
| `internal/roles` | RBAC 권한 검증 |
| `internal/policy` | 정책 관리 |

### 패키지 의존성

없음.

## Public/Private API

### Public API

**Types**

| 타입 | 파일 | 설명 |
|------|------|------|
| `Change` | changes.go:10 | 파일 변경 정보 (FilePath, Status, Diff) |

**Functions**

| 함수 | 파일 | 설명 |
|------|------|------|
| `GetChanges()` | changes.go:18 | 모든 변경 사항 (staged + unstaged + untracked) |
| `GetStagedChanges()` | changes.go:146 | staged 변경만 |
| `ExtractAddedLines(diff)` | changes.go:185 | diff에서 추가된 줄 추출 |
| `GetRepoRoot()` | repo.go:10 | 저장소 루트 경로 |
| `GetCurrentUser()` | repo.go:21 | Git user.name |

### Private API

없음.
26 changes: 13 additions & 13 deletions internal/validator/git.go → internal/git/changes.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package validator
package git

import (
"fmt"
"os/exec"
"strings"
)

// GitChange represents a file change in git
type GitChange struct {
// Change represents a file change in git
type Change struct {
FilePath string
Status string // A(dded), M(odified), D(eleted)
Diff string
}

// GetGitChanges returns all uncommitted changes in the current git repository
// GetChanges returns all uncommitted changes in the current git repository
// This includes: staged changes, unstaged changes, and untracked files
func GetGitChanges() ([]GitChange, error) {
changes := make([]GitChange, 0)
func GetChanges() ([]Change, error) {
changes := make([]Change, 0)
seenFiles := make(map[string]bool) // Track files we've already processed

// 1. Get staged changes (index vs HEAD)
Expand Down Expand Up @@ -55,7 +55,7 @@ func GetGitChanges() ([]GitChange, error) {
diffOutput, _ = diffCmd.Output()
}

changes = append(changes, GitChange{
changes = append(changes, Change{
FilePath: filePath,
Status: status,
Diff: string(diffOutput),
Expand Down Expand Up @@ -97,7 +97,7 @@ func GetGitChanges() ([]GitChange, error) {
continue
}

changes = append(changes, GitChange{
changes = append(changes, Change{
FilePath: filePath,
Status: status,
Diff: string(diffOutput),
Expand Down Expand Up @@ -131,7 +131,7 @@ func GetGitChanges() ([]GitChange, error) {
// We still get the output in diffOutput regardless of error
_ = err // Ignore error since exit code 1 is expected for diffs

changes = append(changes, GitChange{
changes = append(changes, Change{
FilePath: filePath,
Status: "A", // Treat untracked files as Added
Diff: string(diffOutput),
Expand All @@ -143,7 +143,7 @@ func GetGitChanges() ([]GitChange, error) {
}

// GetStagedChanges returns staged changes
func GetStagedChanges() ([]GitChange, error) {
func GetStagedChanges() ([]Change, error) {
cmd := exec.Command("git", "diff", "--cached", "--name-status")
output, err := cmd.Output()
if err != nil {
Expand All @@ -152,10 +152,10 @@ func GetStagedChanges() ([]GitChange, error) {

lines := strings.Split(strings.TrimSpace(string(output)), "\n")
if len(lines) == 0 || lines[0] == "" {
return []GitChange{}, nil
return []Change{}, nil
}

changes := make([]GitChange, 0, len(lines))
changes := make([]Change, 0, len(lines))
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) < 2 {
Expand All @@ -171,7 +171,7 @@ func GetStagedChanges() ([]GitChange, error) {
continue
}

changes = append(changes, GitChange{
changes = append(changes, Change{
FilePath: filePath,
Status: status,
Diff: string(diffOutput),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package validator
package git

import (
"testing"
Expand Down
38 changes: 0 additions & 38 deletions internal/git/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,9 @@ package git
import (
"fmt"
"os/exec"
"regexp"
"strings"
)

// GetRepoInfo returns the owner and repo name from the current git repository
func GetRepoInfo() (owner, repo string, err error) {
cmd := exec.Command("git", "config", "--get", "remote.origin.url")
output, err := cmd.Output()
if err != nil {
return "", "", fmt.Errorf("not a git repository or no remote origin configured")
}

url := strings.TrimSpace(string(output))

// Parse various Git URL formats:
// https://github.com/owner/repo.git
// git@github.com:owner/repo.git
// https://ghes.company.com/owner/repo.git

// SSH format: git@host:owner/repo.git
sshRegex := regexp.MustCompile(`git@[^:]+:([^/]+)/(.+?)(?:\.git)?$`)
if matches := sshRegex.FindStringSubmatch(url); len(matches) == 3 {
return matches[1], matches[2], nil
}

// HTTPS format: https://host/owner/repo.git
httpsRegex := regexp.MustCompile(`https?://[^/]+/([^/]+)/(.+?)(?:\.git)?$`)
if matches := httpsRegex.FindStringSubmatch(url); len(matches) == 3 {
return matches[1], matches[2], nil
}

return "", "", fmt.Errorf("could not parse repository URL: %s", url)
}

// GetRepoRoot returns the root directory of the git repository
func GetRepoRoot() (string, error) {
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
Expand All @@ -48,13 +17,6 @@ func GetRepoRoot() (string, error) {
return strings.TrimSpace(string(output)), nil
}

// IsGitRepo checks if the current directory is a git repository
func IsGitRepo() bool {
cmd := exec.Command("git", "rev-parse", "--git-dir")
err := cmd.Run()
return err == nil
}

// GetCurrentUser returns the current git user name
func GetCurrentUser() (string, error) {
cmd := exec.Command("git", "config", "--get", "user.name")
Expand Down
2 changes: 2 additions & 0 deletions internal/linter/pylint/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ Return ONLY a JSON object (no markdown fences) with this structure:
"options": {"key": "value", ...}
}

For string options (indent-string, good-names, bad-names, regex patterns), wrap values in single quotes: e.g. {"indent-string": "' '"}

Common Pylint rules:
- Naming: invalid-name (C0103), disallowed-name (C0104)
Options: variable-rgx, function-rgx, class-rgx, const-rgx, argument-rgx
Expand Down
4 changes: 2 additions & 2 deletions internal/mcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,8 @@ func (s *Server) handleValidateCode(ctx context.Context, session *sdkmcp.ServerS
var hasErrors bool

// Get all git changes (staged + unstaged + untracked)
// GetGitChanges already includes all types of changes
changes, err := validator.GetGitChanges()
// GetChanges already includes all types of changes
changes, err := git.GetChanges()
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to get git changes: %v\n", err)
return map[string]interface{}{
Expand Down
93 changes: 89 additions & 4 deletions internal/roles/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,93 @@
# roles

RBAC(Role-Based Access Control)을 구현합니다.
RBAC(Role-Based Access Control) 기능을 제공합니다.

역할별 파일 접근 권한을 검증하며 glob 패턴 기반 읽기/쓰기/실행 권한 관리를 제공합니다.
역할 정의 관리와 파일 접근 권한 검증을 담당합니다.

**사용자**: cmd, server
**의존성**: git, policy
## 패키지 구조

```
roles/
├── roles.go # 역할 정의 관리 (Load/Save, Get/Set)
├── rbac.go # RBAC 권한 검증
└── README.md
```

## 의존성

### 패키지 사용자

| 위치 | 용도 |
|------|------|
| `internal/cmd/init.go` | 초기 역할 생성 |
| `internal/cmd/my_role.go` | 역할 선택 CLI |
| `internal/cmd/dashboard.go` | 대시보드 역할 확인 |
| `internal/server/server.go` | 웹 대시보드 역할 API |
| `internal/mcp/server.go` | MCP RBAC 컨텍스트 |
| `internal/validator/validator.go` | 검증 시 RBAC 권한 확인 |

### 패키지 의존성

```
┌──────────┐
│ roles │
└────┬─────┘
┌─────┼─────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────────┐
│ envutil│ │ git │ │ policy │
└────────┘ └────────┘ └────────────┘
┌────────────┐
│ pkg/schema │
└────────────┘
```

## Public / Private API

### Public API

#### Types

| 타입 | 파일 | 설명 |
|------|------|------|
| `Roles` | roles.go:15 | `map[string][]string` - 역할별 사용자 목록 |
| `ValidationResult` | rbac.go:15 | RBAC 검증 결과 |

#### Functions - 역할 파일 관리

| 함수 | 파일 | 설명 |
|------|------|------|
| `GetRolesPath()` | roles.go:39 | .sym/roles.json 경로 반환 |
| `LoadRoles()` | roles.go:48 | roles.json 로드 |
| `SaveRoles(roles)` | roles.go:71 | roles.json 저장 |
| `RolesExists()` | roles.go:110 | roles.json 존재 여부 |

#### Functions - 역할 상태 관리

| 함수 | 파일 | 설명 |
|------|------|------|
| `GetCurrentRole()` | roles.go:129 | 현재 선택된 역할 (.sym/.env) |
| `SetCurrentRole(role)` | roles.go:139 | 현재 역할 설정 |
| `GetAvailableRoles()` | roles.go:149 | 사용 가능한 역할 목록 |
| `IsValidRole(role)` | roles.go:165 | 역할 유효성 검증 |

#### Functions - 사용자/권한 검증

| 함수 | 파일 | 설명 |
|------|------|------|
| `GetUserRole(username)` | roles.go:91 | 사용자의 역할 조회 |
| `LoadUserPolicyFromRepo()` | rbac.go:30 | user-policy.json 로드 |
| `ValidateFilePermissionsForRole(role, files)` | rbac.go:132 | 역할별 파일 권한 검증 |

### Private API

| 함수 | 파일 | 설명 |
|------|------|------|
| `getSymDir()` | roles.go:21 | .sym 디렉토리 경로 |
| `getEnvPath()` | roles.go:30 | .sym/.env 경로 |
| `getUserPolicyPath()` | rbac.go:21 | user-policy.json 경로 |
| `matchPattern(pattern, path)` | rbac.go:48 | glob 패턴 매칭 |
| `checkFilePermission(file, role)` | rbac.go:104 | 단일 파일 권한 확인 |
Loading