aboutsummaryrefslogtreecommitdiff
path: root/scripts/install-lang.sh
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/install-lang.sh')
-rwxr-xr-xscripts/install-lang.sh69
1 files changed, 69 insertions, 0 deletions
diff --git a/scripts/install-lang.sh b/scripts/install-lang.sh
index 2f38fcd..6e8d806 100755
--- a/scripts/install-lang.sh
+++ b/scripts/install-lang.sh
@@ -33,6 +33,75 @@ fi
# Resolve to absolute path
PROJECT="$(cd "$PROJECT" && pwd)"
+# 0. Cross-bundle collision guard.
+#
+# Several bundles ship a file at the same path. gitignore-add.txt is appended
+# and deduped, and CLAUDE.md is seed-only, so both compose across bundles. Three
+# do not: claude/settings.json and githooks/* are installed with `cp -rT`
+# (always overwrite), and coverage-makefile.txt is seeded under a fixed name. So
+# installing a second bundle replaced the first's hook wiring and pre-commit
+# while printing [ok], so a project could lose its paren check or secret scan
+# and read the output as success. Refuse instead, naming what would go.
+#
+# Detection is by rule fingerprint, matching sync-language-bundle.sh: a project
+# has bundle X iff one of X's own rule files is in .claude/rules/. No marker
+# file, so this works on installs that predate the guard.
+project_has_bundle() {
+ local b="$1" rf
+ for rf in "$REPO_ROOT/languages/$b/claude/rules"/*.md; do
+ [ -f "$rf" ] || continue
+ [ -f "$PROJECT/.claude/rules/$(basename "$rf")" ] && return 0
+ done
+ return 1
+}
+
+# Files $1's bundle and $LANG both ship, and that install would overwrite.
+shared_overwritten_files() {
+ local other="$1" rel
+ for rel in claude/settings.json coverage-makefile.txt; do
+ [ -f "$SRC/$rel" ] && [ -f "$REPO_ROOT/languages/$other/$rel" ] \
+ && echo " $(printf '%s' "$rel" | sed 's|^claude/|.claude/|')"
+ done
+ if [ -d "$SRC/githooks" ] && [ -d "$REPO_ROOT/languages/$other/githooks" ]; then
+ for rel in "$SRC/githooks"/*; do
+ [ -f "$rel" ] || continue
+ [ -f "$REPO_ROOT/languages/$other/githooks/$(basename "$rel")" ] \
+ && echo " githooks/$(basename "$rel")"
+ done
+ fi
+}
+
+if [ "$FORCE" != "1" ]; then
+ collisions=""
+ for other_dir in "$REPO_ROOT/languages"/*/; do
+ other="$(basename "$other_dir")"
+ [ "$other" = "$LANG" ] && continue
+ [ -d "$other_dir/claude/rules" ] || continue
+ project_has_bundle "$other" || continue
+ files="$(shared_overwritten_files "$other")"
+ [ -n "$files" ] && collisions="${collisions}The '$other' bundle is already installed here. Installing '$LANG' would replace:
+${files}
+"
+ done
+
+ if [ -n "$collisions" ]; then
+ {
+ echo "ERROR: bundle collision. Refusing to install '$LANG' into $PROJECT"
+ echo
+ printf '%s' "$collisions"
+ echo "Both bundles ship these files, and installing overwrites rather than merges,"
+ echo "so the bundle already here would silently lose them."
+ echo
+ echo "Whether a project can carry two bundles at once is an open question."
+ echo "Until it's settled, install one bundle per project."
+ echo
+ echo "To override: re-run with FORCE=1. That also re-seeds CLAUDE.md from the"
+ echo "bundle template, which overwrites any project-specific edits to it."
+ } >&2
+ exit 1
+ fi
+fi
+
echo "Installing '$LANG' ruleset into $PROJECT"
# 1. Generic rules from claude-rules/ (shared across all languages)