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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions internal/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,6 @@ func runInit(cmd *cobra.Command, args []string) {
fmt.Println("✓ .sym/.env created with default policy path")
}

fmt.Println("\nNext steps:")
fmt.Println(" 1. Review the files:")
fmt.Println(" cat .sym/roles.json")
fmt.Println(" cat .sym/user-policy.json")
fmt.Println(" 2. Commit: git add .sym/ && git commit -m 'Initialize Symphony roles and policy'")
fmt.Println(" 3. Push: git push")
fmt.Println("\nAfter pushing, team members can clone and use 'sym my-role' to check their access.")

// MCP registration prompt
if !skipMCPRegister {
promptMCPRegistration()
Expand All @@ -166,6 +158,19 @@ func runInit(cmd *cobra.Command, args []string) {
if !skipAPIKey {
promptAPIKeyIfNeeded()
}

// Show dashboard guide after all initialization is complete
fmt.Println("\n🎯 What's Next: Use Symphony Dashboard")
fmt.Println()
fmt.Println("Start the web dashboard:")
fmt.Println(" sym dashboard")
fmt.Println()
fmt.Println("Dashboard features:")
fmt.Println(" 📋 Manage roles - Add/remove team members, configure permissions")
fmt.Println(" 📝 Edit policies - Create and modify coding conventions")
fmt.Println(" ✅ Test validation - Check rules against your code in real-time")
fmt.Println()
fmt.Println("After setup, commit and push .sym/ folder to share with your team.")
}

// createDefaultPolicy creates a default policy file with RBAC roles
Expand Down
188 changes: 187 additions & 1 deletion internal/cmd/mcp_register.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ func promptMCPRegistration() {
"Claude Desktop (global)",
"Claude Code (project)",
"Cursor (project)",
"VS Code/Cline (project)",
"VS Code Copilot (project)",
"Cline (project)",
"All",
"Skip",
}
Expand Down Expand Up @@ -271,6 +272,13 @@ func registerMCP(app string) error {

fmt.Printf(" ✓ Symphony MCP server registered\n")

// Create instructions file for project-specific configs
if isProjectConfig {
if err := createInstructionsFile(app); err != nil {
fmt.Printf(" ⚠ Failed to create instructions file: %v\n", err)
}
}

return nil
}

Expand Down Expand Up @@ -329,3 +337,181 @@ func checkNpxAvailable() bool {
_, err := exec.LookPath("npx")
return err == nil
}

// createInstructionsFile creates or updates the instructions file for the specified app
func createInstructionsFile(app string) error {
var instructionsPath string
var content string
var appendMode bool

switch app {
case "claude-code":
instructionsPath = "claude.md"
content = getClaudeCodeInstructions()
appendMode = true
case "cursor":
// Use new .cursor/rules format
instructionsPath = filepath.Join(".cursor", "rules", "symphony.mdc")
content = getCursorInstructions()
appendMode = false
case "vscode":
// Use .github/instructions/symphony.instructions.md for VS Code Copilot
instructionsPath = filepath.Join(".github", "instructions", "symphony.instructions.md")
content = getVSCodeInstructions()
appendMode = false
default:
return nil // No instructions file for this app
}

// Check if file exists
existingContent, err := os.ReadFile(instructionsPath)
fileExists := err == nil

if fileExists {
if appendMode {
// Check if Symphony instructions already exist
if strings.Contains(string(existingContent), "# Symphony Code Conventions") {
fmt.Printf(" ✓ Instructions already exist in %s\n", instructionsPath)
return nil
}
// Append to existing file
content = string(existingContent) + "\n\n" + content
fmt.Printf(" ✓ Appended Symphony instructions to %s\n", instructionsPath)
} else {
// Create backup
backupPath := instructionsPath + ".bak"
if err := os.WriteFile(backupPath, existingContent, 0644); err != nil {
fmt.Printf(" ⚠ Failed to create backup: %v\n", err)
} else {
fmt.Printf(" Backup: %s\n", filepath.Base(backupPath))
}
fmt.Printf(" ✓ Created %s\n", instructionsPath)
}
} else {
// Create new file
fmt.Printf(" ✓ Created %s\n", instructionsPath)
}

// Create directory if needed
dir := filepath.Dir(instructionsPath)
if dir != "." {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
}

// Write file
if err := os.WriteFile(instructionsPath, []byte(content), 0644); err != nil {
return fmt.Errorf("failed to write file: %w", err)
}

return nil
}

// getClaudeCodeInstructions returns instructions for Claude Code (claude.md)
func getClaudeCodeInstructions() string {
return `# Symphony Code Conventions

**This project uses Symphony MCP for automated code convention management.**

## Critical Requirements

### 1. Before Writing Code

**Check MCP Status**: Verify Symphony MCP server is active. If unavailable, warn the user and do not proceed.

**Query Conventions**: Use ` + "`symphony/query_conventions`" + ` to retrieve relevant rules.
- Select appropriate category: security, style, documentation, error_handling, architecture, performance, testing
- Filter by languages as needed

### 2. After Writing Code

**Validate Changes**: Always run ` + "`symphony/validate_code`" + ` to check all changes against project conventions.

**Fix Violations**: Address any issues found before committing.

## Workflow

1. Verify Symphony MCP is active
2. Query conventions for your task
3. Write code
4. Validate with Symphony
5. Fix violations
6. Commit

---

*Auto-generated by Symphony*
`
}

// getCursorInstructions returns instructions for Cursor (.cursor/rules/symphony.mdc)
func getCursorInstructions() string {
return `---
description: Symphony code convention validation
globs:
- "**/*"
alwaysApply: true
---

# Symphony Code Conventions

**This project uses Symphony MCP for code convention management.**

## Required Steps

### Before Code Generation
1. **Verify Symphony MCP is active** - If not available, stop and warn user
2. **Query conventions** - Use ` + "`symphony/query_conventions`" + ` with appropriate category and language

### After Code Generation
1. **Validate all changes** - Use ` + "`symphony/validate_code`" + `
2. **Fix violations** - Address issues before committing

## Convention Categories
- security
- style
- documentation
- error_handling
- architecture
- performance
- testing

---

*Auto-generated by Symphony*
`
}

// getVSCodeInstructions returns instructions for VS Code Copilot (.github/instructions/*.instructions.md)
func getVSCodeInstructions() string {
return `---
description: Symphony code convention validation
name: Symphony Conventions
applyTo: "**"
---

# Symphony Code Conventions

This project uses Symphony MCP for automated code convention management.

## Critical Requirements

### Before Writing Code
1. Verify Symphony MCP server is active. If not available, warn user and stop.
2. Query relevant conventions using symphony/query_conventions tool.
- Categories: security, style, documentation, error_handling, architecture, performance, testing
- Filter by programming language

### After Writing Code
1. Always validate changes using symphony/validate_code tool (validates all git changes)
2. Fix any violations found
3. Only commit after validation passes

## Workflow
Check MCP → Query Conventions → Write Code → Validate → Fix → Commit

---
Auto-generated by Symphony
`
}
42 changes: 0 additions & 42 deletions internal/policy/templates/demo-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,48 +93,6 @@
"category": "naming",
"example": "// ✅ 좋은 예:\nprivate boolean m_isActive;\nprivate boolean m_hasPermission;\npublic boolean isValid() { }\npublic boolean canDelete() { }\n\n// ❌ 나쁜 예:\nprivate boolean m_active;\npublic boolean checkValid() { }\npublic boolean validateUser() { }"
},
{
"id": "F-01",
"say": "클래스 내부 순서: static 변수 → 멤버변수 → 생성자 → public 메서드 → protected 메서드 → private 메서드",
"category": "formatting",
"example": "// ✅ 좋은 예:\npublic class UserService {\n // 1. static 변수\n private static final int MAX_COUNT = 100;\n \n // 2. 멤버변수\n private UserRepo m_userRepo;\n \n // 3. 생성자\n public UserService() { }\n \n // 4. public 메서드\n public void getUser() { }\n \n // 5. protected 메서드\n protected void validate() { }\n \n // 6. private 메서드\n private void _helper() { }\n}"
},
{
"id": "F-02",
"say": "멤버변수는 public → protected → private 순서로 선언합니다",
"category": "formatting",
"example": "// ✅ 좋은 예:\npublic class UserService {\n public String publicField;\n protected String p_protectedField;\n private String m_privateField;\n}"
},
{
"id": "F-03",
"say": "생성자는 매개변수 개수가 적은 순서부터 배치합니다",
"category": "formatting",
"example": "// ✅ 좋은 예:\npublic UserEntity() { }\npublic UserEntity(Long paramId) { }\npublic UserEntity(Long paramId, String paramName) { }\n\n// ❌ 나쁜 예:\npublic UserEntity(Long paramId, String paramName) { }\npublic UserEntity() { }"
},
{
"id": "F-04",
"say": "메서드와 메서드 사이에는 정확히 1개의 공백 라인이 필요합니다",
"category": "formatting",
"example": "// ✅ 좋은 예:\npublic void method1() {\n}\n\npublic void method2() {\n}\n\n// ❌ 나쁜 예:\npublic void method1() {\n}\npublic void method2() {\n}"
},
{
"id": "F-05",
"say": "import 문은 패키지명 기준 알파벳 순으로 정렬합니다 (java.* → javax.* → com.* → org.*)",
"category": "formatting",
"example": "// ✅ 좋은 예:\nimport com.company.ecommerce.dto.UserDto;\nimport com.company.ecommerce.entity.UserEntity;\nimport java.util.List;\nimport javax.persistence.Entity;\nimport org.springframework.stereotype.Service;\n\n// ❌ 나쁜 예:\nimport java.util.List;\nimport com.company.ecommerce.dto.UserDto;\nimport org.springframework.stereotype.Service;"
},
{
"id": "D-01",
"say": "모든 public 클래스는 @author, @since, @version 태그를 포함한 Javadoc이 필수입니다",
"category": "documentation",
"example": "// ✅ 좋은 예:\n/**\n * 사용자 관리 서비스 클래스\n *\n * @author Development Team\n * @since 1.0\n * @version 1.0.0\n */\npublic class UserService { }\n\n// ❌ 나쁜 예:\n// 사용자 관리 서비스\npublic class UserService { }"
},
{
"id": "D-02",
"say": "모든 public 메서드는 @param, @return (반환값 있는 경우) 태그를 포함한 Javadoc이 필수입니다",
"category": "documentation",
"example": "// ✅ 좋은 예:\n/**\n * 사용자 ID로 사용자를 조회합니다.\n *\n * @param paramUserId 조회할 사용자 ID\n * @return 사용자 DTO\n */\npublic UserDto getUser(Long paramUserId) { }\n\n// ❌ 나쁜 예:\n// 사용자 조회\npublic UserDto getUser(Long paramUserId) { }"
},
{
"id": "E-01",
"say": "Custom Exception 클래스는 반드시 'Exception' 접미어를 사용합니다",
Expand Down