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
12 changes: 9 additions & 3 deletions internal/cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,18 @@ func runValidate(cmd *cobra.Command, args []string) error {

fmt.Printf("Found %d changed file(s)\n", len(changes))

// Create validator
v := validator.NewLLMValidator(llmClient, &policy)
// Create unified validator that handles all engines + RBAC
v := validator.NewValidator(&policy, true) // verbose=true for CLI
v.SetLLMClient(llmClient)
defer func() {
if err := v.Close(); err != nil {
fmt.Printf("Warning: failed to close validator: %v\n", err)
}
}()

// Validate changes
ctx := context.Background()
result, err := v.Validate(ctx, changes)
result, err := v.ValidateChanges(ctx, changes)
if err != nil {
return fmt.Errorf("validation failed: %w", err)
}
Expand Down
10 changes: 10 additions & 0 deletions internal/git/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,13 @@ func IsGitRepo() bool {
err := cmd.Run()
return err == nil
}

// GetCurrentUser returns the current git user name
func GetCurrentUser() (string, error) {
cmd := exec.Command("git", "config", "--get", "user.name")
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to get git user.name: %w", err)
}
return strings.TrimSpace(string(output)), nil
}
89 changes: 85 additions & 4 deletions internal/mcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/DevSymphony/sym-cli/internal/git"
"github.com/DevSymphony/sym-cli/internal/llm"
"github.com/DevSymphony/sym-cli/internal/policy"
"github.com/DevSymphony/sym-cli/internal/roles"
"github.com/DevSymphony/sym-cli/internal/validator"
"github.com/DevSymphony/sym-cli/pkg/schema"
sdkmcp "github.com/modelcontextprotocol/go-sdk/mcp"
Expand Down Expand Up @@ -446,9 +447,16 @@ func (s *Server) handleQueryConventions(params map[string]interface{}) (interfac
}
textContent += "\n"
}
textContent += "\n✓ Next Step: Implement your code following these conventions. After completion, MUST call validate_code to verify compliance."
}

// Add RBAC information if available
rbacInfo := s.getRBACInfo()
if rbacInfo != "" {
textContent += "\n\n" + rbacInfo
}

textContent += "\n✓ Next Step: Implement your code following these conventions. After completion, MUST call validate_code to verify compliance."

// Return MCP-compliant response with content array
return map[string]interface{}{
"content": []map[string]interface{}{
Expand Down Expand Up @@ -635,11 +643,17 @@ func (s *Server) handleValidateCode(params map[string]interface{}) (interface{},
}

llmClient := llm.NewClient(apiKey)
llmValidator := validator.NewLLMValidator(llmClient, validationPolicy)

// Validate git changes
// Create unified validator that handles all engines + RBAC
v := validator.NewValidator(validationPolicy, false) // verbose=false for MCP
v.SetLLMClient(llmClient)
defer func() {
_ = v.Close() // Ignore close error in MCP context
}()

// Validate git changes using unified validator
ctx := context.Background()
result, err := llmValidator.Validate(ctx, changes)
result, err := v.ValidateChanges(ctx, changes)
if err != nil {
return nil, &RPCError{
Code: -32000,
Expand Down Expand Up @@ -904,3 +918,70 @@ func (s *Server) needsConversion(codePolicyPath string) bool {
func (s *Server) convertUserPolicy(userPolicyPath, codePolicyPath string) error {
return ConvertPolicyWithLLM(userPolicyPath, codePolicyPath)
}

// getRBACInfo returns RBAC information for the current user
func (s *Server) getRBACInfo() string {
// Try to get current user
username, err := git.GetCurrentUser()
if err != nil {
// Not in a git environment or user not configured
return ""
}

// Get user's role
userRole, err := roles.GetUserRole(username)
if err != nil {
// Roles not configured
return ""
}

if userRole == "none" {
return fmt.Sprintf("⚠️ RBAC: User '%s' has no assigned role. You may not have permission to modify files.", username)
}

// Load user policy to get RBAC details
userPolicy, err := roles.LoadUserPolicyFromRepo()
if err != nil {
// User policy not available
return fmt.Sprintf("🔐 RBAC: Current user '%s' has role '%s'", username, userRole)
}

// Check if RBAC is defined
if userPolicy.RBAC == nil || userPolicy.RBAC.Roles == nil {
return fmt.Sprintf("🔐 RBAC: Current user '%s' has role '%s' (no restrictions defined)", username, userRole)
}

// Get role configuration
roleConfig, exists := userPolicy.RBAC.Roles[userRole]
if !exists {
return fmt.Sprintf("⚠️ RBAC: User '%s' has role '%s', but role is not defined in policy", username, userRole)
}

// Build RBAC info message
var rbacMsg strings.Builder
rbacMsg.WriteString("🔐 RBAC Information:\n")
rbacMsg.WriteString(fmt.Sprintf(" User: %s\n", username))
rbacMsg.WriteString(fmt.Sprintf(" Role: %s\n", userRole))

if len(roleConfig.AllowWrite) > 0 {
rbacMsg.WriteString(fmt.Sprintf(" Allowed paths: %s\n", strings.Join(roleConfig.AllowWrite, ", ")))
} else {
rbacMsg.WriteString(" Allowed paths: All files (no restrictions)\n")
}

if len(roleConfig.DenyWrite) > 0 {
rbacMsg.WriteString(fmt.Sprintf(" Denied paths: %s\n", strings.Join(roleConfig.DenyWrite, ", ")))
}

if roleConfig.CanEditPolicy {
rbacMsg.WriteString(" Can edit policy: Yes\n")
}

if roleConfig.CanEditRoles {
rbacMsg.WriteString(" Can edit roles: Yes\n")
}

rbacMsg.WriteString("\n⚠️ Note: Modifications to denied paths will be blocked during validation.")

return rbacMsg.String()
}
7 changes: 4 additions & 3 deletions internal/validator/llm_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (v *LLMValidator) Validate(ctx context.Context, changes []GitChange) (*Vali
for _, rule := range llmRules {
result.Checked++

violation, err := v.checkRule(ctx, change, addedLines, rule)
violation, err := v.CheckRule(ctx, change, addedLines, rule)
if err != nil {
// Log error but continue
fmt.Printf("Warning: failed to check rule %s: %v\n", rule.ID, err)
Expand Down Expand Up @@ -106,8 +106,9 @@ func (v *LLMValidator) filterLLMRules() []schema.PolicyRule {
return llmRules
}

// checkRule checks if code violates a specific rule using LLM
func (v *LLMValidator) checkRule(ctx context.Context, change GitChange, addedLines []string, rule schema.PolicyRule) (*Violation, error) {
// CheckRule checks if code violates a specific rule using LLM
// This is the single source of truth for LLM-based validation logic
func (v *LLMValidator) CheckRule(ctx context.Context, change GitChange, addedLines []string, rule schema.PolicyRule) (*Violation, error) {
// Build prompt for LLM
systemPrompt := `You are a code reviewer. Check if the code changes violate the given coding convention.

Expand Down
Loading
Loading