aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/diff-lang.sh80
-rwxr-xr-xscripts/lint.sh91
2 files changed, 171 insertions, 0 deletions
diff --git a/scripts/diff-lang.sh b/scripts/diff-lang.sh
new file mode 100755
index 0000000..a72d2b9
--- /dev/null
+++ b/scripts/diff-lang.sh
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+# Diff installed rulesets in a target project vs the repo source.
+# Usage: diff-lang.sh <language> <project-path>
+#
+# Walks every file the installer would copy and shows a unified diff for
+# any that differ. Files missing in the target are flagged separately.
+
+set -u
+
+LANG="${1:-}"
+PROJECT="${2:-}"
+
+if [ -z "$LANG" ] || [ -z "$PROJECT" ]; then
+ echo "Usage: $0 <language> <project-path>" >&2
+ exit 1
+fi
+
+REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+SRC="$REPO_ROOT/languages/$LANG"
+
+[ -d "$SRC" ] || { echo "ERROR: no ruleset for '$LANG'" >&2; exit 1; }
+[ -d "$PROJECT" ] || { echo "ERROR: project path does not exist: $PROJECT" >&2; exit 1; }
+PROJECT="$(cd "$PROJECT" && pwd)"
+
+changed=0
+missing=0
+
+compare_file() {
+ local src="$1" dst="$2"
+ if [ ! -f "$dst" ]; then
+ echo "MISSING: $dst"
+ missing=$((missing + 1))
+ return
+ fi
+ if ! diff -q "$src" "$dst" >/dev/null 2>&1; then
+ echo "--- $src"
+ echo "+++ $dst"
+ diff -u "$src" "$dst" | tail -n +3
+ echo
+ changed=$((changed + 1))
+ fi
+}
+
+echo "Comparing '$LANG' ruleset against $PROJECT"
+echo
+
+# Generic rules (claude-rules/*.md → .claude/rules/)
+for f in "$REPO_ROOT/claude-rules"/*.md; do
+ [ -f "$f" ] || continue
+ name="$(basename "$f")"
+ compare_file "$f" "$PROJECT/.claude/rules/$name"
+done
+
+# Language .claude/ tree
+if [ -d "$SRC/claude" ]; then
+ while IFS= read -r f; do
+ rel="${f#$SRC/claude/}"
+ compare_file "$f" "$PROJECT/.claude/$rel"
+ done < <(find "$SRC/claude" -type f)
+fi
+
+# CLAUDE.md is seed-only (install won't overwrite without FORCE=1), so skip it
+# in normal diff output. Users can diff it manually if curious.
+
+# githooks/
+if [ -d "$SRC/githooks" ]; then
+ while IFS= read -r f; do
+ rel="${f#$SRC/githooks/}"
+ compare_file "$f" "$PROJECT/githooks/$rel"
+ done < <(find "$SRC/githooks" -type f)
+fi
+
+echo "---"
+if [ "$changed" -eq 0 ] && [ "$missing" -eq 0 ]; then
+ echo "No differences."
+else
+ echo "Summary: $changed differ, $missing missing."
+ [ "$changed" -gt 0 ] && exit 1
+ [ "$missing" -gt 0 ] && exit 2
+fi
diff --git a/scripts/lint.sh b/scripts/lint.sh
new file mode 100755
index 0000000..2956aff
--- /dev/null
+++ b/scripts/lint.sh
@@ -0,0 +1,91 @@
+#!/usr/bin/env bash
+# Validate ruleset structure. Runs from the rulesets repo root.
+# Checks:
+# - Every .md rule file starts with a top-level heading
+# - Every rule file has an 'Applies to:' header
+# - Every language CLAUDE.md has a top-level heading
+# - Every hook script has a shebang and is executable
+
+set -u
+
+REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$REPO_ROOT"
+
+errors=0
+
+warn() {
+ printf ' WARN: %s\n' "$1"
+ errors=$((errors + 1))
+}
+
+check_md_heading() {
+ local f="$1"
+ [ -f "$f" ] || return 0
+ if ! head -1 "$f" | grep -q '^# '; then
+ warn "$f — missing top-level heading"
+ fi
+}
+
+check_md_applies_to() {
+ local f="$1"
+ [ -f "$f" ] || return 0
+ if ! grep -q '^Applies to:' "$f"; then
+ warn "$f — missing 'Applies to:' header"
+ fi
+}
+
+check_hook() {
+ local f="$1"
+ [ -f "$f" ] || return 0
+ if ! head -1 "$f" | grep -q '^#!'; then
+ warn "$f — missing shebang"
+ fi
+ if [ ! -x "$f" ]; then
+ warn "$f — not executable (chmod +x)"
+ fi
+}
+
+echo "Linting rulesets in $REPO_ROOT"
+
+# Generic rules
+for f in claude-rules/*.md; do
+ [ -f "$f" ] || continue
+ check_md_heading "$f"
+ check_md_applies_to "$f"
+done
+
+# Per-language rule files
+for rules_dir in languages/*/claude/rules; do
+ [ -d "$rules_dir" ] || continue
+ for f in "$rules_dir"/*.md; do
+ [ -f "$f" ] || continue
+ check_md_heading "$f"
+ check_md_applies_to "$f"
+ done
+done
+
+# Per-language CLAUDE.md templates
+for claude_md in languages/*/CLAUDE.md; do
+ [ -f "$claude_md" ] || continue
+ check_md_heading "$claude_md"
+done
+
+# Hook scripts
+for h in languages/*/claude/hooks/*.sh languages/*/githooks/*; do
+ [ -f "$h" ] || continue
+ check_hook "$h"
+done
+
+# Shared install/diff/lint scripts (sanity check)
+for s in scripts/*.sh; do
+ [ -f "$s" ] || continue
+ check_hook "$s"
+done
+
+echo "---"
+if [ "$errors" -eq 0 ]; then
+ echo "All checks passed."
+else
+ echo "$errors warning(s)."
+ exit 1
+fi