aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-04-19 11:57:23 -0500
committerCraig Jennings <c@cjennings.net>2026-04-19 11:57:23 -0500
commit18fcaf9f27d03849487078b30f667c3b574e6554 (patch)
tree67e3ede717fbf9fbfe10c46042451e15abeeb91f /scripts
parentf8c593791ae051b07dba2606c18f1deb7589825e (diff)
downloadrulesets-18fcaf9f27d03849487078b30f667c3b574e6554.tar.gz
rulesets-18fcaf9f27d03849487078b30f667c3b574e6554.zip
feat: add per-project language bundles + elisp ruleset
Introduces a second install mode alongside the existing global symlinks: per-project language bundles that copy a language-specific Claude Code setup (rules, hooks, settings, pre-commit) into a target project. Layout additions: languages/elisp/ - Emacs Lisp bundle (rules, hooks, settings, CLAUDE.md) scripts/install-lang.sh - shared install logic Makefile additions: make help - unified help text make install-lang LANG=<lang> PROJECT=<path> [FORCE=1] make install-elisp PROJECT=<path> [FORCE=1] (shortcut) make list-languages - show available bundles Elisp bundle contents: - CLAUDE.md template (seed on first install, preserved on update) - .claude/rules/elisp.md, elisp-testing.md, verification.md - .claude/hooks/validate-el.sh (check-parens, byte-compile, run matching tests) - .claude/settings.json (permission allowlist, hook wiring) - githooks/pre-commit (secret scan + staged-file paren check) - gitignore-add.txt (append .claude/settings.local.json) Hooks use \$CLAUDE_PROJECT_DIR with a script-relative fallback, so the same bundle works on any machine or clone path. Install activates git hooks via core.hooksPath=githooks automatically. Re-running install is idempotent; CLAUDE.md is never overwritten without FORCE=1.
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/install-lang.sh97
1 files changed, 97 insertions, 0 deletions
diff --git a/scripts/install-lang.sh b/scripts/install-lang.sh
new file mode 100755
index 0000000..f9b7a31
--- /dev/null
+++ b/scripts/install-lang.sh
@@ -0,0 +1,97 @@
+#!/usr/bin/env bash
+# Install a language ruleset into a target project.
+# Usage: install-lang.sh <language> <project-path> [force]
+#
+# Copies the language's ruleset files into the project. Re-runnable
+# (authoritative source overwrites). CLAUDE.md is preserved unless
+# force=1, to avoid trampling project-specific customizations.
+
+set -euo pipefail
+
+LANG="${1:-}"
+PROJECT="${2:-}"
+FORCE="${3:-}"
+
+if [ -z "$LANG" ] || [ -z "$PROJECT" ]; then
+ echo "Usage: $0 <language> <project-path> [force]" >&2
+ exit 1
+fi
+
+REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+SRC="$REPO_ROOT/languages/$LANG"
+
+if [ ! -d "$SRC" ]; then
+ echo "ERROR: no ruleset for language '$LANG' (expected $SRC)" >&2
+ exit 1
+fi
+
+if [ ! -d "$PROJECT" ]; then
+ echo "ERROR: project path does not exist: $PROJECT" >&2
+ exit 1
+fi
+
+# Resolve to absolute path
+PROJECT="$(cd "$PROJECT" && pwd)"
+
+echo "Installing '$LANG' ruleset into $PROJECT"
+
+# 1. .claude/ — rules, hooks, settings (authoritative, always overwrite)
+if [ -d "$SRC/claude" ]; then
+ mkdir -p "$PROJECT/.claude"
+ cp -rT "$SRC/claude" "$PROJECT/.claude"
+ if [ -d "$PROJECT/.claude/hooks" ]; then
+ find "$PROJECT/.claude/hooks" -type f -name '*.sh' -exec chmod +x {} \;
+ fi
+ echo " [ok] .claude/ installed"
+fi
+
+# 2. githooks/ — pre-commit etc.
+if [ -d "$SRC/githooks" ]; then
+ mkdir -p "$PROJECT/githooks"
+ cp -rT "$SRC/githooks" "$PROJECT/githooks"
+ find "$PROJECT/githooks" -type f -exec chmod +x {} \;
+ if [ -d "$PROJECT/.git" ]; then
+ git -C "$PROJECT" config core.hooksPath githooks
+ echo " [ok] githooks/ installed, core.hooksPath=githooks"
+ else
+ echo " [ok] githooks/ installed (not a git repo — skipped core.hooksPath)"
+ fi
+fi
+
+# 3. CLAUDE.md — seed on first install, don't overwrite unless FORCE=1
+if [ -f "$SRC/CLAUDE.md" ]; then
+ if [ -f "$PROJECT/CLAUDE.md" ] && [ "$FORCE" != "1" ]; then
+ echo " [skip] CLAUDE.md already exists (use FORCE=1 to overwrite)"
+ else
+ cp "$SRC/CLAUDE.md" "$PROJECT/CLAUDE.md"
+ echo " [ok] CLAUDE.md installed"
+ fi
+fi
+
+# 4. .gitignore — append missing lines (deduped, skip comments)
+if [ -f "$SRC/gitignore-add.txt" ]; then
+ touch "$PROJECT/.gitignore"
+ added=0
+ while IFS= read -r line || [ -n "$line" ]; do
+ # Skip blank lines and comments in the source file
+ [ -z "$line" ] && continue
+ case "$line" in \#*) continue ;; esac
+ # Only add if not already present
+ if ! grep -qxF "$line" "$PROJECT/.gitignore"; then
+ # If this is the first line we're adding, prepend a header
+ if [ "$added" -eq 0 ]; then
+ printf '\n# --- %s ruleset ---\n' "$LANG" >> "$PROJECT/.gitignore"
+ fi
+ echo "$line" >> "$PROJECT/.gitignore"
+ added=$((added + 1))
+ fi
+ done < "$SRC/gitignore-add.txt"
+ if [ "$added" -gt 0 ]; then
+ echo " [ok] .gitignore: $added line(s) added"
+ else
+ echo " [skip] .gitignore entries already present"
+ fi
+fi
+
+echo ""
+echo "Install complete."