diff --git a/.gitignore b/.gitignore index 37bc077..08bba7b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,31 @@ -.claude/ +# Build outputs bin/ -!npm/bin/ -!npm/bin/*.js -npm/bin/sym-* +dist/ +*.exe + +# Dependencies +node_modules/ # Test coverage coverage.out coverage.html -node_modules/ -.sym/ -*.coverprofile coverage.txt +*.coverprofile + +# IDE +.vscode/ +.cursor/ +.idea/ + +# OS +.DS_Store -# Symphony API key configuration -.sym/.env -.vscode -.cursor -sym-cli.exe +# Project specific +.claude/ +.sym/ .mcp.json + +# npm package binaries (keep JS wrapper) +!npm/bin/ +!npm/bin/*.js +npm/bin/sym-* diff --git a/AGENTS.md b/AGENTS.md index 5dfb7bb..c2e9384 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,11 +24,11 @@ - 파일은 멀티 모듈 형태로 각 기능별로 구성되어 있어야 해 ## 빌드, 테스트, 개발 명령어 -- 재현성을 위해 Make 타깃 또는 스크립트를 선호하세요: - - `make dev` 또는 `./scripts/dev` — 자동 리로드로 로컬에서 앱 실행 - - `make test` 또는 `./scripts/test` — 전체 테스트 스위트 실행 - - `make lint` 또는 `./scripts/lint` — 린터/포매터 실행 - - `make build` 또는 `./scripts/build` — 릴리스 아티팩트 생성 +- 재현성을 위해 Make 타깃을 선호하세요: + - `make build` — 현재 플랫폼용 빌드 + - `make build-all` — 모든 플랫폼용 빌드 + - `make test` — 전체 테스트 스위트 실행 + - `make lint` — 린터/포매터 실행 - 서비스 사전 요구사항(DB, 큐, 환경 변수)은 `docs/`에 문서화하세요. - Go 전용 권장 명령(가능하면 Make 타깃으로 래핑): @@ -119,4 +119,4 @@ - 새로 생성하는 파일에도 이 문서의 지침을 따르세요. ## 테스트 -테스트는 반드시 작성해야 하고 \ No newline at end of file +테스트는 반드시 작성해야 하고, 변경된 코드의 라인 커버리지 80% 이상을 목표로 해야 합니다. \ No newline at end of file diff --git a/README.md b/README.md index c4b1f13..5bf937a 100644 --- a/README.md +++ b/README.md @@ -1,685 +1,124 @@ -# 🎵 Symphony (sym) +# Symphony (sym) -GitHub Repository Role & Policy Management Tool with Code Convention Validation +자연어 기반 코딩 컨벤션 관리 및 검증 도구 -Symphony는 GitHub OAuth 인증을 통한 역할 기반 파일 접근 권한 및 코딩 정책 관리를 위한 하이브리드 CLI/Web 애플리케이션입니다. 자연어로 정의된 컨벤션을 검증하는 LLM 친화적 linter 기능을 포함합니다. [![Test Coverage](https://img.shields.io/badge/coverage-view%20report-blue)](https://devsymphony.github.io/sym-cli/coverage.html) -## 개요 - -> **✨ 빠른 시작:** `sym login` 한 번이면 끝! OAuth App 설정 불필요. - -## ✨ 주요 기능 - -### 🔐 역할 및 권한 관리 -- **CLI 인터페이스**: 사용자 역할 및 리포지토리 정보 빠른 조회 -- **웹 대시보드**: 권한 관리를 위한 시각적 인터페이스 (포트 8787) -- **OAuth 인증**: 안전한 GitHub/GHES 인증 -- **동적 역할 시스템**: 커스텀 역할 생성 및 관리 -- **JSON API**: 스크립팅을 위한 기계 판독 가능 출력 - -### 📝 정책 편집기 (Policy Editor) -- **RBAC (Role-Based Access Control)**: 역할별 파일 접근 권한 및 시스템 권한 설정 -- **코딩 규칙 관리**: 프로젝트별 코딩 컨벤션 및 정책 정의 -- **템플릿 시스템**: React, Vue, Node.js, Python, Go, TypeScript 템플릿 제공 -- **히스토리 추적**: 정책 변경 이력 조회 (Git 기반) -- **자동 저장**: 30초마다 자동 저장 (선택 가능) -- **안전장치**: 최소 1명의 정책 편집자 보장, 역할 삭제 보호 -- **권한 기반 UI**: 권한에 따른 읽기 전용 모드 자동 적용 -- 자연어 기반 컨벤션 정의 -- **LLM 기반 자동 변환**: OpenAI API로 자연어 규칙을 linter 설정으로 자동 변환 -- **다중 Linter 지원**: ESLint, Checkstyle, PMD 등 여러 linter 설정 파일 동시 생성 -- 코드 스타일 및 아키텍처 규칙 검증 -- RBAC 기반 파일 접근 제어 -- JSON 출력을 통한 LLM 도구 연동 -- 컨텍스트 기반 컨벤션 추출 - -### 🔍 코드 컨벤션 검증 (개발 중) -- **자연어 기반 컨벤션 정의**: `.sym/user-policy.json`에 자연어로 규칙 작성 -- **스키마 변환**: A 스키마 (사용자 입력) → B 스키마 (검증 엔진용) -- **다중 검증 엔진**: Pattern, Length, Style, AST 엔진 지원 -- **LLM 도구 연동**: JSON 출력을 통한 AI 코딩 도구 연동 -- **컨텍스트 기반 추출**: 작업 컨텍스트에 맞는 컨벤션만 추출 - -### 🛠️ 기술 스택 -- **단일 바이너리**: 런타임 의존성 불필요 -- **임베디드 에셋**: go:embed를 통한 정적 파일 내장 (HTML, CSS, JS, SVG) -- **프로덕션 CSS**: Tailwind CSS 빌드 시스템 -- **멀티플랫폼**: Windows, macOS (Intel/ARM), Linux (AMD64/ARM64) 지원 -- **UTF-8 지원**: 한글 및 이모티콘 완벽 지원 - -## 📦 설치 - -### MCP 서버로 설치 (권장 - AI 코딩 도구) - -**Claude Code 원클릭 설치**: -```bash -claude mcp add symphony npx @dev-symphony/sym@latest mcp -``` - -**수동 MCP 설정** (Claude Desktop / Cursor / Continue.dev): - -config 파일 위치: -- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` -- Windows: `%APPDATA%/Claude/claude_desktop_config.json` -- Linux: `~/.config/Claude/claude_desktop_config.json` - -설정 추가: -```json -{ - "mcpServers": { - "symphony": { - "command": "npx", - "args": ["-y", "@dev-symphony/sym@latest", "mcp"], - "env": { - "SYM_POLICY_PATH": "${workspaceFolder}/.sym/user-policy.json" - } - } - } -} -``` +## 빠른 시작 -Claude Desktop 재시작 후 사용 가능! - -### npm 글로벌 설치 +### 1. 설치 ```bash -npm install -g @dev-symphony/sym +npm i -g @dev-symphony/sym ``` -### 바이너리 다운로드 - -GitHub Releases 페이지에서 플랫폼에 맞는 바이너리를 다운로드할 수 있습니다. - -#### GPG 서명 검증 (권장) - -릴리스 바이너리는 GPG로 서명됩니다. 다운로드한 파일의 무결성을 검증하려면: +### 2. 초기화 ```bash -# 1. GPG 공개키 가져오기 (최초 1회) -gpg --keyserver keys.openpgp.org --recv-keys [GPG_KEY_ID] - -# 2. 서명 검증 -gpg --verify sym-linux-amd64.asc sym-linux-amd64 -``` - -서명이 유효하면 `Good signature from "DevSymphony"` 메시지가 표시됩니다. - -### 소스에서 빌드 - -```bash -# 리포지토리 클론 -git clone https://github.com/DevSymphony/sym-cli.git -cd sym-cli - -# 의존성 설치 및 빌드 -make setup -make build - -# Windows의 경우 bin/sym.exe 생성됨 -# Unix 계열의 경우 bin/sym 생성됨 -``` - -### 시스템에 설치 - -```bash -# GOPATH/bin에 설치 -make install - -# 또는 직접 설치 -go install github.com/DevSymphony/sym-cli/cmd/sym@latest -``` - -### PATH 설정 (Windows) - -```powershell -# 사용자 환경변수에 추가 -[System.Environment]::SetEnvironmentVariable('Path', $env:Path + ';D:\Git\sym-cli\bin', 'User') -``` - -## 🚀 빠른 시작 - -### MCP 서버 모드 (AI 코딩 도구와 함께) - -Symphony를 MCP 서버로 실행하여 Claude, Cursor, Continue.dev 등과 함께 사용: - -```bash -# stdio 모드 (기본 - AI 도구 연동용) -sym mcp - -# HTTP 모드 (디버깅/테스트용) -sym mcp --port 4000 - -# 커스텀 정책 파일 지정 -sym mcp --config ./custom-policy.json -``` - -**Claude에게 물어보기**: -- "이 프로젝트의 네이밍 컨벤션은 뭐야?" -- "이 코드가 컨벤션을 지키는지 검증해줘" -- "Go 코드 작성 시 주의할 점은?" - -MCP 설치 방법은 [설치](#-설치) 섹션 참고. - ---- - -### 1. 초기 설정 및 로그인 - -```bash -# 설정 (선택사항 - 기본 서버 사용 시 생략 가능) -sym config - # GitHub OAuth 로그인 sym login -# 현재 사용자 확인 -sym whoami -``` - -### 2. 리포지토리 초기화 -자연어 정책을 linter 설정 파일로 자동 변환합니다. - -```bash -# 모든 지원 linter 설정 파일 생성 (출력: /.sym) -sym convert -i user-policy.json --targets all - -# JavaScript/TypeScript만 -sym convert -i user-policy.json --targets eslint - -# Java만 -sym convert -i user-policy.json --targets checkstyle,pmd - -# 생성되는 파일들: -# - .sym/.eslintrc.json (JavaScript/TypeScript) -# - .sym/checkstyle.xml (Java) -# - .sym/pmd-ruleset.xml (Java) -# - .sym/code-policy.json (내부 검증용) -# - .sym/conversion-report.json -``` - -**참고**: [Convert 명령어 상세 가이드](docs/CONVERT_USAGE.md) - -### 3. 코드 검증 - -작성한 코드가 컨벤션을 준수하는지 검증합니다. - -```bash -# Git 리포지토리로 이동 -cd /path/to/your/repo - -# 역할 및 정책 파일 초기화 (.sym/ 폴더에 생성) +# 프로젝트 초기화 (.sym/ 폴더 생성, MCP 자동 설정) sym init - -# 생성된 파일 확인 -cat .sym/roles.json -cat .sym/user-policy.json - -# Git에 커밋 및 푸시 -git add .sym/ -git commit -m "Initialize Symphony roles and policy" -git push ``` -### 3. 웹 대시보드 실행 +> **참고**: LLM 기반 컨벤션 변환을 사용하려면 `OPENAI_API_KEY` 환경변수가 필요합니다. -```bash -# 대시보드 시작 (http://localhost:8787) -sym dashboard - -# 다른 포트 사용 -sym dashboard --port 8080 -``` - -### 4. 역할 확인 - -```bash -# 내 역할 확인 -sym my-role - -# 다른 사용자 역할 확인 -sym my-role --user username -``` - -### 5. 정책 관리 - -```bash -# 정책 파일 경로 확인 -sym policy path - -# 정책 파일 유효성 검사 -sym policy validate - -# 정책 변경 히스토리 -sym policy history -``` - -## 📁 프로젝트 구조 - -``` -sym-cli/ -├── cmd/sym/ # CLI 진입점 -├── internal/ -│ ├── cmd/ # Cobra 커맨드 정의 -│ │ ├── config.go # 설정 관리 -│ │ ├── login.go # OAuth 로그인 -│ │ ├── logout.go # 로그아웃 -│ │ ├── init.go # 리포지토리 초기화 -│ │ ├── dashboard.go # 웹 대시보드 -│ │ ├── my_role.go # 역할 조회 -│ │ ├── whoami.go # 사용자 정보 -│ │ ├── policy.go # 정책 관리 -│ │ ├── convert.go # 스키마 변환 (개발 중) -│ │ ├── validate.go # 코드 검증 (개발 중) -│ │ └── export.go # 컨벤션 내보내기 (개발 중) -│ ├── auth/ # OAuth 인증 -│ ├── config/ # 설정 관리 -│ ├── git/ # Git 유틸리티 -│ ├── github/ # GitHub API 클라이언트 -│ ├── roles/ # 역할 관리 -│ ├── policy/ # 정책 로딩/파싱 -│ ├── server/ # 웹 대시보드 서버 -│ │ └── static/ # HTML, CSS, JS (임베디드) -│ ├── validator/ # 검증 로직 (개발 중) -│ ├── converter/ # 스키마 변환 (개발 중) -│ ├── engine/ # 검증 엔진 -│ │ ├── pattern/ # 패턴 검증 엔진 -│ │ ├── length/ # 길이 검증 엔진 -│ │ ├── style/ # 스타일 검증 엔진 -│ │ └── ast/ # AST 검증 엔진 -│ └── adapter/ # ESLint, Prettier 어댑터 -├── pkg/ -│ └── schema/ # 스키마 타입 정의 -├── scripts/ # 빌드 스크립트 -├── examples/ # 예제 파일 -├── tests/ # 테스트 -├── testdata/ # 통합 테스트 데이터 -│ ├── javascript/ # JavaScript 테스트 파일 -│ │ ├── pattern/ # 패턴 매칭 테스트 -│ │ ├── length/ # 길이 제한 테스트 -│ │ ├── style/ # 코드 스타일 테스트 -│ │ └── ast/ # AST 구조 테스트 -│ ├── typescript/ # TypeScript 테스트 파일 -│ │ └── typechecker/ # 타입 체킹 테스트 -│ └── java/ # Java 테스트 파일 -│ ├── pattern/ # 패턴 매칭 테스트 -│ ├── length/ # 길이 제한 테스트 -│ ├── style/ # 코드 스타일 테스트 -│ └── ast/ # AST 구조 테스트 -├── .sym/ # 정책 및 역할 파일 (gitignore) -├── Makefile -└── README.md -``` - -## 🔧 개발 - -### 개발 환경 설정 - -```bash -# 개발 의존성 설치 (Go tools, npm packages) -make setup - -# CSS 감시 모드 (개발 중 자동 리빌드) -make watch-css -``` - -### 빌드 - -```bash -# 현재 플랫폼용 빌드 (CSS 자동 빌드 포함) -make build - -# 모든 플랫폼용 빌드 -make build-all - -# CSS만 빌드 -make build-css -``` - -### 테스트 - -```bash -# 전체 테스트 실행 (커버리지 리포트 생성) -make test - -# 특정 패키지 테스트 -go test ./internal/engine/pattern/... -v - -# 통합 테스트 (JavaScript, TypeScript, Java) -go test ./tests/integration/... -v -``` - -**통합 테스트 데이터**: -- `testdata/javascript/`: JavaScript 엔진 테스트 (pattern, length, style, ast) -- `testdata/typescript/`: TypeScript 타입체커 테스트 -- `testdata/java/`: Java 엔진 테스트 (Checkstyle, PMD 검증) - -각 디렉토리는 위반 케이스와 정상 케이스를 포함하여 검증 엔진의 정확성을 보장합니다. -자세한 내용은 [testdata/README.md](testdata/README.md)를 참고하세요. - -테스트 커버리지 리포트는 [여기](https://devsymphony.github.io/sym-cli/coverage.html)에서 확인할 수 있습니다. - -### 코드 품질 +### 3. 사용 +**웹 대시보드로 컨벤션 편집:** ```bash -# 포맷팅 -make fmt - -# 린팅 -make lint - -# 의존성 정리 -make tidy - -# 클린업 -make clean +sym dashboard +# http://localhost:8787 에서 역할/컨벤션 편집 ``` -## 📋 환경 변수 +## 주요 기능 -```bash -# 인증 모드 (기본값: server) -export SYM_AUTH_MODE=server +- 자연어로 코딩 컨벤션 정의 (`.sym/user-policy.json`) +- RBAC 기반 파일 접근 권한 관리 +- LLM으로 자연어 규칙을 ESLint/Checkstyle/PMD 설정으로 변환 +- MCP 서버로 Claude, Cursor 등 AI 도구와 연동 +- 웹 대시보드에서 시각적으로 정책 편집 -# Symphony 인증 서버 URL -export SYM_SERVER_URL=https://symphony-server-98207.web.app -``` - -## 🗂️ 파일 및 디렉토리 +## MCP 설정 -### 설정 파일 위치 +`sym init` 실행 시 MCP가 자동으로 설정됩니다. -- **설정**: `~/.config/sym/config.json` -- **토큰**: `~/.config/sym/token.json` -- **역할**: `.sym/roles.json` (프로젝트 루트) -- **정책**: `.sym/user-policy.json` (프로젝트 루트) - -### .sym/roles.json 예시 +수동 설정이 필요한 경우: ```json { - "admin": ["alice", "bob"], - "developer": ["charlie", "david"], - "viewer": ["eve"] + "mcpServers": { + "symphony": { + "command": "npx", + "args": ["-y", "@dev-symphony/sym@latest", "mcp"] + } + } } ``` -### .sym/user-policy.json 예시 +## CLI 명령어 + +| 명령어 | 설명 | +|--------|------| +| `sym login` | GitHub OAuth 로그인 | +| `sym init` | 프로젝트 초기화 (.sym/ 생성) | +| `sym dashboard` | 웹 대시보드 실행 (포트 8787) | +| `sym my-role` | 내 역할 확인 | +| `sym policy validate` | 정책 파일 유효성 검사 | +| `sym convert -i user-policy.json --targets all` | 컨벤션을 linter 설정으로 변환 | +| `sym mcp` | MCP 서버 실행 | + +## 정책 파일 예시 + +`.sym/user-policy.json`: ```json { "version": "1.0.0", "rbac": { "roles": { - "admin": { - "allowWrite": ["**/*"], - "canEditPolicy": true, - "canEditRoles": true - }, - "developer": { - "allowWrite": ["src/**", "tests/**", "docs/**"], - "denyWrite": [".sym/**", "config/**"], - "canEditPolicy": false, - "canEditRoles": false - } + "admin": { "allowWrite": ["**/*"] }, + "developer": { "allowWrite": ["src/**"], "denyWrite": [".sym/**"] } } }, - "defaults": { - "languages": ["go", "javascript"], - "severity": "error", - "autofix": true - }, "rules": [ - { - "no": 1, - "say": "패키지 이름은 소문자 한 단어로 작성합니다", - "category": "naming", - "example": "// Good: package user\n// Bad: package UserManagement" - } + { "say": "클래스 이름은 PascalCase", "category": "naming" }, + { "say": "한 줄은 100자 이하", "category": "formatting" } ] } ``` -## 🎯 사용 사례 - -### 팀 협업 시나리오 - -1. **프로젝트 관리자**가 `sym init`으로 역할/정책 설정 -2. 팀원들이 저장소 클론 후 `sym login`으로 인증 -3. `sym my-role`로 자신의 권한 확인 -4. `sym dashboard`로 정책 및 역할 관리 -5. Git으로 변경사항 추적 및 이력 관리 - -### 정책 편집 시나리오 - -1. 웹 대시보드 실행: `sym dashboard` -2. 브라우저에서 `http://localhost:8787` 접속 -3. 역할 및 권한 설정 -4. 코딩 규칙 추가/수정 -5. 템플릿 적용 (React, Vue, Node.js 등) -6. 자동 저장 활성화 (30초마다) -7. Git 커밋 및 푸시 +`.sym/roles.json`: -## 📝 라이선스 +```json +{ + "admin": ["alice"], + "developer": ["bob", "charlie"] +} +``` -MIT License +## 개발 -## 🤝 기여 - -Contributions are welcome! Please feel free to submit a Pull Request. - -## 📞 지원 - -- GitHub Issues: [https://github.com/DevSymphony/sym-cli/issues](https://github.com/DevSymphony/sym-cli/issues) - ---- - -**Note:** 코드 검증 기능 (`convert`, `validate`, `export`)은 현재 개발 중입니다. - -## 📊 패키지 구조 및 의존성 - -```mermaid -graph TB - subgraph "메인 진입점" - main[cmd/sym
main] - end - - subgraph "CLI 계층" - cmd[internal/cmd
Cobra Commands] - end - - subgraph "중앙 데이터 구조" - schema[pkg/schema
Types] - end - - subgraph "기본 유틸리티" - config[internal/config] - git[internal/git] - github[internal/github] - llm[internal/llm] - end - - subgraph "도메인 계층" - auth[internal/auth] - - subgraph converter_group["internal/converter"] - converter[converter] - conv_linters[linters] - end - - policy[internal/policy] - end - - subgraph "비즈니스 로직" - roles[internal/roles] - - subgraph adapter_group["internal/adapter"] - adapter[adapter] - adapter_eslint[eslint] - adapter_prettier[prettier] - adapter_tsc[tsc] - adapter_checkstyle[checkstyle] - adapter_pmd[pmd] - adapter_registry[registry] - end - - subgraph engine_group["internal/engine"] - engine[engine] - engine_core[core] - engine_registry[registry] - engine_pattern[pattern] - engine_length[length] - engine_style[style] - engine_ast[ast] - engine_llm[llm engine] - engine_typechecker[typechecker] - end - - validator[internal/validator] - end - - subgraph "통합 계층" - mcp[internal/mcp] - server[internal/server] - end - - %% main 의존성 - main --> cmd - - %% cmd 의존성 - cmd --> auth - cmd --> config - cmd --> converter - cmd --> git - cmd --> github - cmd --> llm - cmd --> mcp - cmd --> policy - cmd --> roles - cmd --> server - cmd --> validator - cmd --> schema - - %% auth 의존성 - auth --> config - auth --> github - - %% converter 의존성 - converter --> llm - converter --> schema - conv_linters --> converter - - %% policy 의존성 - policy --> git - policy --> schema - - %% roles 의존성 - roles --> git - roles --> policy - roles --> schema - - %% adapter 서브패키지 - adapter_eslint --> adapter - adapter_prettier --> adapter - adapter_tsc --> adapter - adapter_checkstyle --> adapter - adapter_pmd --> adapter - adapter_registry --> adapter - adapter --> engine_core - - %% engine 서브패키지 - engine_pattern --> engine_core - engine_pattern --> adapter_eslint - engine_length --> engine_core - engine_length --> adapter_eslint - engine_style --> engine_core - engine_style --> adapter_eslint - engine_style --> adapter_prettier - engine_ast --> engine_core - engine_ast --> adapter_eslint - engine_ast --> adapter_checkstyle - engine_ast --> adapter_pmd - engine_llm --> engine_core - engine_llm --> llm - engine_typechecker --> engine_core - engine_typechecker --> adapter_tsc - engine_registry --> engine_core - engine --> engine_registry - - %% validator 의존성 - validator --> engine - validator --> llm - validator --> roles - validator --> git - validator --> schema - - %% mcp 의존성 - mcp --> converter - mcp --> git - mcp --> llm - mcp --> policy - mcp --> validator - mcp --> schema - - %% server 의존성 - server --> config - server --> git - server --> github - server --> policy - server --> roles - server --> schema - - %% llm의 schema 의존성 - llm --> schema - - classDef mainEntry fill:#e03131,stroke:#a61e4d,color:#fff,stroke-width:3px - classDef cliLayer fill:#ff6b6b,stroke:#c92a2a,color:#fff - classDef core fill:#20c997,stroke:#087f5b,color:#fff - classDef leaf fill:#51cf66,stroke:#2f9e44,color:#fff - classDef domain fill:#74c0fc,stroke:#1971c2,color:#fff - classDef business fill:#ffd43b,stroke:#f08c00,color:#000 - classDef integration fill:#da77f2,stroke:#9c36b5,color:#fff - classDef subpkg fill:#f8f9fa,stroke:#868e96,color:#000 - - class main mainEntry - class cmd cliLayer - class schema core - class config,git,github,llm leaf - class auth,converter,policy domain - class roles,adapter,engine,validator business - class mcp,server integration - class adapter_eslint,adapter_prettier,adapter_tsc,adapter_checkstyle,adapter_pmd,adapter_registry,conv_linters subpkg - class engine_core,engine_registry,engine_pattern,engine_length,engine_style,engine_ast,engine_llm,engine_typechecker subpkg +```bash +make setup # 의존성 설치 +make build # 빌드 +make test # 테스트 +make lint # 린트 ``` -### 패키지 계층 구조 +**필수 도구:** Go 1.21+, Node.js 18+ -**메인 진입점** -- `cmd/sym`: main 패키지 (→ internal/cmd) +## 문서 -**CLI 계층** -- `internal/cmd`: Cobra 기반 CLI 커맨드 구현 (→ 모든 internal 패키지) +- [Convert 기능 가이드](docs/CONVERT_FEATURE.md) +- [Convert 사용법](docs/CONVERT_USAGE.md) +- [Linter 검증](docs/LINTER_VALIDATION.md) -**중앙 데이터 구조** -- `pkg/schema`: UserPolicy(A Schema) 및 CodePolicy(B Schema) 타입 정의 +## 라이선스 -**Tier 0: 기본 유틸리티** (의존성 없음) -- `internal/config`: 전역 설정 및 토큰 관리 -- `internal/git`: Git 저장소 작업 -- `internal/github`: GitHub API 클라이언트 -- `internal/llm`: OpenAI API 클라이언트 (→ schema) - -**Tier 1: 도메인 계층** -- `internal/auth`: GitHub OAuth 인증 (→ config, github) -- `internal/converter`: 정책 변환 (→ llm, schema) -- `internal/policy`: 정책 파일 관리 (→ git, schema) +MIT License -**Tier 2: 비즈니스 로직** -- `internal/roles`: RBAC 구현 (→ git, policy, schema) -- `internal/adapter` ↔ `internal/engine`: 검증 도구 어댑터 및 엔진 (순환 의존성) - - Adapters: ESLint, Prettier, TSC, Checkstyle, PMD - - Engines: Pattern, Length, Style, AST, LLM, TypeChecker -- `internal/validator`: 검증 오케스트레이터 (→ engine, llm, roles, git, schema) +## 지원 -**Tier 3: 통합 계층** -- `internal/mcp`: MCP 서버 (→ converter, git, llm, policy, validator, schema) -- `internal/server`: 웹 대시보드 (→ config, git, github, policy, roles, schema) +- GitHub Issues: https://github.com/DevSymphony/sym-cli/issues diff --git a/SETUP.md b/SETUP.md deleted file mode 100644 index 2bcbd83..0000000 --- a/SETUP.md +++ /dev/null @@ -1,150 +0,0 @@ -# Setup Guide - -This document describes the development environment setup and external tool requirements for the Symphony CLI project. - -## Prerequisites - -### Required Tools - -#### 1. Go (Required) -- **Version**: Go 1.21 or higher -- **Purpose**: Build and run the CLI -- **Installation**: https://go.dev/doc/install - -#### 2. Node.js & npm (Required for JavaScript/TypeScript validation) -- **Version**: Node.js 18+ with npm -- **Purpose**: Run ESLint, Prettier, TSC adapters -- **Installation**: https://nodejs.org/ -- **Used by engines**: Pattern, Length, Style, AST, TypeChecker - -#### 3. Java JDK (Required for Java validation) -- **Version**: Java 21 or higher (OpenJDK recommended) -- **Purpose**: Run Checkstyle and PMD adapters -- **Installation**: - ```bash - # Ubuntu/Debian - sudo apt-get update - sudo apt-get install -y default-jdk - - # macOS (Homebrew) - brew install openjdk@21 - - # Verify installation - java -version - ``` -- **Used by engines**: Pattern (Java), Length (Java), Style (Java), AST (Java) -- **Note**: Java was installed on 2025-11-16 for integration testing - -### Optional Tools - -#### Git -- Required for running integration tests that involve git operations -- Most systems have git pre-installed - ---- - -## External Tool Installation - -The CLI automatically installs external validation tools when first needed: - -### JavaScript/TypeScript Tools -- **ESLint**: Installed to `~/.symphony/tools/node_modules/eslint` via npm -- **Prettier**: Installed to `~/.symphony/tools/node_modules/prettier` via npm -- **TypeScript**: Installed to `~/.symphony/tools/node_modules/typescript` via npm - -### Java Tools -- **Checkstyle**: Downloaded to `~/.symphony/tools/checkstyle-10.26.1.jar` from Maven Central -- **PMD**: Downloaded to `~/.symphony/tools/pmd-/` from GitHub Releases - -**Auto-installation happens when:** -1. A validation rule is executed for the first time -2. The engine checks if the adapter is available (`CheckAvailability()`) -3. If not found, the engine calls `Install()` to download and set up the tool - ---- - -## Running Tests - -### Unit Tests -```bash -# Run all unit tests -go test ./... - -# Run with coverage -go test -cover ./... -``` - -### Integration Tests -```bash -# Run all integration tests -go test ./tests/integration/... - -# Run specific test suite -go test ./tests/integration/validator_policy_test.go ./tests/integration/helper.go -v - -# Skip integration tests in short mode -go test -short ./... -``` - -**Note**: Integration tests require: -- Node.js and npm (for JavaScript/TypeScript tests) -- Java JDK (for Java tests) - ---- - -## Troubleshooting - -### Java Tests Fail with "java not found" -```bash -# Install Java JDK -sudo apt-get install -y default-jdk - -# Verify installation -java -version # Should show: openjdk version "21.0.8" -``` - -### ESLint/Prettier Installation Fails -```bash -# Ensure npm is available -npm --version - -# Manually install tools -cd ~/.symphony/tools -npm install eslint@^8.0.0 prettier@latest -``` - -### Checkstyle Download Fails (HTTP 404) -- The Checkstyle version was updated to 10.26.1 on 2025-11-16 -- If you see 404 errors, check `/workspace/internal/adapter/checkstyle/adapter.go` for the correct version -- Maven Central URL: https://repo1.maven.org/maven2/com/puppycrawl/tools/checkstyle/ - ---- - -## Development Environment - -### Recommended VS Code Extensions -- Go (golang.go) -- YAML (redhat.vscode-yaml) -- JSON (vscode.json-language-features) - -### Directory Structure -``` -~/.symphony/tools/ # Auto-installed external tools - ├── node_modules/ # JavaScript/TypeScript tools - │ ├── eslint/ - │ ├── prettier/ - │ └── typescript/ - ├── checkstyle-10.26.1.jar # Java style checker - └── pmd-/ # Java static analyzer -``` - ---- - -## Installation History - -### 2025-11-16: Java JDK Installation -- **Installed**: OpenJDK 21.0.8 -- **Reason**: Required for Java validation integration tests -- **Method**: `sudo apt-get install -y default-jdk` -- **Verification**: `java -version` shows OpenJDK 21.0.8+9 -- **Impact**: Enables Checkstyle and PMD adapters for Java code validation diff --git a/cmd/test-adapter/main.go b/cmd/test-adapter/main.go deleted file mode 100644 index ee1f9d1..0000000 --- a/cmd/test-adapter/main.go +++ /dev/null @@ -1,176 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/DevSymphony/sym-cli/internal/adapter" - "github.com/DevSymphony/sym-cli/internal/adapter/eslint" - "github.com/DevSymphony/sym-cli/internal/adapter/prettier" - "github.com/DevSymphony/sym-cli/internal/adapter/tsc" -) - -// test-adapter is a CLI tool to test individual adapters -// Usage: go run cmd/test-adapter/main.go [files...] -// -// Examples: -// go run cmd/test-adapter/main.go eslint src/app.js -// go run cmd/test-adapter/main.go tsc -// go run cmd/test-adapter/main.go prettier --check - -func main() { - if len(os.Args) < 2 { - fmt.Println("Usage: test-adapter [files...]") - fmt.Println("\nAvailable adapters:") - fmt.Println(" - eslint") - fmt.Println(" - prettier") - fmt.Println(" - tsc") - fmt.Println("\nExamples:") - fmt.Println(" test-adapter eslint") - fmt.Println(" test-adapter tsc") - fmt.Println(" test-adapter prettier --check") - os.Exit(1) - } - - adapterName := os.Args[1] - files := os.Args[2:] - - // Get current directory - workDir, err := os.Getwd() - if err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) - } - - symDir := filepath.Join(workDir, ".sym") - toolsDir := filepath.Join(os.Getenv("HOME"), ".sym", "tools") - - // Create adapter - var adp adapter.Adapter - switch adapterName { - case "eslint": - adp = eslint.NewAdapter(toolsDir, workDir) - case "prettier": - adp = prettier.NewAdapter(toolsDir, workDir) - case "tsc": - adp = tsc.NewAdapter(toolsDir, workDir) - default: - fmt.Printf("Unknown adapter: %s\n", adapterName) - os.Exit(1) - } - - fmt.Printf("🔧 Testing adapter: %s\n", adp.Name()) - fmt.Printf("📁 Working directory: %s\n", workDir) - fmt.Printf("📁 .sym directory: %s\n", symDir) - fmt.Printf("📁 Tools directory: %s\n\n", toolsDir) - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - - // Check availability - fmt.Printf("🔍 Checking availability...\n") - if err := adp.CheckAvailability(ctx); err != nil { - fmt.Printf("⚠️ Not available: %v\n", err) - fmt.Printf("📦 Installing...\n") - if err := adp.Install(ctx, adapter.InstallConfig{ - ToolsDir: toolsDir, - }); err != nil { - fmt.Printf("❌ Installation failed: %v\n", err) - os.Exit(1) - } - fmt.Printf("✅ Installed successfully\n\n") - } else { - fmt.Printf("✅ Available\n\n") - } - - // Load config from .sym directory - var config []byte - configPath := getConfigPath(symDir, adapterName) - - fmt.Printf("📄 Looking for config: %s\n", configPath) - if data, err := os.ReadFile(configPath); err == nil { - config = data - fmt.Printf("✅ Using config from %s\n\n", configPath) - } else { - fmt.Printf("⚠️ No config found, using default\n\n") - config = []byte("{}") - } - - // If no files specified, let the adapter use its default file discovery - if len(files) == 0 { - fmt.Printf("📂 No files specified, adapter will use its default file discovery\n\n") - } else { - fmt.Printf("📂 Files to check: %v\n\n", files) - } - - // Execute adapter - fmt.Printf("🚀 Running %s...\n", adapterName) - output, err := adp.Execute(ctx, config, files) - if err != nil { - fmt.Printf("❌ Execution failed: %v\n", err) - os.Exit(1) - } - - // Display results - fmt.Printf("\n📊 Results:\n") - fmt.Printf("Exit Code: %d\n", output.ExitCode) - fmt.Printf("Duration: %s\n\n", output.Duration) - - if output.Stdout != "" { - fmt.Printf("📤 Stdout:\n%s\n\n", output.Stdout) - } - - if output.Stderr != "" { - fmt.Printf("📤 Stderr:\n%s\n\n", output.Stderr) - } - - // Parse violations - violations, err := adp.ParseOutput(output) - if err != nil { - fmt.Printf("⚠️ Failed to parse output: %v\n", err) - } else { - fmt.Printf("🔍 Found %d violation(s):\n\n", len(violations)) - for i, v := range violations { - fmt.Printf("[%d] %s:%d:%d\n", i+1, v.File, v.Line, v.Column) - fmt.Printf(" Severity: %s\n", v.Severity) - fmt.Printf(" Message: %s\n", v.Message) - if v.RuleID != "" { - fmt.Printf(" Rule: %s\n", v.RuleID) - } - fmt.Printf("\n") - } - } - - // Print summary as JSON - summary := map[string]interface{}{ - "adapter": adapterName, - "exitCode": output.ExitCode, - "duration": output.Duration, - "violations": len(violations), - } - - summaryJSON, _ := json.MarshalIndent(summary, "", " ") - fmt.Printf("\n📋 Summary:\n%s\n", string(summaryJSON)) - - // Exit with appropriate code - if output.ExitCode != 0 || len(violations) > 0 { - os.Exit(1) - } -} - -func getConfigPath(symDir, adapterName string) string { - switch adapterName { - case "eslint": - return filepath.Join(symDir, ".eslintrc.json") - case "prettier": - return filepath.Join(symDir, ".prettierrc.json") - case "tsc": - return filepath.Join(symDir, "tsconfig.json") - default: - return filepath.Join(symDir, adapterName+".json") - } -} diff --git a/cmd/test-rbac/main.go b/cmd/test-rbac/main.go deleted file mode 100644 index 8466ab3..0000000 --- a/cmd/test-rbac/main.go +++ /dev/null @@ -1,97 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/DevSymphony/sym-cli/internal/roles" -) - -func main() { - // Change to test directory - if err := os.Chdir("/tmp/rbac-test"); err != nil { - fmt.Printf("❌ Failed to change directory: %v\n", err) - return - } - - fmt.Println("🧪 RBAC 검증 테스트 시작") - fmt.Println("================================================================") - - // Test scenarios - testCases := []struct { - name string - username string - files []string - }{ - { - name: "Frontend Dev - 허용된 파일만", - username: "alice", - files: []string{ - "src/components/Button.js", - "src/components/ui/Modal.js", - "src/hooks/useAuth.js", - }, - }, - { - name: "Frontend Dev - 거부된 파일 포함", - username: "alice", - files: []string{ - "src/components/Button.js", - "src/core/engine.js", - "src/api/client.js", - }, - }, - { - name: "Senior Dev - 모든 파일", - username: "charlie", - files: []string{ - "src/components/Button.js", - "src/core/engine.js", - "src/api/client.js", - "src/utils/helper.js", - }, - }, - { - name: "Viewer - 읽기 전용", - username: "david", - files: []string{ - "src/components/Button.js", - }, - }, - { - name: "Frontend Dev - 혼합 케이스", - username: "bob", - files: []string{ - "src/hooks/useData.js", - "src/core/config.js", - "src/utils/format.js", - "src/components/Header.js", - }, - }, - } - - for i, tc := range testCases { - fmt.Printf("\n📋 테스트 %d: %s\n", i+1, tc.name) - fmt.Printf(" 사용자: %s\n", tc.username) - fmt.Printf(" 파일 수: %d개\n", len(tc.files)) - - result, err := roles.ValidateFilePermissions(tc.username, tc.files) - if err != nil { - fmt.Printf(" ❌ 오류: %v\n", err) - continue - } - - if result.Allowed { - fmt.Printf(" ✅ 결과: 모든 파일 수정 가능\n") - } else { - fmt.Printf(" ❌ 결과: %d개 파일 수정 불가\n", len(result.DeniedFiles)) - fmt.Printf(" 거부된 파일:\n") - for _, file := range result.DeniedFiles { - fmt.Printf(" - %s\n", file) - } - } - } - - fmt.Println("\n================================================================") - fmt.Println("✅ 테스트 완료!") -} diff --git a/docs/CONVERT_FEATURE.md b/docs/CONVERT_FEATURE.md index 1916700..23ece53 100644 --- a/docs/CONVERT_FEATURE.md +++ b/docs/CONVERT_FEATURE.md @@ -1,121 +1,121 @@ -# Convert Feature - Multi-Target Linter Configuration Generator +# Convert 기능 - 다중 타겟 Linter 설정 생성기 -## Overview +## 개요 -The enhanced `convert` command transforms natural language coding conventions into linter-specific configuration files using LLM-powered inference. +`convert` 명령어는 자연어 코딩 컨벤션을 LLM 기반 추론을 통해 linter별 설정 파일로 변환합니다. -## Features +## 기능 -- **LLM-Powered Inference**: Uses OpenAI API to intelligently analyze natural language rules -- **Multi-Target Support**: Generates configurations for multiple linters simultaneously +- **LLM 기반 추론**: OpenAI API를 사용하여 자연어 규칙을 분석 +- **다중 타겟 지원**: 여러 linter 설정을 동시에 생성 - **ESLint** (JavaScript/TypeScript) - **Checkstyle** (Java) - **PMD** (Java) -- **Fallback Mechanism**: Pattern-based inference when LLM is unavailable -- **Confidence Scoring**: Tracks inference confidence and warns on low-confidence conversions -- **1:N Rule Mapping**: One user rule can generate multiple linter rules -- **Caching**: Minimizes API calls by caching inference results +- **폴백 메커니즘**: LLM 사용 불가 시 패턴 기반 추론 +- **신뢰도 점수**: 추론 신뢰도를 추적하고 낮은 신뢰도에 대해 경고 +- **1:N 규칙 매핑**: 하나의 사용자 규칙이 여러 linter 규칙을 생성 +- **캐싱**: 추론 결과를 캐싱하여 API 호출 최소화 -## Architecture +## 아키텍처 ``` User Policy (user-policy.json) - ↓ + | Converter - ↓ - LLM Inference (OpenAI API) ← Fallback (Pattern Matching) - ↓ + | + LLM Inference (OpenAI API) <- Fallback (Pattern Matching) + | Rule Intent Detection - ↓ + | Linter Converters - ├── ESLint Converter → .eslintrc.json - ├── Checkstyle Converter → checkstyle.xml - └── PMD Converter → pmd-ruleset.xml + +-- ESLint Converter -> .eslintrc.json + +-- Checkstyle Converter -> checkstyle.xml + +-- PMD Converter -> pmd-ruleset.xml ``` -### Package Structure +### 패키지 구조 ``` internal/ -├── llm/ -│ ├── client.go # OpenAI API client -│ ├── inference.go # Rule inference engine -│ └── types.go # Intent and result types -├── converter/ -│ ├── converter.go # Main conversion logic -│ └── linters/ -│ ├── converter.go # Linter converter interface -│ ├── registry.go # Converter registry -│ ├── eslint.go # ESLint converter -│ ├── checkstyle.go # Checkstyle converter -│ └── pmd.go # PMD converter -└── cmd/ - └── convert.go # CLI command ++-- llm/ +| +-- client.go # OpenAI API 클라이언트 +| +-- inference.go # 규칙 추론 엔진 +| +-- types.go # Intent 및 결과 타입 ++-- converter/ +| +-- converter.go # 메인 변환 로직 +| +-- linters/ +| +-- converter.go # Linter 변환기 인터페이스 +| +-- registry.go # 변환기 레지스트리 +| +-- eslint.go # ESLint 변환기 +| +-- checkstyle.go # Checkstyle 변환기 +| +-- pmd.go # PMD 변환기 ++-- cmd/ + +-- convert.go # CLI 명령어 ``` -## Usage +## 사용법 -### Basic Usage +### 기본 사용 ```bash -# Convert to all supported linters +# 모든 지원 linter로 변환 sym convert -i user-policy.json --targets all --output-dir .linters -# Convert only for JavaScript/TypeScript +# JavaScript/TypeScript만 sym convert -i user-policy.json --targets eslint --output-dir .linters -# Convert for Java +# Java만 sym convert -i user-policy.json --targets checkstyle,pmd --output-dir .linters ``` -### Advanced Options +### 고급 옵션 ```bash -# Use specific OpenAI model +# 특정 OpenAI 모델 사용 sym convert -i user-policy.json \ --targets all \ --output-dir .linters \ --openai-model gpt-4o -# Adjust confidence threshold +# 신뢰도 임계값 조정 sym convert -i user-policy.json \ --targets eslint \ --output-dir .linters \ --confidence-threshold 0.8 -# Enable verbose output +# 상세 출력 활성화 sym convert -i user-policy.json \ --targets all \ --output-dir .linters \ --verbose ``` -### Legacy Mode +### 레거시 모드 ```bash -# Generate only internal code-policy.json (no linter configs) +# 내부 code-policy.json만 생성 (linter 설정 없음) sym convert -i user-policy.json -o code-policy.json ``` -## Configuration +## 설정 -### Environment Variables +### 환경 변수 -- `OPENAI_API_KEY`: OpenAI API key (required for LLM inference) - - If not set, fallback pattern-based inference is used +- `OPENAI_API_KEY`: OpenAI API 키 (LLM 추론에 필요) + - 미설정 시 폴백 패턴 기반 추론 사용 -### Flags +### 플래그 -- `--targets`: Target linters (comma-separated or "all") -- `--output-dir`: Output directory for generated files -- `--openai-model`: OpenAI model to use (default: gpt-4o) -- `--confidence-threshold`: Minimum confidence for inference (default: 0.7) -- `--timeout`: API call timeout in seconds (default: 30) -- `--verbose`: Enable verbose logging +- `--targets`: 타겟 linter (쉼표로 구분 또는 "all") +- `--output-dir`: 생성 파일 출력 디렉토리 +- `--openai-model`: 사용할 OpenAI 모델 (기본값: gpt-4o) +- `--confidence-threshold`: 추론 최소 신뢰도 (기본값: 0.7) +- `--timeout`: API 호출 타임아웃 초 (기본값: 30) +- `--verbose`: 상세 로깅 활성화 -## User Policy Schema +## 사용자 정책 스키마 -### Example +### 예시 ```json { @@ -127,7 +127,7 @@ sym convert -i user-policy.json -o code-policy.json "rules": [ { "id": "naming-class-pascalcase", - "say": "Class names must be PascalCase", + "say": "클래스 이름은 PascalCase여야 합니다", "category": "naming", "languages": ["javascript", "typescript", "java"], "params": { @@ -136,7 +136,7 @@ sym convert -i user-policy.json -o code-policy.json }, { "id": "length-max-line", - "say": "Maximum line length is 100 characters", + "say": "한 줄은 최대 100자입니다", "category": "length", "params": { "max": 100 @@ -146,35 +146,35 @@ sym convert -i user-policy.json -o code-policy.json } ``` -### Supported Categories +### 지원 카테고리 -- `naming`: Identifier naming conventions -- `length`: Size constraints (line/file/function length) -- `style`: Code formatting (indentation, quotes, semicolons) -- `complexity`: Cyclomatic/cognitive complexity -- `security`: Security-related rules -- `error_handling`: Exception handling patterns -- `dependency`: Import/dependency restrictions +- `naming`: 식별자 네이밍 컨벤션 +- `length`: 크기 제약 (라인/파일/함수 길이) +- `style`: 코드 포맷팅 (들여쓰기, 따옴표, 세미콜론) +- `complexity`: 순환/인지 복잡도 +- `security`: 보안 관련 규칙 +- `error_handling`: 예외 처리 패턴 +- `dependency`: import/의존성 제한 -### Supported Engine Types +### 지원 엔진 타입 -- `pattern`: Naming conventions, forbidden patterns, import restrictions -- `length`: Line/file/function length, parameter count -- `style`: Indentation, quotes, semicolons, whitespace -- `ast`: Cyclomatic complexity, nesting depth -- `custom`: Rules that don't fit other categories +- `pattern`: 네이밍 컨벤션, 금지 패턴, import 제한 +- `length`: 라인/파일/함수 길이, 파라미터 수 +- `style`: 들여쓰기, 따옴표, 세미콜론, 공백 +- `ast`: 순환 복잡도, 중첩 깊이 +- `custom`: 기타 카테고리에 맞지 않는 규칙 -## Output Files +## 출력 파일 -### Generated Files +### 생성 파일 -1. **`.eslintrc.json`**: ESLint configuration for JavaScript/TypeScript -2. **`checkstyle.xml`**: Checkstyle configuration for Java -3. **`pmd-ruleset.xml`**: PMD ruleset for Java -4. **`code-policy.json`**: Internal validation policy -5. **`conversion-report.json`**: Detailed conversion report +1. **`.eslintrc.json`**: JavaScript/TypeScript용 ESLint 설정 +2. **`checkstyle.xml`**: Java용 Checkstyle 설정 +3. **`pmd-ruleset.xml`**: Java용 PMD 규칙셋 +4. **`code-policy.json`**: 내부 검증 정책 +5. **`conversion-report.json`**: 상세 변환 리포트 -### Conversion Report Format +### 변환 리포트 형식 ```json { @@ -192,202 +192,96 @@ sym convert -i user-policy.json -o code-policy.json } }, "warnings": [ - "eslint: Rule 2: low confidence (0.40 < 0.70): Maximum line length is 100 characters" + "eslint: Rule 2: low confidence (0.40 < 0.70): 한 줄은 최대 100자입니다" ] } ``` -## LLM Inference +## LLM 추론 -### How It Works +### 동작 방식 -1. **Cache Check**: First checks if the rule has been inferred before -2. **LLM Analysis**: Sends rule to OpenAI API with structured prompt -3. **Intent Extraction**: Parses JSON response to extract: - - Engine type (pattern/length/style/ast) - - Category (naming/security/etc.) - - Target (identifier/content/import) - - Scope (line/file/function) - - Parameters (max, min, case, etc.) - - Confidence score (0.0-1.0) -4. **Fallback**: If LLM fails, uses pattern matching -5. **Conversion**: Maps intent to linter-specific rules +1. **캐시 확인**: 먼저 규칙이 이전에 추론되었는지 확인 +2. **LLM 분석**: 구조화된 프롬프트와 함께 OpenAI API로 규칙 전송 +3. **Intent 추출**: JSON 응답을 파싱하여 추출: + - 엔진 타입 (pattern/length/style/ast) + - 카테고리 (naming/security 등) + - 타겟 (identifier/content/import) + - 범위 (line/file/function) + - 파라미터 (max, min, case 등) + - 신뢰도 점수 (0.0-1.0) +4. **폴백**: LLM 실패 시 패턴 매칭 사용 +5. **변환**: Intent를 linter별 규칙으로 매핑 -### System Prompt +### 폴백 추론 -The LLM is instructed to: -- Analyze natural language coding rules -- Extract structured intent with high precision -- Provide confidence scores for interpretations -- Return results in strict JSON format +LLM 사용 불가 시 패턴 기반 규칙이 감지: +- **네이밍 규칙**: "PascalCase", "camelCase", "name" 등의 키워드 +- **길이 규칙**: "line", "length", "max", "characters" 등의 키워드 +- **스타일 규칙**: "indent", "spaces", "tabs", "quote" 등의 키워드 +- **보안 규칙**: "secret", "password", "hardcoded" 등의 키워드 +- **import 규칙**: "import", "dependency", "layer" 등의 키워드 -### Fallback Inference +## 테스트 -When LLM is unavailable, pattern-based rules detect: -- **Naming rules**: Keywords like "PascalCase", "camelCase", "name" -- **Length rules**: Keywords like "line", "length", "max", "characters" -- **Style rules**: Keywords like "indent", "spaces", "tabs", "quote" -- **Security rules**: Keywords like "secret", "password", "hardcoded" -- **Import rules**: Keywords like "import", "dependency", "layer" - -## Example: Rule Conversion Flow - -### Input Rule -```json -{ - "say": "Class names must be PascalCase", - "category": "naming", - "languages": ["javascript", "java"] -} -``` - -### LLM Inference Result -```json -{ - "engine": "pattern", - "category": "naming", - "target": "identifier", - "params": {"case": "PascalCase"}, - "confidence": 0.95 -} -``` - -### ESLint Output -```json -{ - "rules": { - "id-match": ["error", "^[A-Z][a-zA-Z0-9]*$", { - "properties": false, - "classFields": false, - "onlyDeclarations": true - }] - } -} -``` - -### Checkstyle Output -```xml - - - - -``` - -### PMD Output -```xml - - 1 - -``` - -## Testing - -### Unit Tests +### 단위 테스트 ```bash -# Run all tests +# 모든 테스트 실행 go test ./... -# Run LLM inference tests +# LLM 추론 테스트 go test ./internal/llm/... -# Run linter converter tests +# Linter 변환기 테스트 go test ./internal/converter/linters/... ``` -### Integration Test - -```bash -# Test with example policy -./bin/sym convert \ - -i tests/testdata/user-policy-example.json \ - --targets all \ - --output-dir /tmp/test-output \ - --verbose -``` - -## Limitations +## 제한사항 ### ESLint -- Limited support for complex AST patterns -- Some rules require custom ESLint plugins -- Style rules may conflict with Prettier +- 복잡한 AST 패턴 지원 제한 +- 일부 규칙은 커스텀 ESLint 플러그인 필요 +- 스타일 규칙이 Prettier와 충돌 가능 ### Checkstyle -- Module configuration can be complex -- Some rules require additional checks -- Limited support for custom patterns +- 모듈 설정이 복잡할 수 있음 +- 일부 규칙은 추가 체크 필요 +- 커스텀 패턴 지원 제한 ### PMD -- Rule references must match PMD versions -- Property configuration varies by rule -- Some categories have limited coverage - -### LLM Inference -- Requires OpenAI API key (costs apply) -- May produce incorrect interpretations for complex rules -- Confidence scores are estimates -- Network dependency and latency - -## Future Enhancements - -- [ ] Support for additional linters (Pylint, RuboCop, etc.) -- [ ] Custom rule templates -- [ ] Rule conflict detection -- [ ] Interactive mode for ambiguous rules -- [ ] Cost estimation for LLM API calls -- [ ] Local LLM support (Ollama, etc.) -- [ ] Rule similarity clustering -- [ ] Automatic rule categorization -- [ ] Multi-language rule mapping optimization - -## Troubleshooting - -### "OPENAI_API_KEY not set" -- Set environment variable: `export OPENAI_API_KEY=sk-...` -- Or use fallback mode (lower accuracy) - -### "low confidence" warnings -- Increase `--confidence-threshold` to reduce warnings -- Provide more specific `category` and `params` in rules -- Use LLM instead of fallback for better accuracy - -### Generated rules don't work -- Check linter version compatibility -- Verify rule syntax in linter documentation -- Adjust rule parameters manually if needed -- Report issue with conversion-report.json +- 규칙 참조는 PMD 버전과 일치해야 함 +- 속성 설정이 규칙마다 다름 +- 일부 카테고리의 커버리지 제한 -### Slow conversion -- Reduce number of rules -- Use caching (re-run with same rules) -- Increase `--timeout` for large rule sets -- Use faster OpenAI model (gpt-4o) +### LLM 추론 +- OpenAI API 키 필요 (비용 발생) +- 복잡한 규칙에 대해 잘못된 해석 가능 +- 신뢰도 점수는 추정치 +- 네트워크 의존성 및 지연 -## Performance +## 성능 -### Benchmarks (5 rules, no cache) +### 벤치마크 (5개 규칙, 캐시 없음) -- **With LLM (gpt-4o)**: ~5-10 seconds -- **Fallback only**: <1 second -- **With cache**: <100ms +- **LLM 사용 (gpt-4o)**: 약 5-10초 +- **폴백만**: 1초 미만 +- **캐시 적용**: 100ms 미만 -### Cost Estimation +### 비용 추정 -- **gpt-4o**: ~$0.001 per rule -- **gpt-4o**: ~$0.01 per rule -- **Caching**: Reduces cost by ~90% for repeated rules +- **gpt-4o**: 규칙당 약 $0.001 +- **캐싱**: 반복 규칙에 대해 비용 약 90% 절감 -## Contributing +## 기여 -When adding new linter support: +새 linter 지원 추가 시: -1. Implement `LinterConverter` interface in `internal/converter/linters/` -2. Register converter in `init()` function -3. Add tests in `*_test.go` -4. Update this documentation -5. Add example output to `tests/testdata/` +1. `internal/converter/linters/`에 `LinterConverter` 인터페이스 구현 +2. `init()` 함수에서 변환기 등록 +3. `*_test.go`에 테스트 추가 +4. 이 문서 업데이트 -## License +## 라이선스 -Same as sym-cli project license. +sym-cli 프로젝트 라이선스와 동일 diff --git a/docs/CONVERT_USAGE.md b/docs/CONVERT_USAGE.md index 5fa8cf8..6ea61a5 100644 --- a/docs/CONVERT_USAGE.md +++ b/docs/CONVERT_USAGE.md @@ -1,66 +1,66 @@ -# Convert Command Usage Guide +# Convert 명령어 사용 가이드 -## Quick Start +## 빠른 시작 -Convert natural language rules to linter configurations: +자연어 규칙을 linter 설정으로 변환: ```bash -# Convert to all supported linters (outputs to /.sym) +# 모든 지원 linter로 변환 (출력: /.sym) sym convert -i user-policy.json --targets all -# Convert only for JavaScript/TypeScript +# JavaScript/TypeScript만 sym convert -i user-policy.json --targets eslint -# Convert for Java +# Java만 sym convert -i user-policy.json --targets checkstyle,pmd ``` -## Default Output Directory +## 기본 출력 디렉토리 -**Important**: The convert command automatically creates a `.sym` directory at your git repository root and saves all generated files there. +**중요**: convert 명령어는 Git 저장소 루트에 `.sym` 디렉토리를 자동 생성하고 모든 파일을 저장합니다. -### Directory Structure +### 디렉토리 구조 ``` your-project/ -├── .git/ -├── .sym/ # Auto-generated -│ ├── .eslintrc.json # ESLint config -│ ├── checkstyle.xml # Checkstyle config -│ ├── pmd-ruleset.xml # PMD config -│ ├── code-policy.json # Internal policy -│ └── conversion-report.json # Conversion report -├── src/ -└── user-policy.json # Your input file ++-- .git/ ++-- .sym/ # 자동 생성 +| +-- .eslintrc.json # ESLint 설정 +| +-- checkstyle.xml # Checkstyle 설정 +| +-- pmd-ruleset.xml # PMD 설정 +| +-- code-policy.json # 내부 정책 +| +-- conversion-report.json # 변환 리포트 ++-- src/ ++-- user-policy.json # 입력 파일 ``` -### Why .sym? +### 왜 .sym인가? -- **Consistent location**: Always at git root, easy to find -- **Version control**: Add to `.gitignore` to keep generated files out of git -- **CI/CD friendly**: Scripts can always find configs at `/.sym` +- **일관된 위치**: 항상 Git 루트에 있어 찾기 쉬움 +- **버전 관리**: `.gitignore`에 추가하여 생성 파일을 Git에서 제외 가능 +- **CI/CD 친화적**: 스크립트가 항상 `/.sym`에서 설정 찾음 -### Custom Output Directory +### 커스텀 출력 디렉토리 -If you need a different location: +다른 위치가 필요한 경우: ```bash sym convert -i user-policy.json --targets all --output-dir ./custom-dir ``` -## Prerequisites +## 사전 요구사항 -1. **Git repository**: Run the command from within a git repository -2. **OpenAI API key** (optional): Set `OPENAI_API_KEY` for better inference +1. **Git 저장소**: Git 저장소 내에서 명령어 실행 +2. **OpenAI API 키** (선택): 더 나은 추론을 위해 `OPENAI_API_KEY` 설정 ```bash export OPENAI_API_KEY=sk-... ``` -Without API key, fallback pattern matching is used (lower accuracy). +API 키 없이도 폴백 패턴 매칭 사용 (정확도 낮음) -## User Policy File +## 사용자 정책 파일 -Create a `user-policy.json` with natural language rules: +자연어 규칙으로 `user-policy.json` 작성: ```json { @@ -71,134 +71,132 @@ Create a `user-policy.json` with natural language rules: }, "rules": [ { - "say": "Class names must be PascalCase", + "say": "클래스 이름은 PascalCase여야 합니다", "category": "naming", "languages": ["javascript", "typescript", "java"] }, { - "say": "Maximum line length is 100 characters", + "say": "한 줄은 최대 100자입니다", "category": "length" }, { - "say": "Use 4 spaces for indentation", + "say": "들여쓰기는 4칸 공백을 사용합니다", "category": "style" } ] } ``` -## Command Options +## 명령어 옵션 -### Basic Options +### 기본 옵션 -- `-i, --input`: Input user policy file (default: `user-policy.json`) -- `--targets`: Target linters (comma-separated or `all`) +- `-i, --input`: 입력 사용자 정책 파일 (기본값: `user-policy.json`) +- `--targets`: 타겟 linter (쉼표 구분 또는 `all`) - `eslint` - JavaScript/TypeScript - `checkstyle` - Java - `pmd` - Java - - `all` - All supported linters + - `all` - 모든 지원 linter -### Advanced Options +### 고급 옵션 -- `--output-dir`: Custom output directory (default: `/.sym`) -- `--openai-model`: OpenAI model (default: `gpt-4o`) - - `gpt-4o` - Fast, cheap, good quality - - `gpt-4o` - Slower, more expensive, best quality -- `--confidence-threshold`: Minimum confidence (default: `0.7`) - - Range: 0.0 to 1.0 - - Lower values = more rules converted, more warnings -- `--timeout`: API timeout in seconds (default: `30`) -- `-v, --verbose`: Enable detailed logging +- `--output-dir`: 커스텀 출력 디렉토리 (기본값: `/.sym`) +- `--openai-model`: OpenAI 모델 (기본값: `gpt-4o`) +- `--confidence-threshold`: 최소 신뢰도 (기본값: `0.7`) + - 범위: 0.0 ~ 1.0 + - 낮은 값 = 더 많은 규칙 변환, 더 많은 경고 +- `--timeout`: API 타임아웃 초 (기본값: `30`) +- `-v, --verbose`: 상세 로깅 활성화 -### Legacy Mode +### 레거시 모드 -Generate only internal `code-policy.json`: +내부 `code-policy.json`만 생성: ```bash sym convert -i user-policy.json -o code-policy.json ``` -## Example Workflows +## 예제 워크플로우 -### JavaScript/TypeScript Project +### JavaScript/TypeScript 프로젝트 ```bash -# 1. Create user-policy.json +# 1. user-policy.json 작성 cat > user-policy.json <> .gitignore -# But commit user-policy.json +# user-policy.json은 커밋 git add user-policy.json git commit -m "Add coding conventions policy" ``` -### Sharing Configs +### 설정 공유 ```bash -# Share with team +# 팀과 공유 git add .sym/*.{json,xml} git commit -m "Add generated linter configs" -# Or regenerate on each machine -# (Each developer runs: sym convert -i user-policy.json --targets all) +# 또는 각 머신에서 재생성 +# (각 개발자가 실행: sym convert -i user-policy.json --targets all) ``` -### Updating Rules +### 규칙 업데이트 ```bash -# 1. Edit user-policy.json -# 2. Regenerate configs +# 1. user-policy.json 편집 +# 2. 설정 재생성 sym convert -i user-policy.json --targets all -# 3. Review changes +# 3. 변경사항 검토 git diff .sym/ -# 4. Apply to project +# 4. 프로젝트에 적용 npx eslint --config .sym/.eslintrc.json src/ ``` -## Next Steps +## 다음 단계 -- [Full Feature Documentation](CONVERT_FEATURE.md) -- [User Policy Schema Reference](../tests/testdata/user-policy-example.json) -- [Contributing Guide](../AGENTS.md) +- [전체 기능 문서](CONVERT_FEATURE.md) +- [기여 가이드](../AGENTS.md) diff --git a/docs/LINTER_VALIDATION.md b/docs/LINTER_VALIDATION.md index c425461..3c085c7 100644 --- a/docs/LINTER_VALIDATION.md +++ b/docs/LINTER_VALIDATION.md @@ -1,60 +1,43 @@ -# Linter Configuration Validation +# Linter 설정 검증 -## Purpose +## 목적 -The convert feature generates linter-specific configurations from natural language coding conventions. These configurations are used to validate code changes tracked by git. +convert 기능은 자연어 코딩 컨벤션에서 linter별 설정을 생성합니다. 이 설정은 Git으로 추적되는 코드 변경사항을 검증하는 데 사용됩니다. -## Supported Linters +## 지원 Linter ### JavaScript/TypeScript -- **ESLint**: Validates JS/TS code style, patterns, and best practices -- Output: `.sym/.eslintrc.json` +- **ESLint**: JS/TS 코드 스타일, 패턴, 모범 사례 검증 +- 출력: `.sym/.eslintrc.json` ### Java -- **Checkstyle**: Validates Java code formatting and style -- Output: `.sym/checkstyle.xml` -- **PMD**: Validates Java code quality and detects code smells -- Output: `.sym/pmd-ruleset.xml` +- **Checkstyle**: Java 코드 포맷팅 및 스타일 검증 +- 출력: `.sym/checkstyle.xml` +- **PMD**: Java 코드 품질 검증 및 코드 스멜 감지 +- 출력: `.sym/pmd-ruleset.xml` -### Future Support -- **SonarQube**: Multi-language static analysis -- **LLM Validator**: Custom rules that cannot be expressed in traditional linters +### 향후 지원 예정 +- **SonarQube**: 다중 언어 정적 분석 +- **LLM Validator**: 전통적인 linter로 표현할 수 없는 커스텀 규칙 -## Validation Scripts +## 엔진 할당 -### Validate ESLint Config -```bash -./scripts/validate-eslint.sh -``` - -### Validate Checkstyle Config -```bash -./scripts/validate-checkstyle.sh -``` - -## Engine Assignment +`code-policy.json`의 각 규칙에는 검증 도구를 지정하는 `engine` 필드가 있습니다: -Each rule in `code-policy.json` has an `engine` field that specifies which tool validates it: +- `eslint`: ESLint 설정으로 변환된 규칙 +- `checkstyle`: Checkstyle 모듈로 변환된 규칙 +- `pmd`: PMD 규칙셋으로 변환된 규칙 +- `sonarqube`: 향후 지원 예정 +- `llm-validator`: LLM 분석이 필요한 복잡한 규칙 -- `eslint`: Rule converted to ESLint configuration -- `checkstyle`: Rule converted to Checkstyle module -- `pmd`: Rule converted to PMD ruleset -- `sonarqube`: Future support -- `llm-validator`: Complex rules requiring LLM analysis +## 예제 워크플로우 -## Example Workflow - -1. **Define conventions** in `user-policy.json` -2. **Convert** to linter configs: +1. `user-policy.json`에 **컨벤션 정의** +2. linter 설정으로 **변환**: ```bash sym convert -i user-policy.json --targets eslint,checkstyle,pmd ``` -3. **Validate** generated configs: - ```bash - ./scripts/validate-eslint.sh - ./scripts/validate-checkstyle.sh - ``` -4. **Run linters** on git changes: +3. Git 변경사항에 **linter 실행**: ```bash # JavaScript/TypeScript eslint --config .sym/.eslintrc.json src/**/*.{js,ts} @@ -64,9 +47,9 @@ Each rule in `code-policy.json` has an `engine` field that specifies which tool pmd check -R .sym/pmd-ruleset.xml -d src/ ``` -## Code Policy Schema +## 코드 정책 스키마 -Generated `code-policy.json` contains: +생성된 `code-policy.json` 내용: ```json { "version": "1.0.0", @@ -85,46 +68,20 @@ Generated `code-policy.json` contains: } ``` -Rules with `engine: "llm-validator"` cannot be checked by traditional linters and require custom LLM-based validation. - -## Testing - -### Integration Test Data - -Validation engines are tested using structured test data in `testdata/`: +`engine: "llm-validator"` 규칙은 전통적인 linter로 체크할 수 없으며 커스텀 LLM 기반 검증이 필요합니다. -``` -testdata/ -├── javascript/ # ESLint-based validation tests -│ ├── pattern/ # Naming conventions, regex patterns -│ ├── length/ # Line/function length limits -│ ├── style/ # Code formatting -│ └── ast/ # AST structure validation -├── typescript/ # TSC-based validation tests -│ └── typechecker/ # Type checking tests -└── java/ # Checkstyle/PMD-based validation tests - ├── pattern/ # Naming conventions (PascalCase, camelCase) - ├── length/ # Line/method/parameter length limits - ├── style/ # Java formatting conventions - └── ast/ # Code structure (exception handling, etc.) -``` +## 테스트 -Each directory contains: -- **Violation files**: Code that violates conventions (e.g., `NamingViolations.java`) -- **Valid files**: Code that complies with conventions (e.g., `ValidNaming.java`) - -### Running Integration Tests +### 통합 테스트 실행 ```bash -# All integration tests +# 모든 통합 테스트 go test ./tests/integration/... -v -# Specific engine tests +# 특정 엔진 테스트 go test ./tests/integration/... -v -run TestPatternEngine go test ./tests/integration/... -v -run TestLengthEngine go test ./tests/integration/... -v -run TestStyleEngine go test ./tests/integration/... -v -run TestASTEngine go test ./tests/integration/... -v -run TestTypeChecker ``` - -For detailed test data structure, see [testdata/README.md](../testdata/README.md). diff --git a/examples/check-access.sh b/examples/check-access.sh deleted file mode 100644 index fe026db..0000000 --- a/examples/check-access.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -# 간단한 액세스 체크 스크립트 - -# 현재 사용자 정보 -USER=$(symphony whoami --json 2>/dev/null | jq -r '.username') -ROLE=$(symphony my-role --json 2>/dev/null | jq -r '.role') -REPO=$(symphony my-role --json 2>/dev/null | jq -r '"\(.owner)/\(.repo)"') - -if [ -z "$USER" ] || [ "$USER" == "null" ]; then - echo "❌ Not logged in" - echo "Run: symphony login" - exit 1 -fi - -echo "Access Check Report" -echo "===================" -echo "User: $USER" -echo "Repository: $REPO" -echo "Role: $ROLE" -echo "" - -# 역할별 권한 표시 -case "$ROLE" in - "admin") - echo "Permissions:" - echo " ✓ Read files" - echo " ✓ Write files" - echo " ✓ Deploy to production" - echo " ✓ Manage roles" - ;; - "developer") - echo "Permissions:" - echo " ✓ Read files" - echo " ✓ Write files" - echo " ✓ Deploy to staging" - echo " ✗ Deploy to production" - echo " ✗ Manage roles" - ;; - "viewer") - echo "Permissions:" - echo " ✓ Read files" - echo " ✗ Write files" - echo " ✗ Deploy" - echo " ✗ Manage roles" - ;; - "none") - echo "⚠️ No permissions assigned" - echo "Contact an admin to get access" - ;; -esac diff --git a/examples/deploy.sh b/examples/deploy.sh deleted file mode 100644 index 3a7bc26..0000000 --- a/examples/deploy.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash - -# 배포 권한 체크 예제 스크립트 - -echo "🚀 Deployment Permission Check" -echo "==============================" - -# Symphony CLI API를 사용하여 역할 확인 -echo "Checking your role..." - -ROLE=$(symphony my-role --json 2>/dev/null | jq -r '.role') - -if [ $? -ne 0 ]; then - echo "❌ Error: Failed to check role" - echo " Make sure you are logged in: symphony login" - exit 1 -fi - -echo "Current role: $ROLE" - -# 역할에 따른 권한 체크 -case "$ROLE" in - "admin") - echo "✓ Admin access: Full deployment permissions" - DEPLOYMENT_ENV="production" - ;; - "developer") - echo "✓ Developer access: Staging deployment only" - DEPLOYMENT_ENV="staging" - ;; - "viewer"|"none") - echo "❌ Error: Insufficient permissions for deployment" - echo " Your role ($ROLE) does not allow deployments" - echo " Contact an admin to get developer or admin access" - exit 1 - ;; - *) - echo "❌ Error: Unknown role: $ROLE" - exit 1 - ;; -esac - -echo "" -echo "✓ Permission check passed" -echo "Deploying to: $DEPLOYMENT_ENV" -echo "" - -# 실제 배포 로직 (예시) -echo "Running deployment..." -echo " - Building application..." -# npm run build - -echo " - Running tests..." -# npm test - -echo " - Deploying to $DEPLOYMENT_ENV..." -# ./deploy-to-$DEPLOYMENT_ENV.sh - -echo "" -echo "✅ Deployment completed successfully!" diff --git a/examples/roles.json b/examples/roles.json deleted file mode 100644 index 5a96412..0000000 --- a/examples/roles.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "admin": [ - "alice", - "bob" - ], - "developer": [ - "charlie", - "david", - "eve" - ], - "viewer": [ - "frank", - "grace" - ] -} diff --git a/internal/adapter/prettier/executor_test.go b/internal/adapter/prettier/executor_test.go index f101bd0..99bfa01 100644 --- a/internal/adapter/prettier/executor_test.go +++ b/internal/adapter/prettier/executor_test.go @@ -116,7 +116,8 @@ func TestExecute_Integration(t *testing.T) { return } + // output can be nil if Prettier is not installed if output == nil { - t.Error("Expected non-nil output") + t.Skip("Skipping: Prettier not available in this environment") } } diff --git a/internal/adapter/registry/errors.go b/internal/adapter/registry/errors.go index aff2741..5dd7688 100644 --- a/internal/adapter/registry/errors.go +++ b/internal/adapter/registry/errors.go @@ -2,14 +2,14 @@ package registry import "fmt" -// ErrAdapterNotFound is returned when no adapter is found for the given tool name. -type ErrAdapterNotFound struct { +// errAdapterNotFound is returned when no adapter is found for the given tool name. +type errAdapterNotFound struct { ToolName string } -func (e *ErrAdapterNotFound) Error() string { +func (e *errAdapterNotFound) Error() string { return fmt.Sprintf("adapter not found: %s", e.ToolName) } -// ErrNilAdapter is returned when trying to register a nil adapter. -var ErrNilAdapter = fmt.Errorf("cannot register nil adapter") +// errNilAdapter is returned when trying to register a nil adapter. +var errNilAdapter = fmt.Errorf("cannot register nil adapter") diff --git a/internal/adapter/registry/registry.go b/internal/adapter/registry/registry.go index 4bb3333..3e87958 100644 --- a/internal/adapter/registry/registry.go +++ b/internal/adapter/registry/registry.go @@ -22,10 +22,10 @@ func NewRegistry() *Registry { } // Register adds an adapter to the registry. -// Returns ErrNilAdapter if the adapter is nil. +// Returns errNilAdapter if the adapter is nil. func (r *Registry) Register(adp adapter.Adapter) error { if adp == nil { - return ErrNilAdapter + return errNilAdapter } r.mu.Lock() @@ -38,40 +38,15 @@ func (r *Registry) Register(adp adapter.Adapter) error { } // GetAdapter finds an adapter by tool name (e.g., "eslint", "prettier", "tsc"). -// Returns the adapter, or ErrAdapterNotFound if not registered. +// Returns the adapter, or errAdapterNotFound if not registered. func (r *Registry) GetAdapter(toolName string) (adapter.Adapter, error) { r.mu.RLock() defer r.mu.RUnlock() adp, ok := r.adapters[toolName] if !ok { - return nil, &ErrAdapterNotFound{ToolName: toolName} + return nil, &errAdapterNotFound{ToolName: toolName} } return adp, nil } - -// GetAll returns all registered adapters. -func (r *Registry) GetAll() []adapter.Adapter { - r.mu.RLock() - defer r.mu.RUnlock() - - // Return a copy to prevent external modification - result := make([]adapter.Adapter, 0, len(r.adapters)) - for _, adp := range r.adapters { - result = append(result, adp) - } - return result -} - -// ListTools returns all registered tool names. -func (r *Registry) ListTools() []string { - r.mu.RLock() - defer r.mu.RUnlock() - - tools := make([]string, 0, len(r.adapters)) - for name := range r.adapters { - tools = append(tools, name) - } - return tools -} diff --git a/internal/auth/server.go b/internal/auth/server.go index aab1031..9b08097 100644 --- a/internal/auth/server.go +++ b/internal/auth/server.go @@ -11,15 +11,15 @@ import ( "github.com/pkg/browser" ) -// SessionResponse is the response from /authStart -type SessionResponse struct { +// sessionResponse is the response from /authStart +type sessionResponse struct { SessionCode string `json:"session_code"` AuthURL string `json:"auth_url"` ExpiresIn int `json:"expires_in"` } -// StatusResponse is the response from /authStatus -type StatusResponse struct { +// statusResponse is the response from /authStatus +type statusResponse struct { Status string `json:"status"` Message string `json:"message,omitempty"` Error string `json:"error,omitempty"` @@ -64,7 +64,7 @@ func AuthenticateWithServer(serverURL string) (string, string, error) { } // startAuthSession starts a new authentication session -func startAuthSession(serverURL string) (*SessionResponse, error) { +func startAuthSession(serverURL string) (*sessionResponse, error) { url := serverURL + "/authStart" requestBody := map[string]string{ @@ -87,7 +87,7 @@ func startAuthSession(serverURL string) (*SessionResponse, error) { return nil, fmt.Errorf("server returned error: %s (status: %d)", string(body), resp.StatusCode) } - var session SessionResponse + var session sessionResponse if err := json.NewDecoder(resp.Body).Decode(&session); err != nil { return nil, fmt.Errorf("failed to parse server response: %w", err) } @@ -147,7 +147,7 @@ func pollForToken(serverURL, sessionCode string, expiresIn int) (string, string, } // checkAuthStatus checks the authentication status -func checkAuthStatus(url string) (*StatusResponse, error) { +func checkAuthStatus(url string) (*statusResponse, error) { resp, err := http.Get(url) if err != nil { return nil, err @@ -160,14 +160,14 @@ func checkAuthStatus(url string) (*StatusResponse, error) { if resp.StatusCode == http.StatusGone { // Session expired - var status StatusResponse + var status statusResponse _ = json.NewDecoder(resp.Body).Decode(&status) return &status, nil } if resp.StatusCode == http.StatusForbidden { // Denied - var status StatusResponse + var status statusResponse _ = json.NewDecoder(resp.Body).Decode(&status) return &status, nil } @@ -177,7 +177,7 @@ func checkAuthStatus(url string) (*StatusResponse, error) { return nil, fmt.Errorf("server error: %s (status: %d)", string(body), resp.StatusCode) } - var status StatusResponse + var status statusResponse if err := json.NewDecoder(resp.Body).Decode(&status); err != nil { return nil, err } diff --git a/internal/cmd/export.go b/internal/cmd/export.go deleted file mode 100644 index e09e5f7..0000000 --- a/internal/cmd/export.go +++ /dev/null @@ -1,40 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -var exportCmd = &cobra.Command{ - Use: "export [path]", - Short: "현재 작업에 필요한 컨벤션을 추출하여 반환합니다", - Long: `현재 작업 컨텍스트에 맞는 관련 컨벤션만 추출하여 반환합니다. -LLM이 작업 시 컨텍스트에 포함할 수 있도록 최적화된 형태로 제공됩니다.`, - Args: cobra.MaximumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - path := "." - if len(args) > 0 { - path = args[0] - } - - context, _ := cmd.Flags().GetString("context") - format, _ := cmd.Flags().GetString("format") - - fmt.Printf("Exporting conventions for: %s\n", path) - fmt.Printf("Context: %s\n", context) - fmt.Printf("Format: %s\n", format) - - // TODO: 실제 내보내기 로직 구현 - return nil - }, -} - -func init() { - rootCmd.AddCommand(exportCmd) - - exportCmd.Flags().StringP("context", "c", "", "work context description") - exportCmd.Flags().StringP("format", "f", "text", "output format (text|json|markdown)") - exportCmd.Flags().StringSlice("files", []string{}, "files being modified") - exportCmd.Flags().StringSlice("languages", []string{}, "programming languages involved") -} diff --git a/internal/cmd/mcp.go b/internal/cmd/mcp.go index e38c4db..3b726bf 100644 --- a/internal/cmd/mcp.go +++ b/internal/cmd/mcp.go @@ -12,26 +12,21 @@ import ( "github.com/spf13/cobra" ) -var ( - mcpConfig string - mcpHost string - mcpPort int -) +var mcpConfig string var mcpCmd = &cobra.Command{ Use: "mcp", Short: "Start MCP server to integrate with LLM tools", Long: `Start Model Context Protocol (MCP) server. -LLM-based coding tools can query conventions and validate code through stdio or HTTP. +LLM-based coding tools can query conventions and validate code through stdio. Tools provided by MCP server: - query_conventions: Query conventions for given context - validate_code: Validate code compliance with conventions -By default, communicates via stdio. If --port is specified, starts HTTP server.`, +Communicates via stdio for integration with Claude Desktop, Claude Code, Cursor, and other MCP clients.`, Example: ` sym mcp - sym mcp --config code-policy.json - sym mcp --port 4000 --host 0.0.0.0`, + sym mcp --config code-policy.json`, RunE: runMCP, } @@ -39,8 +34,6 @@ func init() { rootCmd.AddCommand(mcpCmd) mcpCmd.Flags().StringVarP(&mcpConfig, "config", "c", "", "policy file path (code-policy.json)") - mcpCmd.Flags().StringVar(&mcpHost, "host", "127.0.0.1", "server host (HTTP mode only)") - mcpCmd.Flags().IntVarP(&mcpPort, "port", "p", 0, "server port (0 = stdio mode, >0 = HTTP mode)") } func runMCP(cmd *cobra.Command, args []string) error { @@ -81,7 +74,7 @@ func runMCP(cmd *cobra.Command, args []string) error { } // Start MCP server - it will handle conversion automatically if needed - server := mcp.NewServer(mcpHost, mcpPort, configPath) + server := mcp.NewServer(configPath) return server.Start() } diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 01f1228..92553c8 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -47,9 +47,7 @@ func init() { rootCmd.AddCommand(policyCmd) // Note: mcpCmd is registered in mcp.go's init() - // sym-cli core commands (in development) + // sym-cli core commands rootCmd.AddCommand(convertCmd) rootCmd.AddCommand(validateCmd) - // TODO: implement export command - // rootCmd.AddCommand(exportCmd) } diff --git a/internal/config/config.go b/internal/config/config.go index e77caaf..1950d87 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -44,8 +44,8 @@ func init() { tokenPath = filepath.Join(configDir, "token.json") } -// EnsureConfigDir creates the config directory if it doesn't exist -func EnsureConfigDir() error { +// ensureConfigDir creates the config directory if it doesn't exist +func ensureConfigDir() error { return os.MkdirAll(configDir, 0700) } @@ -70,7 +70,7 @@ func LoadConfig() (*Config, error) { // SaveConfig saves the configuration to file func SaveConfig(cfg *Config) error { - if err := EnsureConfigDir(); err != nil { + if err := ensureConfigDir(); err != nil { return err } @@ -103,7 +103,7 @@ func LoadToken() (*Token, error) { // SaveToken saves the access token to file func SaveToken(token *Token) error { - if err := EnsureConfigDir(); err != nil { + if err := ensureConfigDir(); err != nil { return err } diff --git a/internal/converter/converter.go b/internal/converter/converter.go index b58a4ab..025cdce 100644 --- a/internal/converter/converter.go +++ b/internal/converter/converter.go @@ -14,8 +14,8 @@ import ( "github.com/DevSymphony/sym-cli/pkg/schema" ) -// LanguageLinterMapping defines which linters are available for each language -var LanguageLinterMapping = map[string][]string{ +// languageLinterMapping defines which linters are available for each language +var languageLinterMapping = map[string][]string{ "javascript": {"eslint", "prettier"}, "js": {"eslint", "prettier"}, "typescript": {"tsc", "eslint", "prettier"}, @@ -25,8 +25,8 @@ var LanguageLinterMapping = map[string][]string{ "java": {"checkstyle", "pmd"}, } -// Special linter for rules that don't fit any specific external linter -const LLMValidatorEngine = "llm-validator" +// llmValidatorEngine is the special linter for rules that don't fit any specific external linter +const llmValidatorEngine = "llm-validator" // Converter is the main converter with language-based routing type Converter struct { @@ -115,7 +115,7 @@ func (c *Converter) Convert(ctx context.Context, userPolicy *schema.UserPolicy) } // Skip llm-validator - it will be handled in CodePolicy only - if linterName == LLMValidatorEngine { + if linterName == llmValidatorEngine { continue } @@ -189,7 +189,7 @@ func (c *Converter) Convert(ctx context.Context, userPolicy *schema.UserPolicy) } // Special handling for LLM validator - ensure required fields - if linterName == LLMValidatorEngine { + if linterName == llmValidatorEngine { // LLM validator MUST have 'when' selector for file filtering if policyRule.When == nil { // Use languages from rule or defaults @@ -214,7 +214,7 @@ func (c *Converter) Convert(ctx context.Context, userPolicy *schema.UserPolicy) } // Add selector if languages are specified (for non-LLM linters) - if linterName != LLMValidatorEngine && (len(userRule.Languages) > 0 || len(userRule.Include) > 0 || len(userRule.Exclude) > 0) { + if linterName != llmValidatorEngine && (len(userRule.Languages) > 0 || len(userRule.Include) > 0 || len(userRule.Exclude) > 0) { policyRule.When = &schema.Selector{ Languages: userRule.Languages, Include: userRule.Include, @@ -274,7 +274,7 @@ func (c *Converter) routeRulesWithLLM(ctx context.Context, userPolicy *schema.Us availableLinters := c.getAvailableLinters(languages) if len(availableLinters) == 0 { // No language-specific linters, use llm-validator - linterRules[LLMValidatorEngine] = append(linterRules[LLMValidatorEngine], rule) + linterRules[llmValidatorEngine] = append(linterRules[llmValidatorEngine], rule) continue } @@ -283,7 +283,7 @@ func (c *Converter) routeRulesWithLLM(ctx context.Context, userPolicy *schema.Us if len(selectedLinters) == 0 { // LLM couldn't map to any linter, use llm-validator - linterRules[LLMValidatorEngine] = append(linterRules[LLMValidatorEngine], rule) + linterRules[llmValidatorEngine] = append(linterRules[llmValidatorEngine], rule) } else { // Add rule to selected linters for _, linter := range selectedLinters { @@ -304,7 +304,7 @@ func (c *Converter) getAvailableLinters(languages []string) []string { linterSet := make(map[string]bool) for _, lang := range languages { - if linters, ok := LanguageLinterMapping[lang]; ok { + if linters, ok := languageLinterMapping[lang]; ok { for _, linter := range linters { linterSet[linter] = true } diff --git a/internal/converter/linters/checkstyle.go b/internal/converter/linters/checkstyle.go index d90bb57..e92c69a 100644 --- a/internal/converter/linters/checkstyle.go +++ b/internal/converter/linters/checkstyle.go @@ -30,26 +30,26 @@ func (c *CheckstyleLinterConverter) SupportedLanguages() []string { return []string{"java"} } -// CheckstyleModule represents a Checkstyle module -type CheckstyleModule struct { - XMLName xml.Name `xml:"module"` - Name string `xml:"name,attr"` - Properties []CheckstyleProperty `xml:"property,omitempty"` - Modules []CheckstyleModule `xml:"module,omitempty"` +// checkstyleModule represents a Checkstyle module +type checkstyleModule struct { + XMLName xml.Name `xml:"module"` + Name string `xml:"name,attr"` + Properties []checkstyleProperty `xml:"property,omitempty"` + Modules []checkstyleModule `xml:"module,omitempty"` } -// CheckstyleProperty represents a property -type CheckstyleProperty struct { +// checkstyleProperty represents a property +type checkstyleProperty struct { XMLName xml.Name `xml:"property"` Name string `xml:"name,attr"` Value string `xml:"value,attr"` } -// CheckstyleConfig represents root configuration -type CheckstyleConfig struct { +// checkstyleConfig represents root configuration +type checkstyleConfig struct { XMLName xml.Name `xml:"module"` Name string `xml:"name,attr"` - Modules []CheckstyleModule `xml:"module"` + Modules []checkstyleModule `xml:"module"` } // ConvertRules converts user rules to Checkstyle configuration using LLM @@ -61,7 +61,7 @@ func (c *CheckstyleLinterConverter) ConvertRules(ctx context.Context, rules []sc // Convert rules in parallel type moduleResult struct { index int - module *CheckstyleModule + module *checkstyleModule err error } @@ -88,7 +88,7 @@ func (c *CheckstyleLinterConverter) ConvertRules(ctx context.Context, rules []sc }() // Collect modules - var modules []CheckstyleModule + var modules []checkstyleModule var errors []string for result := range results { @@ -107,14 +107,14 @@ func (c *CheckstyleLinterConverter) ConvertRules(ctx context.Context, rules []sc } // Build Checkstyle configuration - treeWalker := CheckstyleModule{ + treeWalker := checkstyleModule{ Name: "TreeWalker", Modules: modules, } - config := CheckstyleConfig{ + config := checkstyleConfig{ Name: "Checker", - Modules: []CheckstyleModule{treeWalker}, + Modules: []checkstyleModule{treeWalker}, } // Marshal to XML @@ -139,7 +139,7 @@ func (c *CheckstyleLinterConverter) ConvertRules(ctx context.Context, rules []sc } // convertSingleRule converts a single rule using LLM -func (c *CheckstyleLinterConverter) convertSingleRule(ctx context.Context, rule schema.UserRule, llmClient *llm.Client) (*CheckstyleModule, error) { +func (c *CheckstyleLinterConverter) convertSingleRule(ctx context.Context, rule schema.UserRule, llmClient *llm.Client) (*checkstyleModule, error) { systemPrompt := `You are a Checkstyle configuration expert. Convert natural language Java coding rules to Checkstyle modules. Return ONLY a JSON object (no markdown fences): @@ -211,20 +211,20 @@ Output: } // Build module - module := &CheckstyleModule{ + module := &checkstyleModule{ Name: result.ModuleName, - Properties: []CheckstyleProperty{}, + Properties: []checkstyleProperty{}, } // Add severity - module.Properties = append(module.Properties, CheckstyleProperty{ + module.Properties = append(module.Properties, checkstyleProperty{ Name: "severity", Value: mapCheckstyleSeverity(result.Severity), }) // Add other properties for key, value := range result.Properties { - module.Properties = append(module.Properties, CheckstyleProperty{ + module.Properties = append(module.Properties, checkstyleProperty{ Name: key, Value: value, }) diff --git a/internal/converter/linters/pmd.go b/internal/converter/linters/pmd.go index ea7e577..75e2939 100644 --- a/internal/converter/linters/pmd.go +++ b/internal/converter/linters/pmd.go @@ -30,19 +30,19 @@ func (c *PMDLinterConverter) SupportedLanguages() []string { return []string{"java"} } -// PMDRuleset represents PMD ruleset -type PMDRuleset struct { +// pmdRuleset represents PMD ruleset +type pmdRuleset struct { XMLName xml.Name `xml:"ruleset"` Name string `xml:"name,attr"` XMLNS string `xml:"xmlns,attr"` XMLNSXSI string `xml:"xmlns:xsi,attr"` XSISchema string `xml:"xsi:schemaLocation,attr"` Description string `xml:"description"` - Rules []PMDRule `xml:"rule"` + Rules []pmdRule `xml:"rule"` } -// PMDRule represents a PMD rule -type PMDRule struct { +// pmdRule represents a PMD rule +type pmdRule struct { XMLName xml.Name `xml:"rule"` Ref string `xml:"ref,attr"` Priority int `xml:"priority,omitempty"` @@ -57,7 +57,7 @@ func (c *PMDLinterConverter) ConvertRules(ctx context.Context, rules []schema.Us // Convert rules in parallel type ruleResult struct { index int - rule *PMDRule + rule *pmdRule err error } @@ -84,7 +84,7 @@ func (c *PMDLinterConverter) ConvertRules(ctx context.Context, rules []schema.Us }() // Collect rules - var pmdRules []PMDRule + var pmdRules []pmdRule var errors []string for result := range results { @@ -103,7 +103,7 @@ func (c *PMDLinterConverter) ConvertRules(ctx context.Context, rules []schema.Us } // Build PMD ruleset - ruleset := PMDRuleset{ + ruleset := pmdRuleset{ Name: "Symphony Rules", XMLNS: "http://pmd.sourceforge.net/ruleset/2.0.0", XMLNSXSI: "http://www.w3.org/2001/XMLSchema-instance", @@ -129,7 +129,7 @@ func (c *PMDLinterConverter) ConvertRules(ctx context.Context, rules []schema.Us } // convertSingleRule converts a single rule using LLM -func (c *PMDLinterConverter) convertSingleRule(ctx context.Context, rule schema.UserRule, llmClient *llm.Client) (*PMDRule, error) { +func (c *PMDLinterConverter) convertSingleRule(ctx context.Context, rule schema.UserRule, llmClient *llm.Client) (*pmdRule, error) { systemPrompt := `You are a PMD configuration expert. Convert natural language Java coding rules to PMD rule references. Return ONLY a JSON object (no markdown fences): @@ -189,7 +189,7 @@ Output: return nil, nil } - return &PMDRule{ + return &pmdRule{ Ref: result.RuleRef, Priority: result.Priority, }, nil diff --git a/internal/github/client.go b/internal/github/client.go index 4f2e1f4..90fb3b7 100644 --- a/internal/github/client.go +++ b/internal/github/client.go @@ -24,8 +24,6 @@ type User struct { type OAuthTokenResponse struct { AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - Scope string `json:"scope"` } // NewClient creates a new GitHub API client diff --git a/internal/llm/client.go b/internal/llm/client.go index 757403b..b386866 100644 --- a/internal/llm/client.go +++ b/internal/llm/client.go @@ -40,20 +40,6 @@ func WithModel(model string) ClientOption { } } -// WithMaxTokens sets the maximum tokens for responses -func WithMaxTokens(maxTokens int) ClientOption { - return func(c *Client) { - c.maxTokens = maxTokens - } -} - -// WithTemperature sets the sampling temperature -func WithTemperature(temperature float64) ClientOption { - return func(c *Client) { - c.temperature = temperature - } -} - // WithTimeout sets the HTTP client timeout func WithTimeout(timeout time.Duration) ClientOption { return func(c *Client) { @@ -61,13 +47,6 @@ func WithTimeout(timeout time.Duration) ClientOption { } } -// WithVerbose enables verbose logging -func WithVerbose(verbose bool) ClientOption { - return func(c *Client) { - c.verbose = verbose - } -} - // NewClient creates a new OpenAI API client func NewClient(apiKey string, opts ...ClientOption) *Client { if apiKey == "" { @@ -203,18 +182,3 @@ func (c *Client) Complete(ctx context.Context, systemPrompt, userPrompt string) return content, nil } - -// CheckAvailability checks if the OpenAI API is available -func (c *Client) CheckAvailability(ctx context.Context) error { - if c.apiKey == "" { - return fmt.Errorf("OPENAI_API_KEY environment variable not set") - } - - // Simple test request - _, err := c.Complete(ctx, "You are a test assistant.", "Say 'OK'") - if err != nil { - return fmt.Errorf("OpenAI API not available: %w", err) - } - - return nil -} diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 6f9ff20..35bb217 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "net/http" "os" "path/filepath" "strings" @@ -78,10 +77,8 @@ func ConvertPolicyWithLLM(userPolicyPath, codePolicyPath string) error { } // Server is a MCP (Model Context Protocol) server. -// It communicates via JSON-RPC over stdio or HTTP. +// It communicates via JSON-RPC over stdio. type Server struct { - host string - port int configPath string userPolicy *schema.UserPolicy codePolicy *schema.CodePolicy @@ -89,17 +86,15 @@ type Server struct { } // NewServer creates a new MCP server instance. -func NewServer(host string, port int, configPath string) *Server { +func NewServer(configPath string) *Server { return &Server{ - host: host, - port: port, configPath: configPath, loader: policy.NewLoader(false), // verbose = false for MCP } } // Start starts the MCP server. -// It communicates via JSON-RPC over stdio or HTTP. +// It communicates via JSON-RPC over stdio. func (s *Server) Start() error { // Determine the directory to look for policy files var dir string @@ -180,138 +175,17 @@ func (s *Server) Start() error { } } - if s.port > 0 { - return s.startHTTPServer() - } - fmt.Fprintln(os.Stderr, "Symphony MCP server started (stdio mode)") - fmt.Fprintf(os.Stderr, "Listening on: %s:%d\n", s.host, s.port) fmt.Fprintln(os.Stderr, "Available tools: query_conventions, validate_code") // Use official MCP go-sdk for stdio to ensure spec-compliant framing and lifecycle return s.runStdioWithSDK(context.Background()) } -// startHTTPServer starts HTTP server for JSON-RPC. -func (s *Server) startHTTPServer() error { - addr := fmt.Sprintf("%s:%d", s.host, s.port) - - mux := http.NewServeMux() - mux.HandleFunc("/", s.handleHTTPRequest) - mux.HandleFunc("/health", s.handleHealthCheck) - - server := &http.Server{ - Addr: addr, - Handler: mux, - ReadHeaderTimeout: 10 * time.Second, - ReadTimeout: 30 * time.Second, - WriteTimeout: 30 * time.Second, - IdleTimeout: 60 * time.Second, - MaxHeaderBytes: 1 << 20, // 1MB - } - - fmt.Fprintf(os.Stderr, "Symphony MCP server started (HTTP mode)\n") - fmt.Fprintf(os.Stderr, "Listening on: http://%s\n", addr) - fmt.Fprintf(os.Stderr, "Available tools: query_conventions, validate_code\n") - - return server.ListenAndServe() -} - -// handleHealthCheck handles health check requests. -func (s *Server) handleHealthCheck(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(map[string]interface{}{ - "status": "ok", - "version": "1.0.0", - }); err != nil { - // Log error but don't fail - headers already sent - _ = err - } -} - -// handleHTTPRequest handles HTTP JSON-RPC requests. -func (s *Server) handleHTTPRequest(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - return - } - - var req JSONRPCRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - _ = json.NewEncoder(w).Encode(JSONRPCResponse{ - JSONRPC: "2.0", - Error: &RPCError{ - Code: -32700, - Message: "parse error", - }, - ID: nil, - }) - return - } - - var result interface{} - var rpcErr *RPCError - - switch req.Method { - case "initialize": - result, rpcErr = s.handleInitialize(req.Params) - case "initialized": - // Notification - no response needed, but we'll send empty result - result = nil - case "tools/list": - result, rpcErr = s.handleToolsList(req.Params) - case "tools/call": - result, rpcErr = s.handleToolsCall(req.Params) - case "query_conventions": - result, rpcErr = s.handleQueryConventions(req.Params) - case "validate_code": - result, rpcErr = s.handleValidateCode(req.Params) - default: - rpcErr = &RPCError{ - Code: -32601, - Message: fmt.Sprintf("method not found: %s", req.Method), - } - } - - resp := JSONRPCResponse{ - JSONRPC: "2.0", - Result: result, - Error: rpcErr, - ID: req.ID, - } - - w.Header().Set("Content-Type", "application/json") - if rpcErr != nil { - w.WriteHeader(http.StatusBadRequest) - } else { - w.WriteHeader(http.StatusOK) - } - _ = json.NewEncoder(w).Encode(resp) -} - -// JSONRPCRequest is a JSON-RPC 2.0 request. -type JSONRPCRequest struct { - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - Params map[string]interface{} `json:"params"` - ID interface{} `json:"id"` -} - -// JSONRPCResponse is a JSON-RPC 2.0 response. -type JSONRPCResponse struct { - JSONRPC string `json:"jsonrpc"` - Result interface{} `json:"result,omitempty"` - Error *RPCError `json:"error,omitempty"` - ID interface{} `json:"id"` -} - -// RPCError is a JSON-RPC error. +// RPCError is an error type used for internal error handling. type RPCError struct { - Code int `json:"code"` - Message string `json:"message"` + Code int + Message string } // QueryConventionsInput represents the input schema for the query_conventions tool (go-sdk). @@ -724,144 +598,6 @@ func (s *Server) getValidationPolicy() (*schema.CodePolicy, error) { return nil, fmt.Errorf("no code policy loaded - validation requires code policy") } -// handleInitialize handles MCP initialize request. -// This is the first request from the client to establish protocol version and capabilities. -func (s *Server) handleInitialize(params map[string]interface{}) (interface{}, *RPCError) { - return map[string]interface{}{ - "protocolVersion": "2024-11-05", - "capabilities": map[string]interface{}{ - "tools": map[string]interface{}{}, - }, - "serverInfo": map[string]interface{}{ - "name": "symphony", - "version": "1.0.0", - }, - "instructions": `Symphony Code Convention Enforcer - -MANDATORY WORKFLOW for all coding tasks: - -STEP 1 [BEFORE CODE]: Query Conventions -→ Call query_conventions tool FIRST before writing any code -→ Filter by category (security, style, architecture, etc.) -→ Filter by language/files you'll work with -→ Review and understand the conventions - -STEP 2 [DURING CODE]: Write Code -→ Implement your code following the conventions from Step 1 -→ Keep security, style, and architecture guidelines in mind - -STEP 3 [AFTER CODE]: Validate Code -→ Call validate_code tool LAST after completing implementation -→ MANDATORY: Must validate before marking task complete -→ Fix any violations found and re-validate -→ Only proceed when validation passes with no errors - -This 3-step workflow ensures all code meets project standards. Never skip steps 1 and 3.`, - }, nil -} - -// handleToolsList handles tools/list request. -// Returns the list of available tools that clients can call. -func (s *Server) handleToolsList(params map[string]interface{}) (interface{}, *RPCError) { - tools := []map[string]interface{}{ - { - "name": "query_conventions", - "description": `⚠️ CALL THIS FIRST - BEFORE WRITING ANY CODE ⚠️ - -This tool is MANDATORY before you start coding. Query project conventions to understand what rules your code must follow. - -Usage: -- Filter by category: security, style, error_handling, architecture, performance, testing, documentation -- Filter by languages: javascript, typescript, python, go, java, etc. - -Example: Before adding a login feature, call query_conventions(category="security") first. - -DO NOT write code before calling this tool. Violations will be caught by validate_code later.`, - "inputSchema": map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{ - "category": map[string]interface{}{ - "type": "string", - "description": "Filter by category (optional). Leave empty or use 'all' to fetch all categories. Options: security, style, documentation, error_handling, architecture, performance, testing", - }, - "languages": map[string]interface{}{ - "type": "array", - "items": map[string]string{"type": "string"}, - "description": "Programming languages to filter by (optional). Leave empty to get conventions for all languages. Examples: go, javascript, typescript, python, java", - }, - }, - }, - }, - { - "name": "validate_code", - "description": `[STEP 3 - ALWAYS CALL LAST] Validate your git changes against all project conventions. - -CRITICAL WORKFLOW: -1. Call this tool AFTER you have written or modified code -2. MANDATORY: Always validate before considering the task complete -3. If violations are found, fix them and validate again -4. Only mark the task as done after validation passes with no errors - -This tool automatically validates: -- All STAGED changes (git add) -- All UNSTAGED changes (modified but not staged) -- Only checks the ADDED/MODIFIED lines in your diffs (not entire files) - -This is the final quality gate. Never skip this validation step. - -The tool will check your changes for: -- Security violations (hardcoded secrets, SQL injection, XSS, etc.) -- Style violations (formatting, naming, documentation) -- Architecture violations (separation of concerns, patterns) -- Error handling violations (missing error checks, empty catch blocks) - -If violations are found, you MUST fix them before proceeding.`, - "inputSchema": map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{ - "role": map[string]interface{}{ - "type": "string", - "description": "RBAC role for validation (optional)", - }, - }, - }, - }, - } - - return map[string]interface{}{ - "tools": tools, - }, nil -} - -// handleToolsCall handles tools/call request. -// This routes tool calls to the appropriate handler based on tool name. -func (s *Server) handleToolsCall(params map[string]interface{}) (interface{}, *RPCError) { - toolName, ok := params["name"].(string) - if !ok { - return nil, &RPCError{ - Code: -32602, - Message: "tool name is required", - } - } - - arguments, ok := params["arguments"].(map[string]interface{}) - if !ok { - arguments = make(map[string]interface{}) - } - - switch toolName { - case "query_conventions": - return s.handleQueryConventions(arguments) - case "validate_code": - return s.handleValidateCode(arguments) - default: - return nil, &RPCError{ - Code: -32601, - Message: fmt.Sprintf("unknown tool: %s", toolName), - } - } -} - // needsConversion checks if user policy needs to be converted to code policy. // Returns true if: // 1. code-policy.json doesn't exist, OR diff --git a/internal/mcp/server_test.go b/internal/mcp/server_test.go index 30093a6..a8686e6 100644 --- a/internal/mcp/server_test.go +++ b/internal/mcp/server_test.go @@ -1,7 +1,6 @@ package mcp import ( - "encoding/json" "os" "path/filepath" "testing" @@ -209,52 +208,3 @@ func TestQueryConventions(t *testing.T) { assert.NotContains(t, text, "DOC-001") }) } - -func TestFilterConventionsWithDefaults(t *testing.T) { - // Create test server with user policy that has defaults - userPolicy := &UserPolicyForTest{ - Defaults: DefaultsForTest{ - Severity: "error", - }, - Rules: []UserRuleForTest{ - { - ID: "TEST-001", - Say: "Test rule without severity", - Category: "testing", - Languages: []string{"go"}, - // No severity or message specified - }, - { - ID: "TEST-002", - Say: "Test rule with severity", - Category: "testing", - Languages: []string{"go"}, - Severity: "warning", - Message: "Custom message", - }, - }, - } - - // Convert to JSON and back to ensure proper structure - data, _ := json.Marshal(userPolicy) - t.Logf("User policy: %s", string(data)) -} - -// Test helper types to match schema.UserPolicy structure -type UserPolicyForTest struct { - Defaults DefaultsForTest `json:"defaults"` - Rules []UserRuleForTest `json:"rules"` -} - -type DefaultsForTest struct { - Severity string `json:"severity"` -} - -type UserRuleForTest struct { - ID string `json:"id"` - Say string `json:"say"` - Category string `json:"category"` - Languages []string `json:"languages"` - Severity string `json:"severity,omitempty"` - Message string `json:"message,omitempty"` -} diff --git a/internal/policy/loader.go b/internal/policy/loader.go index c0205d6..a7edb09 100644 --- a/internal/policy/loader.go +++ b/internal/policy/loader.go @@ -10,14 +10,11 @@ import ( // Loader handles loading policy files type Loader struct { - verbose bool } // NewLoader creates a new policy loader func NewLoader(verbose bool) *Loader { - return &Loader{ - verbose: verbose, - } + return &Loader{} } // LoadUserPolicy loads user-friendly policy (A schema) @@ -49,17 +46,3 @@ func (l *Loader) LoadCodePolicy(path string) (*schema.CodePolicy, error) { return &policy, nil } - -// SaveCodePolicy saves policy to file -func (l *Loader) SaveCodePolicy(path string, policy *schema.CodePolicy) error { - data, err := json.MarshalIndent(policy, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal policy: %w", err) - } - - if err := os.WriteFile(path, data, 0644); err != nil { - return fmt.Errorf("failed to write policy file: %w", err) - } - - return nil -} diff --git a/internal/policy/templates.go b/internal/policy/templates.go index 39e0f7e..4995bfc 100644 --- a/internal/policy/templates.go +++ b/internal/policy/templates.go @@ -99,8 +99,3 @@ func GetTemplate(name string) (*schema.UserPolicy, error) { return &policy, nil } - -// ApplyTemplate applies a template to create a new policy -func ApplyTemplate(templateName string) (*schema.UserPolicy, error) { - return GetTemplate(templateName) -} diff --git a/internal/roles/roles.go b/internal/roles/roles.go index 75bd1dc..298e5d6 100644 --- a/internal/roles/roles.go +++ b/internal/roles/roles.go @@ -86,26 +86,6 @@ func GetUserRole(username string) (string, error) { return "none", nil } -// IsAdmin checks if a user is in the "admin" role -// NOTE: This is deprecated. Use RBAC canEditPolicy permission instead. -func IsAdmin(username string) (bool, error) { - roles, err := LoadRoles() - if err != nil { - return false, err - } - - // Check if admin role exists and contains the user - if adminUsers, exists := roles["admin"]; exists { - for _, admin := range adminUsers { - if admin == username { - return true, nil - } - } - } - - return false, nil -} - // RolesExists checks if roles.json file exists func RolesExists() (bool, error) { rolesPath, err := GetRolesPath() diff --git a/internal/validator/engine.go b/internal/validator/engine.go deleted file mode 100644 index 3a646c1..0000000 --- a/internal/validator/engine.go +++ /dev/null @@ -1,72 +0,0 @@ -package validator - -import ( - "context" - - "github.com/DevSymphony/sym-cli/pkg/schema" -) - -// Engine represents a validation engine that can check code against rules -type Engine interface { - // Name returns the engine name (e.g., "eslint", "llm-validator", "checkstyle") - Name() string - - // CanHandle checks if this engine can handle the given rule - CanHandle(rule schema.PolicyRule) bool - - // Returns violations found - Execute(ctx context.Context, files []string, rules []schema.PolicyRule) ([]Violation, error) -} - -// EngineRegistry manages available validation engines -type EngineRegistry struct { - engines map[string]Engine -} - -// NewEngineRegistry creates a new engine registry -func NewEngineRegistry() *EngineRegistry { - return &EngineRegistry{ - engines: make(map[string]Engine), - } -} - -// Register registers a validation engine -func (r *EngineRegistry) Register(engine Engine) { - r.engines[engine.Name()] = engine -} - -// Get retrieves an engine by name -func (r *EngineRegistry) Get(name string) (Engine, bool) { - engine, ok := r.engines[name] - return engine, ok -} - -// GetEngineForRule finds the appropriate engine for a rule -func (r *EngineRegistry) GetEngineForRule(rule schema.PolicyRule) Engine { - // Check if rule specifies an engine - if engineName, ok := rule.Check["engine"].(string); ok { - if engine, exists := r.engines[engineName]; exists { - if engine.CanHandle(rule) { - return engine - } - } - } - - // Fallback: find any engine that can handle this rule - for _, engine := range r.engines { - if engine.CanHandle(rule) { - return engine - } - } - - return nil -} - -// ListEngines returns all registered engines -func (r *EngineRegistry) ListEngines() []string { - names := make([]string, 0, len(r.engines)) - for name := range r.engines { - names = append(names, name) - } - return names -} \ No newline at end of file diff --git a/internal/validator/validator.go b/internal/validator/validator.go index 9ebadd6..6792412 100644 --- a/internal/validator/validator.go +++ b/internal/validator/validator.go @@ -33,12 +33,6 @@ type Violation struct { ExecutionMs int64 // execution time in milliseconds } -// Result represents validation result -type Result struct { - Violations []Violation - Passed bool -} - // Validator validates code against policy using adapters directly // This replaces the old engine-based architecture type Validator struct { @@ -81,90 +75,6 @@ func (v *Validator) SetLLMClient(client *llm.Client) { v.llmClient = client } -// Validate validates the given path using adapters directly -func (v *Validator) Validate(path string) (*Result, error) { - if v.policy == nil { - return nil, fmt.Errorf("policy is not loaded") - } - - result := &Result{ - Violations: make([]Violation, 0), - Passed: true, - } - - if v.verbose { - fmt.Printf("🔍 Validating %s against %d rule(s)...\n", path, len(v.policy.Rules)) - } - - // Check RBAC permissions if enabled - if err := v.checkRBAC(path, result); err != nil { - if v.verbose { - fmt.Printf("⚠️ RBAC check error: %v\n", err) - } - } - - // Validate each enabled rule - for _, rule := range v.policy.Rules { - if !rule.Enabled { - continue - } - - engineName := getEngineName(rule) - if engineName == "" { - if v.verbose { - fmt.Printf("⚠️ Rule %s has no engine specified, skipping\n", rule.ID) - } - continue - } - - // Get files that match this rule's selector - files, err := v.selectFilesForRule(path, &rule) - if err != nil { - fmt.Printf("⚠️ Failed to select files for rule %s: %v\n", rule.ID, err) - continue - } - - if len(files) == 0 { - if v.verbose { - fmt.Printf(" Rule %s: no matching files\n", rule.ID) - } - continue - } - - if v.verbose { - fmt.Printf(" Rule %s (%s): checking %d file(s)...\n", rule.ID, engineName, len(files)) - } - - // Execute validation based on engine type - violations, err := v.executeRule(engineName, rule, files) - if err != nil { - fmt.Printf("⚠️ Validation failed for rule %s: %v\n", rule.ID, err) - continue - } - - if len(violations) > 0 { - result.Violations = append(result.Violations, violations...) - if v.verbose { - fmt.Printf(" ❌ Found %d violation(s)\n", len(violations)) - } - } else if v.verbose { - fmt.Printf(" ✓ Passed\n") - } - } - - result.Passed = len(result.Violations) == 0 - - if v.verbose { - if result.Passed { - fmt.Printf("\n✅ Validation passed: no violations found\n") - } else { - fmt.Printf("\n❌ Validation failed: %d violation(s) found\n", len(result.Violations)) - } - } - - return result, nil -} - // executeRule executes a rule using the appropriate adapter func (v *Validator) executeRule(engineName string, rule schema.PolicyRule, files []string) ([]Violation, error) { // Special case: LLM validator @@ -429,87 +339,6 @@ func getEngineName(rule schema.PolicyRule) string { return "" } -// checkRBAC checks RBAC permissions -func (v *Validator) checkRBAC(path string, result *Result) error { - if v.policy.Enforce.RBACConfig == nil || !v.policy.Enforce.RBACConfig.Enabled { - return nil - } - - username, err := git.GetCurrentUser() - if err != nil { - return err - } - - if v.verbose { - fmt.Printf("🔐 Checking RBAC permissions for user: %s\n", username) - } - - fileInfo, err := os.Stat(path) - if err == nil && !fileInfo.IsDir() { - rbacResult, err := roles.ValidateFilePermissions(username, []string{path}) - if err != nil { - return err - } - - if !rbacResult.Allowed { - for _, deniedFile := range rbacResult.DeniedFiles { - result.Violations = append(result.Violations, Violation{ - RuleID: "rbac-permission-denied", - Severity: "error", - Message: fmt.Sprintf("User '%s' does not have permission to modify this file", username), - File: deniedFile, - Line: 0, - Column: 0, - }) - } - result.Passed = false - - if v.verbose { - fmt.Printf("❌ RBAC: %d file(s) denied for user %s\n", len(rbacResult.DeniedFiles), username) - } - } else if v.verbose { - fmt.Printf("✓ RBAC: User %s has permission to modify all files\n", username) - } - } - - return nil -} - -// selectFilesForRule selects files that match the rule's selector -func (v *Validator) selectFilesForRule(basePath string, rule *schema.PolicyRule) ([]string, error) { - fileInfo, err := os.Stat(basePath) - if err != nil { - return nil, err - } - - if !fileInfo.IsDir() { - // Single file - check if it matches selector - if rule.When != nil { - lang := GetLanguageFromFile(basePath) - if len(rule.When.Languages) > 0 { - matched := false - for _, ruleLang := range rule.When.Languages { - if ruleLang == lang { - matched = true - break - } - } - if !matched { - return []string{}, nil - } - } - } - return []string{basePath}, nil - } - - // Directory - use selector to find files - if rule.When == nil { - return v.selector.SelectFiles(nil) - } - - return v.selector.SelectFiles(rule.When) -} - // ValidateChanges validates git changes using adapters directly func (v *Validator) ValidateChanges(ctx context.Context, changes []GitChange) (*ValidationResult, error) { if v.policy == nil { @@ -713,13 +542,3 @@ func (v *Validator) Close() error { } return nil } - -// CanAutoFix checks if violations can be auto-fixed -func (v *Result) CanAutoFix() bool { - return false -} - -// AutoFix attempts to automatically fix violations -func (v *Validator) AutoFix(result *Result) error { - return fmt.Errorf("auto-fix not implemented yet") -} diff --git a/npm/README.md b/npm/README.md index 26c9526..fa0588f 100644 --- a/npm/README.md +++ b/npm/README.md @@ -2,29 +2,24 @@ LLM-friendly convention linter for AI coding tools. -## Installation - -### One-line MCP Setup - -```bash -claude mcp add symphony npx @dev-symphony/sym@latest mcp -``` - -### Direct Installation +## Quick Start ```bash +# 1. Install npm install -g @dev-symphony/sym + +# 2. Initialize (GitHub OAuth login + MCP auto-setup) +sym login +sym init ``` -## Usage +> **Note**: `OPENAI_API_KEY` environment variable is required for LLM-based convention conversion. -### MCP Configuration +## MCP Configuration -Add to your MCP config file: +MCP is auto-configured during `sym init`. -- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` -- Windows: `%APPDATA%/Claude/claude_desktop_config.json` -- Linux: `~/.config/Claude/claude_desktop_config.json` +For manual setup: ```json { diff --git a/npm/package.json b/npm/package.json index 378c497..7d5bb41 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "@dev-symphony/sym", - "version": "0.1.4", + "version": "0.1.5", "description": "Symphony - LLM-friendly convention linter for AI coding assistants", "keywords": [ "mcp", diff --git a/scripts/build.sh b/scripts/build.sh deleted file mode 100644 index ff9883c..0000000 --- a/scripts/build.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash -# Symphony Multi-Platform Build Script - -set -e - -# Set UTF-8 encoding for proper emoji display -export LC_ALL="${LC_ALL:-en_US.UTF-8}" -export LANG="${LANG:-en_US.UTF-8}" - -# Ensure terminal supports UTF-8 -if [ -z "$LC_ALL" ] && [ -z "$LANG" ]; then - export LC_ALL=C.UTF-8 - export LANG=C.UTF-8 -fi - -VERSION=${1:-"1.0.0"} -OUTPUT_DIR="dist" - -echo "🎵 Building Symphony v${VERSION}" -echo "" - -# Clean previous builds -echo "🧹 Cleaning previous builds..." -rm -rf ${OUTPUT_DIR} -mkdir -p ${OUTPUT_DIR} - -# Build CSS first -echo "🎨 Building Tailwind CSS..." -npm run build:css - -# Build for each platform -echo "" -echo "📦 Building binaries..." - -# Windows AMD64 -echo " ⚙️ Building for Windows (amd64)..." -GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o ${OUTPUT_DIR}/symphony-windows-amd64.exe . - -# macOS AMD64 -echo " ⚙️ Building for macOS (amd64)..." -GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o ${OUTPUT_DIR}/symphony-darwin-amd64 . - -# macOS ARM64 (Apple Silicon) -echo " ⚙️ Building for macOS (arm64)..." -GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -o ${OUTPUT_DIR}/symphony-darwin-arm64 . - -# Linux AMD64 -echo " ⚙️ Building for Linux (amd64)..." -GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o ${OUTPUT_DIR}/symphony-linux-amd64 . - -# Linux ARM64 -echo " ⚙️ Building for Linux (arm64)..." -GOOS=linux GOARCH=arm64 go build -ldflags "-s -w" -o ${OUTPUT_DIR}/symphony-linux-arm64 . - -echo "" -echo "✅ Build complete!" -echo "" -echo "📂 Output directory: ${OUTPUT_DIR}/" -ls -lh ${OUTPUT_DIR}/ diff --git a/scripts/installers/install-unix.sh b/scripts/installers/install-unix.sh deleted file mode 100644 index f6ec16a..0000000 --- a/scripts/installers/install-unix.sh +++ /dev/null @@ -1,197 +0,0 @@ -#!/bin/bash -# Symphony macOS/Linux Installer - -set -e - -# Set UTF-8 encoding for proper emoji and Korean display -export LC_ALL="${LC_ALL:-en_US.UTF-8}" -export LANG="${LANG:-en_US.UTF-8}" -export LANGUAGE="${LANGUAGE:-en_US:en}" - -# Ensure terminal supports UTF-8 -if [ -z "$LC_ALL" ] && [ -z "$LANG" ]; then - export LC_ALL=C.UTF-8 - export LANG=C.UTF-8 -fi - -BLUE='\033[0;34m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -echo -e "${BLUE}🎵 Symphony Installer${NC}" -echo "" - -# Detect OS and architecture -OS=$(uname -s | tr '[:upper:]' '[:lower:]') -ARCH=$(uname -m) - -case "$ARCH" in - x86_64) - ARCH="amd64" - ;; - arm64|aarch64) - ARCH="arm64" - ;; - *) - echo -e "${RED}❌ Unsupported architecture: $ARCH${NC}" - exit 1 - ;; -esac - -BINARY_NAME="symphony-${OS}-${ARCH}" - -echo -e "${GREEN}✓ Detected: $OS ($ARCH)${NC}" -echo "" - -# Check if binary exists -if [ ! -f "$BINARY_NAME" ]; then - echo -e "${RED}❌ Error: $BINARY_NAME not found in current directory${NC}" - echo -e "${YELLOW} Please run this script from the dist/ directory${NC}" - exit 1 -fi - -# Determine installation method -if [ "$EUID" -eq 0 ] || sudo -n true 2>/dev/null; then - # Running as root or can sudo - INSTALL_DIR="/usr/local/bin" - USE_SUDO=true - echo -e "${GREEN}✓ Will install to system directory: $INSTALL_DIR${NC}" -else - # Install to user directory - INSTALL_DIR="$HOME/.local/bin" - USE_SUDO=false - echo -e "${YELLOW}⚠ Installing to user directory: $INSTALL_DIR${NC}" - echo -e "${YELLOW} (Run with sudo to install system-wide)${NC}" -fi - -echo "" -echo -e "${BLUE}📂 Installation directory: $INSTALL_DIR${NC}" -echo "" - -# Ask for confirmation -read -p "Continue with installation? (Y/n) " -n 1 -r -echo -if [[ $REPLY =~ ^[Nn]$ ]]; then - echo -e "${RED}❌ Installation cancelled${NC}" - exit 1 -fi - -# Create installation directory -echo -e "${BLUE}📁 Creating installation directory...${NC}" -if [ "$USE_SUDO" = true ]; then - sudo mkdir -p "$INSTALL_DIR" -else - mkdir -p "$INSTALL_DIR" -fi - -# Copy binary -echo -e "${BLUE}📋 Installing Symphony binary...${NC}" -if [ "$USE_SUDO" = true ]; then - sudo cp "$BINARY_NAME" "$INSTALL_DIR/symphony" - sudo chmod 755 "$INSTALL_DIR/symphony" -else - cp "$BINARY_NAME" "$INSTALL_DIR/symphony" - chmod 755 "$INSTALL_DIR/symphony" -fi - -# Configure PATH -echo "" -echo -e "${BLUE}🔧 Configuring PATH...${NC}" - -PATH_CONFIGURED=false - -# Check if already in PATH -if echo "$PATH" | grep -q "$INSTALL_DIR"; then - echo -e "${GREEN}✓ $INSTALL_DIR is already in PATH${NC}" - PATH_CONFIGURED=true -elif [ "$INSTALL_DIR" = "/usr/local/bin" ]; then - echo -e "${GREEN}✓ /usr/local/bin is typically in PATH by default${NC}" - PATH_CONFIGURED=true -else - # Need to add to PATH - echo -e "${YELLOW}⚠ $INSTALL_DIR is not in PATH${NC}" - echo "" - read -p "Add to PATH automatically? (Y/n) " -n 1 -r - echo - - if [[ ! $REPLY =~ ^[Nn]$ ]]; then - # Detect shell - SHELL_NAME=$(basename "$SHELL") - case "$SHELL_NAME" in - bash) - SHELL_RC="$HOME/.bashrc" - ;; - zsh) - SHELL_RC="$HOME/.zshrc" - ;; - fish) - SHELL_RC="$HOME/.config/fish/config.fish" - ;; - *) - SHELL_RC="$HOME/.profile" - ;; - esac - - echo -e "${BLUE}📝 Adding to $SHELL_RC...${NC}" - - # Add to shell config - if [ "$SHELL_NAME" = "fish" ]; then - echo "" >> "$SHELL_RC" - echo "# Symphony" >> "$SHELL_RC" - echo "set -gx PATH $INSTALL_DIR \$PATH" >> "$SHELL_RC" - else - echo "" >> "$SHELL_RC" - echo "# Symphony" >> "$SHELL_RC" - echo "export PATH=\"$INSTALL_DIR:\$PATH\"" >> "$SHELL_RC" - fi - - echo -e "${GREEN}✓ Added to $SHELL_RC${NC}" - echo -e "${YELLOW}⚠ Note: Run 'source $SHELL_RC' or restart your terminal${NC}" - PATH_CONFIGURED=true - - # Update current session - export PATH="$INSTALL_DIR:$PATH" - else - echo -e "${YELLOW}⊘ Skipped PATH configuration${NC}" - echo -e "${YELLOW} To use Symphony, either:${NC}" - echo -e "${YELLOW} 1. Run: $INSTALL_DIR/symphony${NC}" - echo -e "${YELLOW} 2. Add '$INSTALL_DIR' to your PATH manually${NC}" - fi -fi - -# Verify installation -echo "" -echo -e "${BLUE}🔍 Verifying installation...${NC}" - -if [ -f "$INSTALL_DIR/symphony" ]; then - echo -e "${GREEN}✓ Binary installed successfully${NC}" - - # Try to run symphony - if "$INSTALL_DIR/symphony" whoami --help &>/dev/null; then - echo -e "${GREEN}✓ Symphony is executable${NC}" - else - echo -e "${YELLOW}⚠ Warning: Could not execute symphony${NC}" - fi -else - echo -e "${RED}❌ Installation failed: binary not found${NC}" - exit 1 -fi - -echo "" -echo -e "${GREEN}✅ Symphony installed successfully!${NC}" -echo "" -echo -e "${BLUE}📖 Next steps:${NC}" -echo -e " ${NC}1. Configure: ${GREEN}symphony config${NC}" -echo -e " ${NC}2. Login: ${GREEN}symphony login${NC}" -echo -e " ${NC}3. Init repo: ${GREEN}symphony init${NC}" -echo -e " ${NC}4. Dashboard: ${GREEN}symphony dashboard${NC}" -echo "" -echo -e "${BLUE}📚 Documentation: See README.md${NC}" -echo "" - -if [ "$PATH_CONFIGURED" = false ]; then - echo -e "${YELLOW}⚠ Remember to add $INSTALL_DIR to your PATH!${NC}" - echo "" -fi diff --git a/scripts/installers/install-windows.ps1 b/scripts/installers/install-windows.ps1 deleted file mode 100644 index 0b33b05..0000000 --- a/scripts/installers/install-windows.ps1 +++ /dev/null @@ -1,129 +0,0 @@ -# Symphony Windows Installer -# Run with: powershell -ExecutionPolicy Bypass -File install-windows.ps1 - -$ErrorActionPreference = "Stop" - -# Set console encoding to UTF-8 for proper Korean display -$OutputEncoding = [System.Text.Encoding]::UTF8 -[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 -$PSDefaultParameterValues['*:Encoding'] = 'utf8' - -# Set console code page to UTF-8 (65001) -chcp 65001 > $null - -Write-Host "🎵 Symphony Windows Installer" -ForegroundColor Cyan -Write-Host "" - -# Check if running as Administrator -$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) -$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) - -# Determine installation directory -if ($isAdmin) { - $installDir = "C:\Program Files\Symphony" - Write-Host "✓ Running with administrator privileges" -ForegroundColor Green -} else { - $installDir = "$env:LOCALAPPDATA\Symphony" - Write-Host "⚠ Not running as administrator - installing to user directory" -ForegroundColor Yellow -} - -Write-Host "📂 Installation directory: $installDir" -ForegroundColor Cyan -Write-Host "" - -# Ask for confirmation -$confirmation = Read-Host "Continue with installation? (Y/n)" -if ($confirmation -eq 'n' -or $confirmation -eq 'N') { - Write-Host "❌ Installation cancelled" -ForegroundColor Red - exit 1 -} - -# Create installation directory -Write-Host "📁 Creating installation directory..." -New-Item -ItemType Directory -Force -Path $installDir | Out-Null - -# Copy binary -$binaryName = "symphony-windows-amd64.exe" -$targetBinary = "$installDir\symphony.exe" - -if (Test-Path $binaryName) { - Write-Host "📋 Copying Symphony binary..." - Copy-Item -Path $binaryName -Destination $targetBinary -Force -} else { - Write-Host "❌ Error: $binaryName not found in current directory" -ForegroundColor Red - Write-Host " Please run this script from the dist/ directory" -ForegroundColor Yellow - exit 1 -} - -# Add to PATH -Write-Host "" -Write-Host "🔧 Configuring PATH environment variable..." -ForegroundColor Cyan - -$addToPath = Read-Host "Add Symphony to PATH? This allows you to run 'symphony' from anywhere (Y/n)" -if ($addToPath -ne 'n' -and $addToPath -ne 'N') { - try { - if ($isAdmin) { - # System PATH (requires admin) - $envTarget = [System.EnvironmentVariableTarget]::Machine - $pathScope = "system" - } else { - # User PATH - $envTarget = [System.EnvironmentVariableTarget]::User - $pathScope = "user" - } - - $currentPath = [Environment]::GetEnvironmentVariable("Path", $envTarget) - - if ($currentPath -notlike "*$installDir*") { - $newPath = "$currentPath;$installDir" - [Environment]::SetEnvironmentVariable("Path", $newPath, $envTarget) - Write-Host "✓ Added to $pathScope PATH" -ForegroundColor Green - - # Update current session PATH - $env:Path = "$env:Path;$installDir" - - Write-Host "" - Write-Host "⚠ Note: You may need to restart your terminal for PATH changes to take effect" -ForegroundColor Yellow - } else { - Write-Host "✓ Already in PATH" -ForegroundColor Green - } - } catch { - Write-Host "❌ Failed to update PATH: $_" -ForegroundColor Red - Write-Host " You can manually add '$installDir' to your PATH" -ForegroundColor Yellow - } -} else { - Write-Host "⊘ Skipped PATH configuration" -ForegroundColor Yellow - Write-Host " To use Symphony, either:" -ForegroundColor Yellow - Write-Host " 1. Run: $targetBinary" -ForegroundColor Yellow - Write-Host " 2. Manually add '$installDir' to your PATH" -ForegroundColor Yellow -} - -# Verify installation -Write-Host "" -Write-Host "🔍 Verifying installation..." -ForegroundColor Cyan - -if (Test-Path $targetBinary) { - Write-Host "✓ Binary installed successfully" -ForegroundColor Green - - # Try to run symphony version check - try { - $version = & $targetBinary whoami --help 2>&1 - Write-Host "✓ Symphony is executable" -ForegroundColor Green - } catch { - Write-Host "⚠ Warning: Could not execute symphony" -ForegroundColor Yellow - } -} else { - Write-Host "❌ Installation failed: binary not found" -ForegroundColor Red - exit 1 -} - -Write-Host "" -Write-Host "✅ Symphony installed successfully!" -ForegroundColor Green -Write-Host "" -Write-Host "📖 Next steps:" -ForegroundColor Cyan -Write-Host " 1. Configure: symphony config" -ForegroundColor White -Write-Host " 2. Login: symphony login" -ForegroundColor White -Write-Host " 3. Init repo: symphony init" -ForegroundColor White -Write-Host " 4. Dashboard: symphony dashboard" -ForegroundColor White -Write-Host "" -Write-Host "📚 Documentation: See README.md" -ForegroundColor Cyan -Write-Host "" diff --git a/scripts/installers/uninstall-unix.sh b/scripts/installers/uninstall-unix.sh deleted file mode 100644 index 17317c2..0000000 --- a/scripts/installers/uninstall-unix.sh +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/bash -# Symphony macOS/Linux Uninstaller - -set -e - -# Set UTF-8 encoding for proper emoji and Korean display -export LC_ALL="${LC_ALL:-en_US.UTF-8}" -export LANG="${LANG:-en_US.UTF-8}" -export LANGUAGE="${LANGUAGE:-en_US:en}" - -# Ensure terminal supports UTF-8 -if [ -z "$LC_ALL" ] && [ -z "$LANG" ]; then - export LC_ALL=C.UTF-8 - export LANG=C.UTF-8 -fi - -BLUE='\033[0;34m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -echo -e "${BLUE}🎵 Symphony Uninstaller${NC}" -echo "" - -# Check possible installation locations -POSSIBLE_LOCATIONS=( - "/usr/local/bin/symphony" - "$HOME/.local/bin/symphony" -) - -FOUND_LOCATIONS=() -for loc in "${POSSIBLE_LOCATIONS[@]}"; do - if [ -f "$loc" ]; then - FOUND_LOCATIONS+=("$loc") - fi -done - -if [ ${#FOUND_LOCATIONS[@]} -eq 0 ]; then - echo -e "${RED}❌ Symphony installation not found${NC}" - echo "" - echo -e "${YELLOW}Checked locations:${NC}" - for loc in "${POSSIBLE_LOCATIONS[@]}"; do - echo -e "${YELLOW} - $loc${NC}" - done - exit 1 -fi - -echo -e "${BLUE}📂 Found Symphony installations:${NC}" -for loc in "${FOUND_LOCATIONS[@]}"; do - echo -e " ${NC}- $loc" -done -echo "" - -# Ask for confirmation -read -p "Uninstall Symphony? (y/N) " -n 1 -r -echo -if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo -e "${RED}❌ Uninstallation cancelled${NC}" - exit 1 -fi - -# Remove installations -for binary_path in "${FOUND_LOCATIONS[@]}"; do - echo "" - echo -e "${BLUE}🗑️ Removing $binary_path...${NC}" - - if [ -w "$binary_path" ]; then - rm -f "$binary_path" - echo -e "${GREEN}✓ Removed${NC}" - else - # Try with sudo - if sudo rm -f "$binary_path" 2>/dev/null; then - echo -e "${GREEN}✓ Removed (with sudo)${NC}" - else - echo -e "${RED}❌ Failed to remove${NC}" - echo -e "${YELLOW} Try: sudo rm $binary_path${NC}" - fi - fi -done - -# Clean PATH entries from shell configs -echo "" -echo -e "${BLUE}🔧 Cleaning shell configuration files...${NC}" - -SHELL_CONFIGS=( - "$HOME/.bashrc" - "$HOME/.zshrc" - "$HOME/.profile" - "$HOME/.config/fish/config.fish" -) - -for config in "${SHELL_CONFIGS[@]}"; do - if [ -f "$config" ]; then - # Check if file contains Symphony PATH entries - if grep -q "# Symphony" "$config" 2>/dev/null; then - # Create backup - cp "$config" "$config.backup" - - # Remove Symphony lines - sed -i.tmp '/# Symphony/,+1d' "$config" 2>/dev/null || \ - sed -i '' '/# Symphony/,+1d' "$config" 2>/dev/null - - echo -e "${GREEN}✓ Cleaned $(basename $config)${NC}" - fi - fi -done - -# Optional: Remove config files -echo "" -read -p "Remove configuration files in ~/.config/symphony? (y/N) " -n 1 -r -echo - -if [[ $REPLY =~ ^[Yy]$ ]]; then - CONFIG_DIR="$HOME/.config/symphony" - if [ -d "$CONFIG_DIR" ]; then - rm -rf "$CONFIG_DIR" - echo -e "${GREEN}✓ Configuration files removed${NC}" - else - echo -e "${YELLOW}⊘ No configuration files found${NC}" - fi -fi - -echo "" -echo -e "${GREEN}✅ Symphony uninstalled successfully!${NC}" -echo "" -echo -e "${YELLOW}⚠ You may need to restart your terminal or run:${NC}" -echo -e "${YELLOW} source ~/.bashrc (or ~/.zshrc, etc.)${NC}" -echo "" diff --git a/scripts/installers/uninstall-windows.ps1 b/scripts/installers/uninstall-windows.ps1 deleted file mode 100644 index cecf0eb..0000000 --- a/scripts/installers/uninstall-windows.ps1 +++ /dev/null @@ -1,128 +0,0 @@ -# Symphony Windows Uninstaller -# Run with: powershell -ExecutionPolicy Bypass -File uninstall-windows.ps1 - -$ErrorActionPreference = "Stop" - -# Set console encoding to UTF-8 for proper Korean display -$OutputEncoding = [System.Text.Encoding]::UTF8 -[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 -$PSDefaultParameterValues['*:Encoding'] = 'utf8' - -# Set console code page to UTF-8 (65001) -chcp 65001 > $null - -Write-Host "🎵 Symphony Windows Uninstaller" -ForegroundColor Cyan -Write-Host "" - -# Check possible installation locations -$possibleLocations = @( - "C:\Program Files\Symphony", - "$env:LOCALAPPDATA\Symphony" -) - -$foundLocations = @() -foreach ($loc in $possibleLocations) { - if (Test-Path "$loc\symphony.exe") { - $foundLocations += $loc - } -} - -if ($foundLocations.Count -eq 0) { - Write-Host "❌ Symphony installation not found" -ForegroundColor Red - Write-Host "" - Write-Host "Checked locations:" -ForegroundColor Yellow - foreach ($loc in $possibleLocations) { - Write-Host " - $loc" -ForegroundColor Yellow - } - exit 1 -} - -Write-Host "📂 Found Symphony installations:" -ForegroundColor Cyan -foreach ($loc in $foundLocations) { - Write-Host " - $loc" -ForegroundColor White -} -Write-Host "" - -# Ask for confirmation -$confirmation = Read-Host "Uninstall Symphony? (y/N)" -if ($confirmation -ne 'y' -and $confirmation -ne 'Y') { - Write-Host "❌ Uninstallation cancelled" -ForegroundColor Red - exit 1 -} - -# Check if running as Administrator -$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) -$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) - -# Remove installations -foreach ($installDir in $foundLocations) { - Write-Host "" - Write-Host "🗑️ Removing $installDir..." -ForegroundColor Cyan - - try { - Remove-Item -Path $installDir -Recurse -Force - Write-Host "✓ Removed" -ForegroundColor Green - } catch { - if ($installDir -like "C:\Program Files*" -and -not $isAdmin) { - Write-Host "❌ Failed: Administrator privileges required" -ForegroundColor Red - Write-Host " Please run this script as Administrator" -ForegroundColor Yellow - } else { - Write-Host "❌ Failed: $_" -ForegroundColor Red - } - } -} - -# Remove from PATH -Write-Host "" -Write-Host "🔧 Cleaning PATH environment variable..." -ForegroundColor Cyan - -foreach ($installDir in $foundLocations) { - # Try system PATH (requires admin) - if ($isAdmin) { - try { - $envTarget = [System.EnvironmentVariableTarget]::Machine - $currentPath = [Environment]::GetEnvironmentVariable("Path", $envTarget) - - if ($currentPath -like "*$installDir*") { - $newPath = ($currentPath.Split(';') | Where-Object { $_ -ne $installDir }) -join ';' - [Environment]::SetEnvironmentVariable("Path", $newPath, $envTarget) - Write-Host "✓ Removed from system PATH" -ForegroundColor Green - } - } catch { - Write-Host "⚠ Could not update system PATH: $_" -ForegroundColor Yellow - } - } - - # Try user PATH - try { - $envTarget = [System.EnvironmentVariableTarget]::User - $currentPath = [Environment]::GetEnvironmentVariable("Path", $envTarget) - - if ($currentPath -like "*$installDir*") { - $newPath = ($currentPath.Split(';') | Where-Object { $_ -ne $installDir }) -join ';' - [Environment]::SetEnvironmentVariable("Path", $newPath, $envTarget) - Write-Host "✓ Removed from user PATH" -ForegroundColor Green - } - } catch { - Write-Host "⚠ Could not update user PATH: $_" -ForegroundColor Yellow - } -} - -# Optional: Remove config files -Write-Host "" -$removeConfig = Read-Host "Remove configuration files in $env:USERPROFILE\.config\symphony? (y/N)" -if ($removeConfig -eq 'y' -or $removeConfig -eq 'Y') { - $configDir = "$env:USERPROFILE\.config\symphony" - if (Test-Path $configDir) { - Remove-Item -Path $configDir -Recurse -Force - Write-Host "✓ Configuration files removed" -ForegroundColor Green - } else { - Write-Host "⊘ No configuration files found" -ForegroundColor Yellow - } -} - -Write-Host "" -Write-Host "✅ Symphony uninstalled successfully!" -ForegroundColor Green -Write-Host "" -Write-Host "⚠ You may need to restart your terminal" -ForegroundColor Yellow -Write-Host "" diff --git a/scripts/validate-checkstyle.sh b/scripts/validate-checkstyle.sh deleted file mode 100755 index 1f514b3..0000000 --- a/scripts/validate-checkstyle.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# Validates generated Checkstyle configuration - -set -e - -CHECKSTYLE_XML=".sym/checkstyle.xml" - -if [ ! -f "$CHECKSTYLE_XML" ]; then - echo "Error: $CHECKSTYLE_XML not found" - exit 1 -fi - -echo "Validating Checkstyle configuration..." - -# Check if file is valid XML -if ! xmllint --noout "$CHECKSTYLE_XML" 2>/dev/null; then - echo "Error: Invalid XML in $CHECKSTYLE_XML" - exit 1 -fi - -# Check required structure -if ! xmllint --xpath "//module[@name='Checker']" "$CHECKSTYLE_XML" > /dev/null 2>&1; then - echo "Error: Missing Checker module in $CHECKSTYLE_XML" - exit 1 -fi - -# Count modules -MODULE_COUNT=$(xmllint --xpath "count(//module[@name='TreeWalker']/module)" "$CHECKSTYLE_XML") -echo "✓ Valid Checkstyle config with $MODULE_COUNT modules" diff --git a/scripts/validate-eslint.sh b/scripts/validate-eslint.sh deleted file mode 100755 index 2e17f56..0000000 --- a/scripts/validate-eslint.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# Validates generated ESLint configuration - -set -e - -ESLINTRC=".sym/.eslintrc.json" - -if [ ! -f "$ESLINTRC" ]; then - echo "Error: $ESLINTRC not found" - exit 1 -fi - -echo "Validating ESLint configuration..." - -# Check if file is valid JSON -if ! jq empty "$ESLINTRC" 2>/dev/null; then - echo "Error: Invalid JSON in $ESLINTRC" - exit 1 -fi - -# Check required fields -if ! jq -e '.rules' "$ESLINTRC" > /dev/null; then - echo "Error: Missing 'rules' field in $ESLINTRC" - exit 1 -fi - -# Count rules -RULE_COUNT=$(jq '.rules | length' "$ESLINTRC") -echo "✓ Valid ESLint config with $RULE_COUNT rules" - -# Optional: Run eslint --print-config if eslint is installed -if command -v eslint &> /dev/null; then - echo "✓ ESLint validation passed" -else - echo "ℹ eslint not installed, skipping full validation" -fi diff --git a/test_validator.go b/test_validator.go deleted file mode 100644 index 8f2ea97..0000000 --- a/test_validator.go +++ /dev/null @@ -1,103 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/DevSymphony/sym-cli/internal/validator" - "github.com/DevSymphony/sym-cli/pkg/schema" -) - -func main() { - // Create a simple test policy inline with multiple engine types - policy := &schema.CodePolicy{ - Version: "1.0.0", - Rules: []schema.PolicyRule{ - { - ID: "test-max-len", - Enabled: true, - Category: "style", - Severity: "warning", - Desc: "Lines should not exceed 120 characters", - When: &schema.Selector{ - Languages: []string{"javascript"}, - }, - Check: map[string]any{ - "engine": "length", - "scope": "line", - "max": 120, - }, - Message: "Line too long (max 120 characters)", - }, - { - ID: "test-pattern", - Enabled: true, - Category: "security", - Severity: "error", - Desc: "No hardcoded API keys", - When: &schema.Selector{ - Languages: []string{"javascript"}, - }, - Check: map[string]any{ - "engine": "pattern", - "pattern": "sk-[a-zA-Z0-9]{30,}", - "target": "content", - }, - Message: "Hardcoded API key detected", - }, - }, - Enforce: schema.EnforceSettings{ - Stages: []string{"pre-commit"}, - FailOn: []string{"error"}, - }, - } - - fmt.Printf("📋 Testing validator with %d rule(s)\n\n", len(policy.Rules)) - - // Create validator - v := validator.NewValidator(policy, true) - defer func() { - if err := v.Close(); err != nil { - fmt.Fprintf(os.Stderr, "Warning: failed to close validator: %v\n", err) - } - }() - - // Test files - testFiles := []string{ - "tests/e2e/examples/bad-example.js", - "tests/e2e/examples/good-example.js", - } - - for _, file := range testFiles { - // Check if file exists - if _, err := os.Stat(file); os.IsNotExist(err) { - fmt.Printf("⚠️ Skipping %s (file not found)\n\n", file) - continue - } - - fmt.Printf("═══════════════════════════════════════════════\n") - fmt.Printf("Testing: %s\n", file) - fmt.Printf("═══════════════════════════════════════════════\n\n") - - result, err := v.Validate(file) - if err != nil { - fmt.Printf("❌ Validation error: %v\n\n", err) - continue - } - - if result.Passed { - fmt.Printf("\n✅ PASSED: No violations\n\n") - } else { - fmt.Printf("\n❌ FAILED: %d violation(s) found:\n", len(result.Violations)) - for i, violation := range result.Violations { - fmt.Printf("\n%d. [%s] %s\n", i+1, violation.Severity, violation.RuleID) - fmt.Printf(" File: %s:%d:%d\n", violation.File, violation.Line, violation.Column) - fmt.Printf(" Message: %s\n", violation.Message) - } - fmt.Println() - } - } - - fmt.Printf("═══════════════════════════════════════════════\n") - fmt.Printf("✅ Validator test complete!\n") -} diff --git a/testdata/README.md b/testdata/README.md deleted file mode 100644 index 0267f2d..0000000 --- a/testdata/README.md +++ /dev/null @@ -1,111 +0,0 @@ -# Test Data Directory - -This directory contains test data files for integration testing of the validation engines. - -## Directory Structure - -``` -testdata/ -├── javascript/ -│ ├── pattern/ # Pattern matching and naming convention tests -│ ├── length/ # Line, file, and function length tests -│ ├── style/ # Code formatting and style tests -│ └── ast/ # AST-based structural tests -├── typescript/ -│ └── typechecker/ # Type checking tests -└── java/ - ├── pattern/ # Pattern matching and naming convention tests - ├── length/ # Line, method, and parameter length tests - ├── style/ # Code formatting and style tests - └── ast/ # AST-based structural tests -``` - -## Engine Types - -### Pattern Engine -Tests regex-based pattern matching and naming conventions. - -**JavaScript Files:** -- `naming-violations.js` - Snake_case and incorrect naming patterns -- `security-violations.js` - Hardcoded secrets and security issues -- `valid.js` - Correct naming conventions - -**Java Files:** -- `NamingViolations.java` - Invalid class, method, variable names -- `ValidNaming.java` - Correct PascalCase and camelCase usage - -### Length Engine -Tests line length, file length, and parameter count limits. - -**JavaScript Files:** -- `length-violations.js` - Long lines, long functions, too many parameters -- `valid.js` - Proper length constraints - -**Java Files:** -- `LengthViolations.java` - Long lines, long methods, too many parameters -- `ValidLength.java` - Proper length constraints - -### Style Engine -Tests code formatting and style conventions. - -**JavaScript Files:** -- `style-violations.js` - Bad indentation, spacing, quotes -- `valid.js` - Proper formatting - -**Java Files:** -- `StyleViolations.java` - Inconsistent indentation, brace placement, spacing -- `ValidStyle.java` - Standard Java formatting - -### AST Engine -Tests structural patterns via Abstract Syntax Tree analysis. - -**JavaScript Files:** -- `naming-violations.js` - AST-level naming issues -- `valid.js` - Valid AST structure - -**Java Files:** -- `AstViolations.java` - Empty catch blocks, System.out usage, missing docs -- `ValidAst.java` - Proper exception handling and structure - -### TypeChecker Engine -Tests type safety and TypeScript-specific checks. - -**TypeScript Files:** -- `type-errors.ts` - Type mismatches and errors -- `strict-mode-errors.ts` - Strict mode violations -- `valid.ts` - Correct type usage - -## File Naming Conventions - -- **Violations**: Files containing rule violations are named `*-violations.*` or `*Violations.*` -- **Valid**: Files with compliant code are named `valid.*` or `Valid*.*` -- **Specific**: Files testing specific issues use descriptive names (e.g., `security-violations.js`) - -## Adding New Test Data - -When adding new test data: - -1. Choose the appropriate engine directory (`pattern`, `length`, `style`, `ast`, `typechecker`) -2. Create both violation and valid files for comprehensive testing -3. Add clear comments explaining what each violation tests -4. Update integration tests to reference new files -5. Ensure files compile/parse correctly for their language - -## Integration Test Usage - -These files are referenced by integration tests in `tests/integration/*_integration_test.go`: - -- `pattern_integration_test.go` - Uses pattern engine test data -- `length_integration_test.go` - Uses length engine test data -- `style_integration_test.go` - Uses style engine test data -- `ast_integration_test.go` - Uses ast engine test data -- `typechecker_integration_test.go` - Uses typechecker engine test data - -## Validation Engines - -Each engine uses specific adapters: - -- **JavaScript/TypeScript**: ESLint, Prettier, TSC -- **Java**: Checkstyle, PMD - -Test data files should reflect the validation capabilities of these underlying tools. diff --git a/testdata/java/ast/AstViolations.java b/testdata/java/ast/AstViolations.java deleted file mode 100644 index 43962ec..0000000 --- a/testdata/java/ast/AstViolations.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Test file with AST-level violations - * Contains structural issues detectable via AST analysis - */ -package com.example; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; - -public class AstViolations { - - // VIOLATION: System.out usage in production code - public void debugPrint(String message) { - System.out.println("Debug: " + message); - } - - // VIOLATION: File I/O without try-catch - public String readFileUnsafe(String path) { - FileReader reader = new FileReader(path); - return "content"; - } - - // VIOLATION: Empty catch block - public void emptyCatch() { - try { - riskyOperation(); - } catch (Exception e) { - // Empty catch - swallows exception - } - } - - // VIOLATION: Generic exception catch - public void catchGeneric() { - try { - riskyOperation(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - // VIOLATION: Missing method documentation - public int calculate(int a, int b, int c) { - return a + b * c; - } - - private void riskyOperation() throws IOException { - File file = new File("test.txt"); - if (!file.exists()) { - throw new IOException("File not found"); - } - } - - public static void main(String[] args) { - AstViolations obj = new AstViolations(); - obj.debugPrint("test"); - obj.emptyCatch(); - } -} diff --git a/testdata/java/ast/ValidAst.java b/testdata/java/ast/ValidAst.java deleted file mode 100644 index 004b1cd..0000000 --- a/testdata/java/ast/ValidAst.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Test file with valid AST structure - * Demonstrates proper exception handling and code structure - */ -package com.example; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.logging.Logger; - -public class ValidAst { - - private static final Logger LOGGER = Logger.getLogger(ValidAst.class.getName()); - - /** - * Reads file content safely with proper exception handling - * - * @param path the file path to read - * @return the file content - * @throws IOException if file cannot be read - */ - public String readFileSafe(String path) throws IOException { - try (FileReader reader = new FileReader(path)) { - return "content"; - } catch (IOException e) { - LOGGER.severe("Failed to read file: " + path); - throw e; - } - } - - /** - * Performs calculation with proper error handling - * - * @param a first operand - * @param b second operand - * @param c third operand - * @return calculated result - */ - public int calculate(int a, int b, int c) { - return a + b * c; - } - - /** - * Processes data with specific exception handling - */ - public void processWithSpecificCatch() { - try { - riskyOperation(); - } catch (IOException e) { - LOGGER.warning("I/O error during processing: " + e.getMessage()); - handleError(e); - } - } - - private void riskyOperation() throws IOException { - File file = new File("test.txt"); - if (!file.exists()) { - throw new IOException("File not found"); - } - } - - private void handleError(Exception e) { - LOGGER.severe("Error handled: " + e.getMessage()); - } - - /** - * Main entry point for the application. - * @param args command line arguments - */ - public static void main(String[] args) { - ValidAst obj = new ValidAst(); - obj.processWithSpecificCatch(); - int result = obj.calculate(1, 2, 3); - LOGGER.info("Result: " + result); - } -} diff --git a/testdata/java/ast/code-policy.json b/testdata/java/ast/code-policy.json deleted file mode 100644 index 147ed81..0000000 --- a/testdata/java/ast/code-policy.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "version": "1.0.0", - "rules": [ - { - "id": "JAVA-AST-NO-SYSTEM-OUT", - "enabled": true, - "category": "error_handling", - "severity": "error", - "desc": "Avoid using System.out for logging", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "ast", - "language": "java", - "node": "MethodCallExpr", - "where": { - "scope": "System.out", - "name": "println" - } - }, - "message": "Use a proper logging framework instead of System.out" - }, - { - "id": "JAVA-AST-EMPTY-CATCH", - "enabled": true, - "category": "error_handling", - "severity": "error", - "desc": "Empty catch blocks are not allowed", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "ast", - "language": "java", - "node": "CatchClause", - "where": { - "body.statements.size": 0 - } - }, - "message": "Catch block must handle the exception or at least log it" - }, - { - "id": "JAVA-AST-GENERIC-EXCEPTION", - "enabled": true, - "category": "error_handling", - "severity": "error", - "desc": "Avoid catching generic Exception", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "ast", - "language": "java", - "node": "CatchClause", - "where": { - "parameter.type.name": "Exception" - } - }, - "message": "Catch specific exception types instead of generic Exception" - }, - { - "id": "JAVA-AST-MISSING-JAVADOC", - "enabled": true, - "category": "documentation", - "severity": "error", - "desc": "Public methods must have Javadoc", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "ast", - "language": "java", - "node": "MethodDeclaration", - "where": { - "isPublic": true, - "hasJavadoc": false - } - }, - "message": "Public methods must have Javadoc documentation" - } - ], - "enforce": { - "stages": ["pre-commit"], - "fail_on": ["error"] - } -} diff --git a/testdata/java/length/LengthViolations.java b/testdata/java/length/LengthViolations.java deleted file mode 100644 index ef9b0f2..0000000 --- a/testdata/java/length/LengthViolations.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Test file with length violations - * Contains violations for line length, method length, and parameter count - */ -package com.example; - -public class LengthViolations { - - // VIOLATION: Line length exceeds 100 characters - private static final String VERY_LONG_CONSTANT_NAME_THAT_EXCEEDS_THE_MAXIMUM_LINE_LENGTH_AND_SHOULD_BE_FLAGGED = "test-value"; - - // VIOLATION: Too many parameters (more than 4) - public String processData(String firstName, String lastName, String email, String phone, String address, String city) { - return firstName + " " + lastName + " - " + email; - } - - // VIOLATION: Method is too long (more than 50 lines) - public void veryLongMethod() { - int line1 = 1; - int line2 = 2; - int line3 = 3; - int line4 = 4; - int line5 = 5; - int line6 = 6; - int line7 = 7; - int line8 = 8; - int line9 = 9; - int line10 = 10; - int line11 = 11; - int line12 = 12; - int line13 = 13; - int line14 = 14; - int line15 = 15; - int line16 = 16; - int line17 = 17; - int line18 = 18; - int line19 = 19; - int line20 = 20; - int line21 = 21; - int line22 = 22; - int line23 = 23; - int line24 = 24; - int line25 = 25; - int line26 = 26; - int line27 = 27; - int line28 = 28; - int line29 = 29; - int line30 = 30; - int line31 = 31; - int line32 = 32; - int line33 = 33; - int line34 = 34; - int line35 = 35; - int line36 = 36; - int line37 = 37; - int line38 = 38; - int line39 = 39; - int line40 = 40; - int line41 = 41; - int line42 = 42; - int line43 = 43; - int line44 = 44; - int line45 = 45; - int line46 = 46; - int line47 = 47; - int line48 = 48; - int line49 = 49; - int line50 = 50; - int line51 = 51; - System.out.println("Line: " + line51); - } - - public static void main(String[] args) { - LengthViolations obj = new LengthViolations(); - obj.veryLongMethod(); - } -} diff --git a/testdata/java/length/ValidLength.java b/testdata/java/length/ValidLength.java deleted file mode 100644 index c8e3996..0000000 --- a/testdata/java/length/ValidLength.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Test file with valid length constraints - * All lines, methods, and parameter counts are within limits - */ -package com.example; - -public class ValidLength { - - private static final String CONFIG = "config-value"; - - // Correct: 4 parameters or fewer - public String formatUser(String name, String email, String role) { - return name + " (" + email + ") - " + role; - } - - // Correct: Short, focused method - public int add(int a, int b) { - return a + b; - } - - // Correct: Method within reasonable length - public void processRequest() { - String input = readInput(); - String validated = validate(input); - String result = transform(validated); - save(result); - } - - private String readInput() { - return "input"; - } - - private String validate(String input) { - if (input == null || input.isEmpty()) { - throw new IllegalArgumentException("Invalid input"); - } - return input; - } - - private String transform(String data) { - return data.toUpperCase(); - } - - private void save(String data) { - System.out.println("Saved: " + data); - } - - public static void main(String[] args) { - ValidLength obj = new ValidLength(); - obj.processRequest(); - } -} diff --git a/testdata/java/length/code-policy.json b/testdata/java/length/code-policy.json deleted file mode 100644 index 42dd961..0000000 --- a/testdata/java/length/code-policy.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "version": "1.0.0", - "rules": [ - { - "id": "JAVA-LENGTH-MAX-LINE", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Line length must not exceed 100 characters", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "length", - "language": "java", - "scope": "line", - "max": 100 - }, - "message": "Line length must not exceed 100 characters" - }, - { - "id": "JAVA-LENGTH-MAX-METHOD", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Method length must not exceed 50 lines", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "length", - "language": "java", - "scope": "method", - "max": 50 - }, - "message": "Method length must not exceed 50 lines" - }, - { - "id": "JAVA-LENGTH-MAX-PARAMS", - "enabled": true, - "category": "formatting", - "severity": "warning", - "desc": "Methods should have at most 4 parameters", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "length", - "language": "java", - "scope": "params", - "max": 4 - }, - "message": "Methods should have at most 4 parameters" - } - ], - "enforce": { - "stages": ["pre-commit"], - "fail_on": ["error"] - } -} diff --git a/testdata/java/pattern/NamingViolations.java b/testdata/java/pattern/NamingViolations.java deleted file mode 100644 index e021999..0000000 --- a/testdata/java/pattern/NamingViolations.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Test file with naming convention violations - * Violates Checkstyle naming rules - */ -package com.example; - -// VIOLATION: Class name should start with uppercase (PascalCase) -public class invalidClassName { - - // VIOLATION: Constant should be UPPER_SNAKE_CASE - private static final String apiKey = "sk-1234567890"; - - // VIOLATION: Variable name using UPPER_SNAKE_CASE (should be camelCase) - private int BAD_VARIABLE = 100; - - // VIOLATION: Method name starts with uppercase (should be camelCase) - public void BadMethod() { - System.out.println("This method has bad naming"); - } - - // VIOLATION: Method parameter uses snake_case - public String processData(String user_name) { - return "Hello " + user_name; - } - - // VIOLATION: Multiple naming issues - public static void MAIN(String[] args) { - invalidClassName obj = new invalidClassName(); - obj.BadMethod(); - } -} diff --git a/testdata/java/pattern/ValidNaming.java b/testdata/java/pattern/ValidNaming.java deleted file mode 100644 index 995cfec..0000000 --- a/testdata/java/pattern/ValidNaming.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Test file with correct naming conventions - * Complies with Checkstyle naming rules - */ -package com.example; - -public class ValidNaming { - - // Correct: Constant in UPPER_SNAKE_CASE - private static final String API_KEY = "from-environment"; - - // Correct: Variable in camelCase - private int goodVariable = 100; - - // Correct: Method in camelCase - public void goodMethod() { - System.out.println("This method has good naming"); - } - - // Correct: Parameter in camelCase - public String processData(String userName) { - return "Hello " + userName; - } - - // Correct: Main method - public static void main(String[] args) { - ValidNaming obj = new ValidNaming(); - obj.goodMethod(); - } -} diff --git a/testdata/java/pattern/code-policy.json b/testdata/java/pattern/code-policy.json deleted file mode 100644 index 6f4a174..0000000 --- a/testdata/java/pattern/code-policy.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "version": "1.0.0", - "rules": [ - { - "id": "JAVA-PATTERN-CLASS-PASCAL", - "enabled": true, - "category": "naming", - "severity": "error", - "desc": "Class names must be PascalCase", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "pattern", - "language": "java", - "target": "TypeName", - "pattern": "^[A-Z][a-zA-Z0-9]*$" - }, - "message": "Class names must be PascalCase (e.g., ValidNaming)" - }, - { - "id": "JAVA-PATTERN-METHOD-CAMEL", - "enabled": true, - "category": "naming", - "severity": "error", - "desc": "Method names must be camelCase", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "pattern", - "language": "java", - "target": "MethodName", - "pattern": "^[a-z][a-zA-Z0-9]*$" - }, - "message": "Method names must be camelCase (e.g., goodMethod)" - }, - { - "id": "JAVA-PATTERN-VAR-CAMEL", - "enabled": true, - "category": "naming", - "severity": "error", - "desc": "Variable names must be camelCase", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "pattern", - "language": "java", - "target": "MemberName", - "pattern": "^[a-z][a-zA-Z0-9]*$" - }, - "message": "Variable names must be camelCase (e.g., goodVariable)" - }, - { - "id": "JAVA-PATTERN-CONST-UPPER", - "enabled": true, - "category": "naming", - "severity": "error", - "desc": "Constants must be UPPER_SNAKE_CASE", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "pattern", - "language": "java", - "target": "ConstantName", - "pattern": "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$" - }, - "message": "Constants must be UPPER_SNAKE_CASE (e.g., API_KEY)" - }, - { - "id": "JAVA-PATTERN-PARAM-CAMEL", - "enabled": true, - "category": "naming", - "severity": "error", - "desc": "Parameter names must be camelCase", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "pattern", - "language": "java", - "target": "ParameterName", - "pattern": "^[a-z][a-zA-Z0-9]*$" - }, - "message": "Parameter names must be camelCase (e.g., userName)" - } - ], - "enforce": { - "stages": ["pre-commit"], - "fail_on": ["error"] - } -} diff --git a/testdata/java/style/StyleViolations.java b/testdata/java/style/StyleViolations.java deleted file mode 100644 index 97d8f7b..0000000 --- a/testdata/java/style/StyleViolations.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Test file with style violations - * Contains violations for indentation, spacing, and formatting - */ -package com.example; - -public class StyleViolations { - -// VIOLATION: Missing indentation for class member -private String name; - - // VIOLATION: Inconsistent indentation - public void badIndentation() { - int x = 1; - int y = 2; - int z = 3; - System.out.println(x + y + z); - } - - // VIOLATION: Opening brace on next line (Java convention: same line) - public void badBracePlacement() - { - System.out.println("Bad brace placement"); - } - - // VIOLATION: Multiple statements on one line - public void multipleStatements() { int a = 1; int b = 2; System.out.println(a + b); } - - // VIOLATION: No space after if/for keywords - public void noSpaceAfterKeyword() { - if(true){ - for(int i=0;i<10;i++){ - System.out.println(i); - } - } - } - - // VIOLATION: Inconsistent spacing around operators - public int badOperatorSpacing() { - int result=10+20*30/5-2; - return result; - } - - // VIOLATION: Long line exceeding typical style guide limit - private static final String EXTREMELY_LONG_LINE_THAT_EXCEEDS_REASONABLE_LENGTH_LIMITS_AND_SHOULD_BE_WRAPPED_OR_REFACTORED = "value"; - - // VIOLATION: Missing blank line before method - public void missingBlankLine() { - System.out.println("No blank line above"); - } - public void anotherMethod() { - System.out.println("Methods too close together"); - } -} diff --git a/testdata/java/style/ValidStyle.java b/testdata/java/style/ValidStyle.java deleted file mode 100644 index 2b2b5db..0000000 --- a/testdata/java/style/ValidStyle.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Test file with valid Java style - * Follows standard Java formatting conventions - */ -package com.example; - -public class ValidStyle { - - private String name; - private int value; - - public ValidStyle() { - this.name = "default"; - this.value = 0; - } - - public void properIndentation() { - int x = 1; - int y = 2; - int z = 3; - System.out.println(x + y + z); - } - - public void properBracePlacement() { - System.out.println("Correct brace placement"); - } - - public void properSpacing() { - if (true) { - for (int i = 0; i < 10; i++) { - System.out.println(i); - } - } - } - - public int properOperatorSpacing() { - int result = 10 + 20 * 30 / 5 - 2; - return result; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getValue() { - return value; - } - - public void setValue(int value) { - this.value = value; - } - - public static void main(String[] args) { - ValidStyle obj = new ValidStyle(); - obj.properIndentation(); - obj.properSpacing(); - System.out.println("Result: " + obj.properOperatorSpacing()); - } -} diff --git a/testdata/java/style/code-policy.json b/testdata/java/style/code-policy.json deleted file mode 100644 index afd629f..0000000 --- a/testdata/java/style/code-policy.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "version": "1.0.0", - "rules": [ - { - "id": "JAVA-STYLE-INDENT", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Use consistent 4-space indentation", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "style", - "language": "java", - "indent": 4, - "indentStyle": "space" - }, - "message": "Use 4 spaces for indentation" - }, - { - "id": "JAVA-STYLE-BRACE-SAME-LINE", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Opening brace must be on same line", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "style", - "language": "java", - "braceStyle": "same-line" - }, - "message": "Opening brace should be on the same line" - }, - { - "id": "JAVA-STYLE-SPACE-AFTER-KEYWORD", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Require space after control keywords", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "style", - "language": "java", - "spaceAfterKeyword": true - }, - "message": "Add space after control keywords (if, for, while, etc.)" - }, - { - "id": "JAVA-STYLE-OPERATOR-SPACING", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Require spacing around operators", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "style", - "language": "java", - "spaceAroundOperators": true - }, - "message": "Add spaces around operators" - }, - { - "id": "JAVA-STYLE-MAX-LINE", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Maximum line length of 120 characters", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "style", - "language": "java", - "printWidth": 120 - }, - "message": "Line length must not exceed 120 characters" - }, - { - "id": "JAVA-STYLE-BLANK-LINE-SEPARATOR", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Require blank lines between methods", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "style", - "language": "java", - "blankLinesBetweenMethods": true - }, - "message": "Add blank line between method declarations" - }, - { - "id": "JAVA-STYLE-ONE-STATEMENT-PER-LINE", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Only one statement per line", - "when": { - "languages": ["java"] - }, - "check": { - "engine": "style", - "language": "java", - "oneStatementPerLine": true - }, - "message": "Place each statement on its own line" - } - ], - "enforce": { - "stages": ["pre-commit"], - "fail_on": ["error"] - } -} diff --git a/testdata/javascript/ast/code-policy.json b/testdata/javascript/ast/code-policy.json deleted file mode 100644 index aa5171d..0000000 --- a/testdata/javascript/ast/code-policy.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "version": "1.0.0", - "rules": [ - { - "id": "JS-AST-CLASS-NAMING", - "enabled": true, - "category": "naming", - "severity": "error", - "desc": "Class declarations must be PascalCase", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "pattern", - "language": "javascript", - "target": "identifier", - "context": "class", - "pattern": "^[A-Z][a-zA-Z0-9]*$" - }, - "message": "Class names must be PascalCase" - }, - { - "id": "JS-AST-FUNCTION-NAMING", - "enabled": true, - "category": "naming", - "severity": "error", - "desc": "Function declarations must be camelCase", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "pattern", - "language": "javascript", - "target": "identifier", - "context": "function", - "pattern": "^[a-z][a-zA-Z0-9]*$" - }, - "message": "Function names must be camelCase" - }, - { - "id": "JS-AST-VAR-NAMING", - "enabled": true, - "category": "naming", - "severity": "error", - "desc": "Variable declarations must be camelCase", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "pattern", - "language": "javascript", - "target": "identifier", - "context": "variable", - "pattern": "^[a-z][a-zA-Z0-9]*$" - }, - "message": "Variable names must be camelCase" - } - ], - "enforce": { - "stages": ["pre-commit"], - "fail_on": ["error"] - } -} diff --git a/testdata/javascript/ast/naming-violations.js b/testdata/javascript/ast/naming-violations.js deleted file mode 100644 index 196df75..0000000 --- a/testdata/javascript/ast/naming-violations.js +++ /dev/null @@ -1,29 +0,0 @@ -// File with naming convention violations - -// Bad: snake_case function name (should be camelCase) -function bad_function_name() { - return 'test'; -} - -// Bad: lowercase class name (should be PascalCase) -class lowercase_class { - constructor() { - this.value = 0; - } -} - -// Bad: variable with uppercase -var BAD_VARIABLE = 'test'; - -// Good examples for comparison -function goodFunctionName() { - return 'test'; -} - -class GoodClassName { - constructor() { - this.value = 0; - } -} - -const goodVariable = 'test'; diff --git a/testdata/javascript/ast/valid.js b/testdata/javascript/ast/valid.js deleted file mode 100644 index edebf0f..0000000 --- a/testdata/javascript/ast/valid.js +++ /dev/null @@ -1,28 +0,0 @@ -// Valid JavaScript file for testing - -class Calculator { - constructor() { - this.result = 0; - } - - add(a, b) { - return a + b; - } - - subtract(a, b) { - return a - b; - } - - multiply(a, b) { - return a * b; - } - - divide(a, b) { - if (b === 0) { - throw new Error('Division by zero'); - } - return a / b; - } -} - -module.exports = Calculator; diff --git a/testdata/javascript/length/code-policy.json b/testdata/javascript/length/code-policy.json deleted file mode 100644 index a1487bc..0000000 --- a/testdata/javascript/length/code-policy.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "version": "1.0.0", - "rules": [ - { - "id": "JS-LENGTH-MAX-LINE", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Line length must not exceed 100 characters", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "length", - "language": "javascript", - "scope": "line", - "max": 100 - }, - "message": "Line length must not exceed 100 characters" - }, - { - "id": "JS-LENGTH-MAX-FUNCTION", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Function length must not exceed 50 lines", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "length", - "language": "javascript", - "scope": "function", - "max": 50 - }, - "message": "Function length must not exceed 50 lines" - }, - { - "id": "JS-LENGTH-MAX-PARAMS", - "enabled": true, - "category": "formatting", - "severity": "warning", - "desc": "Functions should have at most 4 parameters", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "length", - "language": "javascript", - "scope": "params", - "max": 4 - }, - "message": "Functions should have at most 4 parameters" - } - ], - "enforce": { - "stages": ["pre-commit"], - "fail_on": ["error"] - } -} diff --git a/testdata/javascript/length/length-violations.js b/testdata/javascript/length/length-violations.js deleted file mode 100644 index 0ef2874..0000000 --- a/testdata/javascript/length/length-violations.js +++ /dev/null @@ -1,65 +0,0 @@ -// File with length violations - -// Bad: line too long (over 100 characters) -const reallyLongVariableNameThatExceedsTheMaximumLineLengthSetByOurLinterConfigurationAndShouldBeReported = 'test'; - -// Bad: function with too many parameters (over 4) -function tooManyParameters(param1, param2, param3, param4, param5, param6) { - return param1 + param2 + param3 + param4 + param5 + param6; -} - -// Bad: function with too many lines (over 50) -function veryLongFunction() { - const line1 = 1; - const line2 = 2; - const line3 = 3; - const line4 = 4; - const line5 = 5; - const line6 = 6; - const line7 = 7; - const line8 = 8; - const line9 = 9; - const line10 = 10; - const line11 = 11; - const line12 = 12; - const line13 = 13; - const line14 = 14; - const line15 = 15; - const line16 = 16; - const line17 = 17; - const line18 = 18; - const line19 = 19; - const line20 = 20; - const line21 = 21; - const line22 = 22; - const line23 = 23; - const line24 = 24; - const line25 = 25; - const line26 = 26; - const line27 = 27; - const line28 = 28; - const line29 = 29; - const line30 = 30; - const line31 = 31; - const line32 = 32; - const line33 = 33; - const line34 = 34; - const line35 = 35; - const line36 = 36; - const line37 = 37; - const line38 = 38; - const line39 = 39; - const line40 = 40; - const line41 = 41; - const line42 = 42; - const line43 = 43; - const line44 = 44; - const line45 = 45; - const line46 = 46; - const line47 = 47; - const line48 = 48; - const line49 = 49; - const line50 = 50; - const line51 = 51; - return line51; -} diff --git a/testdata/javascript/length/valid.js b/testdata/javascript/length/valid.js deleted file mode 100644 index edebf0f..0000000 --- a/testdata/javascript/length/valid.js +++ /dev/null @@ -1,28 +0,0 @@ -// Valid JavaScript file for testing - -class Calculator { - constructor() { - this.result = 0; - } - - add(a, b) { - return a + b; - } - - subtract(a, b) { - return a - b; - } - - multiply(a, b) { - return a * b; - } - - divide(a, b) { - if (b === 0) { - throw new Error('Division by zero'); - } - return a / b; - } -} - -module.exports = Calculator; diff --git a/testdata/javascript/pattern/code-policy.json b/testdata/javascript/pattern/code-policy.json deleted file mode 100644 index 2e29e08..0000000 --- a/testdata/javascript/pattern/code-policy.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "version": "1.0.0", - "rules": [ - { - "id": "JS-PATTERN-CLASS-PASCAL", - "enabled": true, - "category": "naming", - "severity": "error", - "desc": "Class names must be PascalCase", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "pattern", - "language": "javascript", - "target": "identifier", - "pattern": "^[A-Z][a-zA-Z0-9]*$", - "scope": "class" - }, - "message": "Class names must be PascalCase (e.g., Calculator)" - }, - { - "id": "JS-PATTERN-FUNCTION-CAMEL", - "enabled": true, - "category": "naming", - "severity": "error", - "desc": "Function names must be camelCase", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "pattern", - "language": "javascript", - "target": "identifier", - "pattern": "^[a-z][a-zA-Z0-9]*$", - "scope": "function" - }, - "message": "Function names must be camelCase (e.g., goodFunctionName)" - }, - { - "id": "JS-PATTERN-VAR-CAMEL", - "enabled": true, - "category": "naming", - "severity": "error", - "desc": "Variable names must be camelCase", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "pattern", - "language": "javascript", - "target": "identifier", - "pattern": "^[a-z][a-zA-Z0-9]*$", - "scope": "variable" - }, - "message": "Variable names must be camelCase (e.g., goodVariable)" - }, - { - "id": "JS-PATTERN-NO-SECRETS", - "enabled": true, - "category": "security", - "severity": "error", - "desc": "No hardcoded secrets allowed", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "pattern", - "language": "javascript", - "target": "content", - "pattern": "(api[_-]?key|password|secret|token)\\s*=\\s*['\"][^'\"]+['\"]", - "flags": "i" - }, - "message": "Do not hardcode secrets. Use environment variables (process.env)" - } - ], - "enforce": { - "stages": ["pre-commit"], - "fail_on": ["error"] - } -} diff --git a/testdata/javascript/pattern/naming-violations.js b/testdata/javascript/pattern/naming-violations.js deleted file mode 100644 index 196df75..0000000 --- a/testdata/javascript/pattern/naming-violations.js +++ /dev/null @@ -1,29 +0,0 @@ -// File with naming convention violations - -// Bad: snake_case function name (should be camelCase) -function bad_function_name() { - return 'test'; -} - -// Bad: lowercase class name (should be PascalCase) -class lowercase_class { - constructor() { - this.value = 0; - } -} - -// Bad: variable with uppercase -var BAD_VARIABLE = 'test'; - -// Good examples for comparison -function goodFunctionName() { - return 'test'; -} - -class GoodClassName { - constructor() { - this.value = 0; - } -} - -const goodVariable = 'test'; diff --git a/testdata/javascript/pattern/security-violations.js b/testdata/javascript/pattern/security-violations.js deleted file mode 100644 index 414ced5..0000000 --- a/testdata/javascript/pattern/security-violations.js +++ /dev/null @@ -1,18 +0,0 @@ -// File with security violations - -// Bad: hardcoded API key -const API_KEY = 'sk-1234567890abcdef1234567890abcdef'; - -// Bad: hardcoded password -const password = 'mySecretPassword123'; - -// Bad: hardcoded secret -const client_secret = 'super-secret-value-12345'; - -// Bad: hardcoded token -const access_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'; - -// Good: using environment variables -const apiKey = process.env.API_KEY; -const userPassword = process.env.PASSWORD; -const clientSecret = process.env.CLIENT_SECRET; diff --git a/testdata/javascript/pattern/valid.js b/testdata/javascript/pattern/valid.js deleted file mode 100644 index edebf0f..0000000 --- a/testdata/javascript/pattern/valid.js +++ /dev/null @@ -1,28 +0,0 @@ -// Valid JavaScript file for testing - -class Calculator { - constructor() { - this.result = 0; - } - - add(a, b) { - return a + b; - } - - subtract(a, b) { - return a - b; - } - - multiply(a, b) { - return a * b; - } - - divide(a, b) { - if (b === 0) { - throw new Error('Division by zero'); - } - return a / b; - } -} - -module.exports = Calculator; diff --git a/testdata/javascript/style/code-policy.json b/testdata/javascript/style/code-policy.json deleted file mode 100644 index 987ecc4..0000000 --- a/testdata/javascript/style/code-policy.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "version": "1.0.0", - "rules": [ - { - "id": "JS-STYLE-INDENT", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Use consistent 2-space indentation", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "style", - "language": "javascript", - "indent": 2, - "indentStyle": "space" - }, - "message": "Use 2 spaces for indentation" - }, - { - "id": "JS-STYLE-QUOTES", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Use single quotes for strings", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "style", - "language": "javascript", - "quotes": "single" - }, - "message": "Use single quotes for strings" - }, - { - "id": "JS-STYLE-SEMI", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Require semicolons", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "style", - "language": "javascript", - "semi": true - }, - "message": "Statements must end with semicolons" - }, - { - "id": "JS-STYLE-MAX-LINE", - "enabled": true, - "category": "formatting", - "severity": "error", - "desc": "Maximum line length of 100 characters", - "when": { - "languages": ["javascript"] - }, - "check": { - "engine": "style", - "language": "javascript", - "printWidth": 100 - }, - "message": "Line length must not exceed 100 characters" - } - ], - "enforce": { - "stages": ["pre-commit"], - "fail_on": ["error"] - } -} diff --git a/testdata/javascript/style/style-violations.js b/testdata/javascript/style/style-violations.js deleted file mode 100644 index 73ab57c..0000000 --- a/testdata/javascript/style/style-violations.js +++ /dev/null @@ -1,31 +0,0 @@ -// File with style violations - -// Bad: inconsistent indentation -function badIndentation() { -const x = 1; - const y = 2; - const z = 3; - return x + y + z; -} - -// Bad: double quotes (should be single) -const message = "Hello World"; - -// Bad: missing semicolons -const a = 1 -const b = 2 -const c = 3 - -// Bad: long line exceeding 100 characters -const veryLongLineHereThisIsWayTooLongAndShouldBeReportedByTheLinterAsAViolationOfTheLineLength = true; - -// Bad: multiple statements on one line -const x = 1; const y = 2; const z = 3; - -// Good examples -function goodIndentation() { - const x = 1; - const y = 2; - const z = 3; - return x + y + z; -} diff --git a/testdata/javascript/style/valid.js b/testdata/javascript/style/valid.js deleted file mode 100644 index edebf0f..0000000 --- a/testdata/javascript/style/valid.js +++ /dev/null @@ -1,28 +0,0 @@ -// Valid JavaScript file for testing - -class Calculator { - constructor() { - this.result = 0; - } - - add(a, b) { - return a + b; - } - - subtract(a, b) { - return a - b; - } - - multiply(a, b) { - return a * b; - } - - divide(a, b) { - if (b === 0) { - throw new Error('Division by zero'); - } - return a / b; - } -} - -module.exports = Calculator; diff --git a/testdata/typescript/typechecker/code-policy.json b/testdata/typescript/typechecker/code-policy.json deleted file mode 100644 index 12498ea..0000000 --- a/testdata/typescript/typechecker/code-policy.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "version": "1.0.0", - "rules": [ - { - "id": "TS-TYPE-STRICT", - "enabled": true, - "category": "typing", - "severity": "error", - "desc": "Enable strict type checking", - "when": { - "languages": ["typescript"] - }, - "check": { - "engine": "typechecker", - "language": "typescript", - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true - }, - "message": "Strict type checking enabled - all type errors must be resolved" - }, - { - "id": "TS-TYPE-NO-IMPLICIT-ANY", - "enabled": true, - "category": "typing", - "severity": "error", - "desc": "Disallow implicit any types", - "when": { - "languages": ["typescript"] - }, - "check": { - "engine": "typechecker", - "language": "typescript", - "noImplicitAny": true - }, - "message": "Variables and parameters must have explicit type annotations" - }, - { - "id": "TS-TYPE-RETURN-TYPE", - "enabled": true, - "category": "typing", - "severity": "error", - "desc": "Functions must have explicit return types", - "when": { - "languages": ["typescript"] - }, - "check": { - "engine": "typechecker", - "language": "typescript", - "noImplicitReturns": true - }, - "message": "Functions must have explicit return type annotations" - } - ], - "enforce": { - "stages": ["pre-commit"], - "fail_on": ["error"] - } -} diff --git a/testdata/typescript/typechecker/strict-mode-errors.ts b/testdata/typescript/typechecker/strict-mode-errors.ts deleted file mode 100644 index e650f08..0000000 --- a/testdata/typescript/typechecker/strict-mode-errors.ts +++ /dev/null @@ -1,29 +0,0 @@ -// File with strict mode violations - -// Error: Variable 'x' implicitly has an 'any' type -let x; -x = 10; -x = 'string'; - -// Error: Parameter 'input' implicitly has an 'any' type -function processInput(input) { - return input.toUpperCase(); -} - -// Error: Function lacks return type annotation -function calculate(a: number, b: number) { - return a + b; -} - -// Error: Object is possibly 'undefined' -function getUserName(user: { name?: string }) { - return user.name.toUpperCase(); // name might be undefined -} - -// Error: Not all code paths return a value -function getValue(condition: boolean): string { - if (condition) { - return 'yes'; - } - // Missing return for false case -} diff --git a/testdata/typescript/typechecker/type-errors.ts b/testdata/typescript/typechecker/type-errors.ts deleted file mode 100644 index 4ee1a68..0000000 --- a/testdata/typescript/typechecker/type-errors.ts +++ /dev/null @@ -1,41 +0,0 @@ -// File with TypeScript type errors - -interface Person { - name: string; - age: number; -} - -// Error: Type 'string' is not assignable to type 'number' -const person: Person = { - name: 'John', - age: 'thirty' // Should be number -}; - -// Error: Property 'email' does not exist on type 'Person' -function printEmail(p: Person) { - console.log(p.email); // email doesn't exist -} - -// Error: Cannot find name 'undefinedVariable' -const result = undefinedVariable + 10; - -// Error: Argument of type 'number' is not assignable to parameter of type 'string' -function greet(name: string): string { - return `Hello, ${name}`; -} -greet(123); // Should be string - -// Error: Object is possibly 'null' -function getLength(str: string | null) { - return str.length; // str might be null -} - -// Error: 'this' implicitly has type 'any' -const obj = { - value: 10, - getValue: function() { - return function() { - return this.value; // 'this' has wrong context - }; - } -}; diff --git a/testdata/typescript/typechecker/valid.ts b/testdata/typescript/typechecker/valid.ts deleted file mode 100644 index 02eb210..0000000 --- a/testdata/typescript/typechecker/valid.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Valid TypeScript file for testing - -interface User { - id: number; - name: string; - email: string; -} - -class UserService { - private users: User[] = []; - - addUser(user: User): void { - this.users.push(user); - } - - getUser(id: number): User | undefined { - return this.users.find(u => u.id === id); - } - - getAllUsers(): User[] { - return [...this.users]; - } - - removeUser(id: number): boolean { - const index = this.users.findIndex(u => u.id === id); - if (index !== -1) { - this.users.splice(index, 1); - return true; - } - return false; - } -} - -export { User, UserService }; diff --git a/tests/TESTING_GUIDE.md b/tests/TESTING_GUIDE.md index 9f0dce1..20f1c7a 100644 --- a/tests/TESTING_GUIDE.md +++ b/tests/TESTING_GUIDE.md @@ -139,61 +139,6 @@ go test -v ./tests/e2e/... -timeout 5m - [ ] 위반사항 보고서 생성 - [ ] 종료 코드 설정 (위반 시 1) -## 통합 테스트 데이터 구조 - -### testdata 디렉토리 - -검증 엔진의 정확성을 보장하기 위한 테스트 데이터는 `testdata/` 디렉토리에 엔진별, 언어별로 구조화되어 있습니다: - -``` -testdata/ -├── javascript/ -│ ├── pattern/ # 패턴 매칭 및 네이밍 컨벤션 테스트 -│ │ ├── naming-violations.js -│ │ ├── security-violations.js -│ │ └── valid.js -│ ├── length/ # 라인/함수 길이 제한 테스트 -│ │ ├── length-violations.js -│ │ └── valid.js -│ ├── style/ # 코드 스타일 및 포맷팅 테스트 -│ │ ├── style-violations.js -│ │ └── valid.js -│ └── ast/ # AST 구조 검증 테스트 -│ ├── naming-violations.js -│ └── valid.js -├── typescript/ -│ └── typechecker/ # 타입 체킹 테스트 -│ ├── type-errors.ts -│ ├── strict-mode-errors.ts -│ └── valid.ts -└── java/ - ├── pattern/ # Checkstyle 패턴 테스트 - │ ├── NamingViolations.java - │ └── ValidNaming.java - ├── length/ # Checkstyle 길이 제한 테스트 - │ ├── LengthViolations.java - │ └── ValidLength.java - ├── style/ # Checkstyle 스타일 테스트 - │ ├── StyleViolations.java - │ └── ValidStyle.java - └── ast/ # PMD AST 검증 테스트 - ├── AstViolations.java - └── ValidAst.java -``` - -**파일 네이밍 컨벤션**: -- `*-violations.*` / `*Violations.*`: 규칙 위반 케이스 -- `valid.*` / `Valid*.*`: 규칙 준수 케이스 - -각 엔진은 해당 언어의 testdata를 사용하여 통합 테스트를 실행합니다: -- Pattern Engine: 정규식 패턴 검증 (ESLint/Checkstyle) -- Length Engine: 길이 제한 검증 (ESLint/Checkstyle) -- Style Engine: 포맷팅 검증 (Prettier/Checkstyle) -- AST Engine: 구조 검증 (ESLint/PMD) -- TypeChecker Engine: 타입 검증 (TSC) - -자세한 내용은 [testdata/README.md](../testdata/README.md)를 참고하세요. - ## E2E 테스트 데이터 ### 자연어 컨벤션 예시 diff --git a/tests/integration/helper.go.disabled b/tests/integration/helper.go.disabled deleted file mode 100644 index 0caba76..0000000 --- a/tests/integration/helper.go.disabled +++ /dev/null @@ -1,85 +0,0 @@ -package integration - -import ( - "os" - "path/filepath" - "testing" - - "github.com/DevSymphony/sym-cli/internal/policy" - "github.com/DevSymphony/sym-cli/internal/validator" - "github.com/DevSymphony/sym-cli/pkg/schema" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// getTestdataDir returns the path to the testdata directory -func getTestdataDir(t *testing.T) string { - t.Helper() - - // Get current working directory - cwd, err := os.Getwd() - if err != nil { - t.Fatalf("Failed to get working directory: %v", err) - } - - // Go up two levels from tests/integration to project root - projectRoot := filepath.Join(cwd, "../..") - - return projectRoot -} - -// loadPolicyFromTestdata loads a code-policy.json from testdata directory -func loadPolicyFromTestdata(t *testing.T, relativePath string) *schema.CodePolicy { - t.Helper() - loader := policy.NewLoader(false) - policyPath := filepath.Join(getTestdataDir(t), relativePath) - pol, err := loader.LoadCodePolicy(policyPath) - require.NoError(t, err, "Failed to load policy from %s", relativePath) - require.NotNil(t, pol, "Policy should not be nil") - return pol -} - -// createTestValidator creates a validator with given policy and registers cleanup -func createTestValidator(t *testing.T, pol *schema.CodePolicy) *validator.Validator { - t.Helper() - v := validator.NewValidator(pol, testing.Verbose()) - t.Cleanup(func() { - if err := v.Close(); err != nil { - t.Logf("Warning: failed to close validator: %v", err) - } - }) - return v -} - -// assertViolationsDetected asserts that violations are found and logs them -func assertViolationsDetected(t *testing.T, result *validator.Result) { - t.Helper() - assert.False(t, result.Passed, "Should detect violations") - assert.Greater(t, len(result.Violations), 0, "Should have violations") - - // Log violations for debugging - if len(result.Violations) > 0 { - t.Logf("Found %d violation(s):", len(result.Violations)) - for i, v := range result.Violations { - t.Logf(" %d. [%s] %s at %s:%d:%d (severity: %s)", - i+1, v.RuleID, v.Message, v.File, v.Line, v.Column, v.Severity) - } - } -} - -// assertNoPolicyViolations asserts that no violations are found -func assertNoPolicyViolations(t *testing.T, result *validator.Result) { - t.Helper() - if !result.Passed || len(result.Violations) > 0 { - // Log violations if any for debugging - if len(result.Violations) > 0 { - t.Logf("Unexpected violations found:") - for i, v := range result.Violations { - t.Logf(" %d. [%s] %s at %s:%d:%d", - i+1, v.RuleID, v.Message, v.File, v.Line, v.Column) - } - } - } - assert.True(t, result.Passed, "Should pass validation") - assert.Equal(t, 0, len(result.Violations), "Should have no violations") -} diff --git a/tests/integration/rbac_test.go b/tests/integration/rbac_test.go index 6b1e86f..16493fe 100644 --- a/tests/integration/rbac_test.go +++ b/tests/integration/rbac_test.go @@ -4,57 +4,8 @@ import ( "testing" "github.com/DevSymphony/sym-cli/internal/roles" - "github.com/DevSymphony/sym-cli/pkg/schema" ) -// Test matchPattern function with various glob patterns -func TestMatchPattern(t *testing.T) { - tests := []struct { - pattern string - path string - expected bool - }{ - // ** patterns - {"src/**", "src/components/Button.js", true}, - {"src/**", "src/utils/helper.js", true}, - {"src/**", "lib/main.js", false}, - {"src/components/**", "src/components/ui/Button.js", true}, - {"src/components/**", "src/utils/helper.js", false}, - - // ** with suffix - {"**/*.js", "src/components/Button.js", true}, - {"**/*.js", "lib/utils.js", true}, - {"**/*.js", "src/styles.css", false}, - {"src/**/test", "src/components/test", true}, - {"src/**/test", "src/a/b/c/test", true}, - - // * patterns - {"src/*.js", "src/main.js", true}, - {"src/*.js", "src/components/Button.js", false}, - - // Exact match - {"src/main.js", "src/main.js", true}, - {"src/main.js", "src/app.js", false}, - - // Directory prefix - {"src/components/", "src/components/Button.js", true}, - {"src/components/", "src/utils/helper.js", false}, - } - - for _, tt := range tests { - // Since matchPattern is not exported, we'll test through checkFilePermission - role := &schema.UserRole{ - AllowWrite: []string{tt.pattern}, - DenyWrite: []string{}, - } - // This will use matchPattern internally - _ = role - // We can't directly test matchPattern since it's not exported - // So this test is commented out for now - t.Skip("matchPattern is not exported, test through integration") - } -} - // Test complex RBAC scenarios with admin, developer, viewer roles func TestComplexRBACPatterns(t *testing.T) { tests := []struct { diff --git a/tests/testdata/test_violation.go b/tests/testdata/test_violation.go deleted file mode 100644 index 48c983b..0000000 --- a/tests/testdata/test_violation.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import "fmt" - -func main() { - // Hardcoded API key - should violate security rule - apiKey := "sk-1234567890abcdef" - fmt.Println(apiKey) -} diff --git a/tests/testdata/user-policy-example.json b/tests/testdata/user-policy-example.json deleted file mode 100644 index 3e5cb0d..0000000 --- a/tests/testdata/user-policy-example.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "version": "1.0.0", - "defaults": { - "severity": "error", - "autofix": false - }, - "rules": [ - { - "id": "naming-class-pascalcase", - "say": "Class names must be PascalCase", - "category": "naming", - "languages": ["javascript", "typescript", "java"], - "params": { - "case": "PascalCase" - } - }, - { - "id": "length-max-line", - "say": "Maximum line length is 100 characters", - "category": "length", - "params": { - "max": 100 - } - }, - { - "id": "style-indent", - "say": "Use 4 spaces for indentation", - "category": "style", - "languages": ["javascript", "typescript", "java"], - "params": { - "indent": 4 - } - }, - { - "id": "security-no-hardcoded-secrets", - "say": "Do not hardcode secrets, API keys, or passwords", - "category": "security", - "severity": "error" - }, - { - "id": "complexity-max", - "say": "Maximum cyclomatic complexity is 10", - "category": "complexity", - "languages": ["javascript", "typescript", "java"], - "params": { - "complexity": 10 - } - } - ] -}