aboutsummaryrefslogtreecommitdiff
path: root/scripts/lint.sh
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lint.sh')
-rwxr-xr-xscripts/lint.sh36
1 files changed, 35 insertions, 1 deletions
diff --git a/scripts/lint.sh b/scripts/lint.sh
index 82f3b34..ca6abbd 100755
--- a/scripts/lint.sh
+++ b/scripts/lint.sh
@@ -21,10 +21,22 @@ warn() {
errors=$((errors + 1))
}
+# Print a rule file's body with any leading YAML frontmatter stripped, so the
+# structural checks below see the Markdown regardless of whether the file
+# carries a `paths:` block. Claude Code reads the frontmatter; the heading check
+# should not care that it is there.
+md_body() {
+ awk 'NR==1 && $0=="---" {fm=1; next}
+ fm && $0=="---" {fm=0; next}
+ !fm {print}' "$1"
+}
+
check_md_heading() {
local f="$1"
[ -f "$f" ] || return 0
- if ! head -1 "$f" | grep -q '^# '; then
+ # First non-blank line, so a blank separator after frontmatter doesn't read as
+ # a missing heading.
+ if ! md_body "$f" | grep -m1 -v '^[[:space:]]*$' | grep -q '^# '; then
warn "$f — missing top-level heading"
fi
}
@@ -37,6 +49,27 @@ check_md_applies_to() {
fi
}
+# A rule whose prose declares a file-type scope must also carry `paths:`
+# frontmatter, or Claude Code loads it into every session regardless of what the
+# prose says. Three rules declared a narrow scope this way and were loaded
+# universally for as long as they shipped, because the declaration lived only in
+# a line the loader never reads. The prose is for the human; the frontmatter is
+# what actually scopes the load. Keep them saying the same thing.
+check_md_paths_frontmatter() {
+ local f="$1" applies
+ [ -f "$f" ] || return 0
+ applies=$(grep -m1 '^Applies to:' "$f" 2>/dev/null) || return 0
+ # A scope naming a concrete extension (`**/*.org`, `**/*.el`) is path-scopable.
+ # A bare `**/*` is genuinely universal and wants no frontmatter.
+ case "$applies" in
+ *'**/*.'*)
+ if ! head -1 "$f" | grep -q '^---$'; then
+ warn "$f — declares a file-type scope in prose but has no 'paths:' frontmatter; it loads in every session"
+ fi
+ ;;
+ esac
+}
+
check_hook() {
local f="$1"
[ -f "$f" ] || return 0
@@ -81,6 +114,7 @@ for f in claude-rules/*.md; do
[ -f "$f" ] || continue
check_md_heading "$f"
check_md_applies_to "$f"
+ check_md_paths_frontmatter "$f"
done
# Per-language rule files