on main help sign in
feat: discover skills from git root in monorepos (#3078)
363ffecTai Groot
internal/config/load.go+28−6
@@ -1046,15 +1046,37 @@ func GlobalSkillsDirs() []string {
1046 return paths
1046 return paths
1047}
1047}
1048
1048
1049+// projectSkillSubdirs lists the conventional subdirectories where
1050+// project-level skills are discovered. Shared across working-dir and
1051+// git-root lookups to prevent drift when a new convention is added.
1052+var projectSkillSubdirs = []string{
1053+ ".agents/skills",
1054+ ".crush/skills",
1055+ ".claude/skills",
1056+ ".cursor/skills",
1057+}
1058+
1049// ProjectSkillsDir returns the default project directories for which Crush
1059// ProjectSkillsDir returns the default project directories for which Crush
1050−// will look for skills.
1060+// will look for skills. In addition to the working directory, it also
1061+// checks the git working tree root so that monorepo-level skills are
1062+// discovered when the user is inside a subdirectory.
1063+// Working-directory paths come first so local skills take precedence
1064+// over monorepo-level ones.
1051func ProjectSkillsDir(workingDir string) []string {
1065func ProjectSkillsDir(workingDir string) []string {
1052− return []string{
1066+ dirs := make([]string, 0, len(projectSkillSubdirs)*2)
1067+ for _, sub := range projectSkillSubdirs {
1053− filepath.Join(workingDir, ".agents/skills"),
1068+ dirs = append(dirs, filepath.Join(workingDir, sub))
1054− filepath.Join(workingDir, ".crush/skills"),
1069+ }
1055− filepath.Join(workingDir, ".claude/skills"),
1070+
1056− filepath.Join(workingDir, ".cursor/skills"),
1071+ // When the working directory is inside a git repository, also look at
1072+ // the repository root so monorepo-level .agents/skills are found.
1073+ if root := worktreeRoot(workingDir); root != "" && root != workingDir {
1074+ for _, sub := range projectSkillSubdirs {
1075+ dirs = append(dirs, filepath.Join(root, sub))
1076+ }
1057 }
1077 }
1078+
1079+ return dirs
1058}
1080}
1059
1081
1060func isAppleTerminal() bool { return os.Getenv("TERM_PROGRAM") == "Apple_Terminal" }
1082func isAppleTerminal() bool { return os.Getenv("TERM_PROGRAM") == "Apple_Terminal" }