feat(client): ignore rule in .lodge

This commit is contained in:
hh committed 2026-10-02 21:59:27 +00:00
1 parent 4207f7e6f8
commit 45c81cfcaf
8 files changed
+146 -18

No files matched your search

+31 -6
View File
@@ -5,6 +5,7 @@ import (
"os"
"path"
"path/filepath"
"slices"
"strings"
"lodge/internal/gitrepo"
@@ -15,6 +16,7 @@ const (
RulesFile = ".lodge"
sharedKeyword = "shared"
branchKeyword = "branch"
ignoreKeyword = "ignore"
excludeBegin = "# lodge begin"
excludeEnd = "# lodge end"
DefaultRules = "shared docs/agents\nshared CLAUDE.local.md\nbranch .agent\n"
@@ -23,6 +25,7 @@ const (
type Rule struct {
Path string `json:"path"`
Shared bool `json:"shared"`
Ignore bool `json:"ignore,omitempty"`
}
type Rules []Rule
@@ -31,12 +34,12 @@ func parseRules(text string) Rules {
rules := Rules{{Path: RulesFile, Shared: true}}
for _, line := range strings.Split(text, "\n") {
fields := strings.Fields(line)
if len(fields) != 2 || fields[0] != sharedKeyword && fields[0] != branchKeyword {
if len(fields) != 2 || !slices.Contains([]string{sharedKeyword, branchKeyword, ignoreKeyword}, fields[0]) {
continue
}
rulePath := path.Clean(strings.Trim(filepath.ToSlash(fields[1]), "/"))
if protocol.SafePath(rulePath) {
rules = append(rules, Rule{Path: rulePath, Shared: fields[0] == sharedKeyword})
rules = append(rules, Rule{Path: rulePath, Shared: fields[0] == sharedKeyword, Ignore: fields[0] == ignoreKeyword})
}
}
return rules
@@ -50,9 +53,14 @@ func LoadRules(root string) Rules {
return parseRules(string(data))
}
func (rules Rules) synced() Rules {
return slices.DeleteFunc(slices.Clone(rules), func(rule Rule) bool { return rule.Ignore })
}
func (rules Rules) paths() []string {
paths := make([]string, len(rules))
for i, rule := range rules {
synced := rules.synced()
paths := make([]string, len(synced))
for i, rule := range synced {
paths[i] = rule.Path
}
return paths
@@ -60,12 +68,29 @@ func (rules Rules) paths() []string {
func (rules Rules) covers(file string, shared bool) bool {
best := Rule{}
for _, rule := range rules {
for _, rule := range rules.synced() {
if within(file, rule.Path) && len(rule.Path) > len(best.Path) {
best = rule
}
}
return best.Path != "" && best.Shared == shared
return best.Path != "" && best.Shared == shared && !rules.ignored(file)
}
func (rules Rules) ignored(file string) bool {
return file != RulesFile && slices.ContainsFunc(rules, func(rule Rule) bool { return rule.Ignore && rule.matches(file) })
}
func (rule Rule) matches(file string) bool {
for name := file; name != "." && name != "/"; name = path.Dir(name) {
target := name
if !strings.Contains(rule.Path, "/") {
target = path.Base(name)
}
if matched, _ := path.Match(rule.Path, target); matched {
return true
}
}
return false
}
func within(path, dir string) bool {