loadAllRules scans both global and workspace rules directories. Workspace rules override global rules with the same filename.
()
| 84 | // loadAllRules scans both global and workspace rules directories. |
| 85 | // Workspace rules override global rules with the same filename. |
| 86 | func (rl *RulesLoader) loadAllRules() []*Rule { |
| 87 | seen := make(map[string]*Rule) // keyed by rule name (dedup) |
| 88 | |
| 89 | // Load global rules first (lower priority) |
| 90 | globalRulesDir := filepath.Join(rl.globalDir, "rules") |
| 91 | rl.scanRulesDir(globalRulesDir, seen) |
| 92 | |
| 93 | // Load workspace rules (higher priority — overwrites global) |
| 94 | workspaceRulesDir := filepath.Join(rl.workspaceDir, ".chatcli", "rules") |
| 95 | rl.scanRulesDir(workspaceRulesDir, seen) |
| 96 | |
| 97 | // Also check workspace root (for standalone .chatcli/rules/ without leading dot) |
| 98 | altRulesDir := filepath.Join(rl.workspaceDir, "rules") |
| 99 | if _, err := os.Stat(filepath.Join(rl.workspaceDir, ".chatcli")); err != nil { |
| 100 | // No .chatcli dir, try bare rules/ in workspace |
| 101 | rl.scanRulesDir(altRulesDir, seen) |
| 102 | } |
| 103 | |
| 104 | rules := make([]*Rule, 0, len(seen)) |
| 105 | for _, rule := range seen { |
| 106 | rules = append(rules, rule) |
| 107 | } |
| 108 | return rules |
| 109 | } |
| 110 | |
| 111 | // scanRulesDir reads all .md files in a directory and loads them as rules. |
| 112 | func (rl *RulesLoader) scanRulesDir(dir string, seen map[string]*Rule) { |
no test coverage detected