diff --git a/internal/cmd/init.go b/internal/cmd/init.go index 30d6d77..431fdd2 100644 --- a/internal/cmd/init.go +++ b/internal/cmd/init.go @@ -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() @@ -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 diff --git a/internal/cmd/mcp_register.go b/internal/cmd/mcp_register.go index ad73006..a6179b8 100644 --- a/internal/cmd/mcp_register.go +++ b/internal/cmd/mcp_register.go @@ -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", } @@ -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 } @@ -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 +` +} diff --git a/internal/policy/templates/demo-template.json b/internal/policy/templates/demo-template.json index ce4e13b..51868ed 100644 --- a/internal/policy/templates/demo-template.json +++ b/internal/policy/templates/demo-template.json @@ -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' μ ‘λ―Έμ–΄λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€",