aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/install-lang.sh11
-rwxr-xr-xscripts/sync-language-bundle.sh26
-rw-r--r--scripts/tests/install-lang.bats17
-rw-r--r--scripts/tests/sync-language-bundle.bats62
4 files changed, 116 insertions, 0 deletions
diff --git a/scripts/install-lang.sh b/scripts/install-lang.sh
index 4097bde..0fc9ea8 100755
--- a/scripts/install-lang.sh
+++ b/scripts/install-lang.sh
@@ -76,6 +76,17 @@ if [ -f "$SRC/CLAUDE.md" ]; then
fi
fi
+# 3b. coverage-makefile.txt — project-owned Makefile fragment, seed on first
+# install. Never overwrites (the project edits its own copy) unless FORCE=1.
+if [ -f "$SRC/coverage-makefile.txt" ]; then
+ if [ -f "$PROJECT/coverage-makefile.txt" ] && [ "$FORCE" != "1" ]; then
+ echo " [skip] coverage-makefile.txt already exists (use FORCE=1 to overwrite)"
+ else
+ cp "$SRC/coverage-makefile.txt" "$PROJECT/coverage-makefile.txt"
+ echo " [ok] coverage-makefile.txt installed (copy its targets into your Makefile)"
+ fi
+fi
+
# 4. .gitignore — append missing lines (deduped, skip comments)
if [ -f "$SRC/gitignore-add.txt" ]; then
touch "$PROJECT/.gitignore"
diff --git a/scripts/sync-language-bundle.sh b/scripts/sync-language-bundle.sh
index b2db8cb..25af11b 100755
--- a/scripts/sync-language-bundle.sh
+++ b/scripts/sync-language-bundle.sh
@@ -84,6 +84,22 @@ surface() {
MANUAL=$((MANUAL + 1))
}
+# inbox_drop SRC BASENAME — offer a project-owned file via the .ai/ inbox.
+# Only acts when the project uses the .ai/ inbox convention (so a bundle target
+# without .ai/ is never given an empty one). No-op once the project has already
+# adopted the file at its root or has a copy waiting in the inbox.
+inbox_drop() {
+ local src="$1" base="$2" inbox="$PROJECT/.ai/inbox"
+ [ -f "$src" ] || return 0
+ [ -d "$inbox" ] || return 0
+ [ -f "$PROJECT/$base" ] && return 0 # already adopted at project root
+ ls "$inbox"/*"$base" >/dev/null 2>&1 && return 0 # already waiting in inbox
+ cp "$src" "$inbox/from-rulesets-$base"
+ ensure_header
+ OUT+=" inbox .ai/inbox/from-rulesets-$base (project-owned — adopt deliberately)"$'\n'
+ FIXED=$((FIXED + 1))
+}
+
# process_bundle KIND SRC_DIR — reconcile one bundle (KIND is language|team).
# A team overlay owns only its own rule files; a language bundle also owns the
# shared generic rules, its hooks/githooks, and surfaces settings.json.
@@ -131,6 +147,12 @@ process_bundle() {
fix "$f" "$PROJECT/.claude/$rel" exec
done < <(find "$src/claude/hooks" -type f)
fi
+ if [ -d "$src/claude/scripts" ]; then
+ while IFS= read -r f; do
+ rel="${f#"$src"/claude/}"
+ fix "$f" "$PROJECT/.claude/$rel"
+ done < <(find "$src/claude/scripts" -type f)
+ fi
if [ -d "$src/githooks" ]; then
while IFS= read -r f; do
rel="${f#"$src"/githooks/}"
@@ -138,6 +160,10 @@ process_bundle() {
done < <(find "$src/githooks" -type f)
fi
surface "$src/claude/settings.json" "$PROJECT/.claude/settings.json"
+ # The Makefile fragment is project-owned: never auto-fix it, never edit the
+ # project Makefile. If the project uses the .ai/ inbox convention and hasn't
+ # already adopted the fragment, drop a copy there for deliberate adoption.
+ inbox_drop "$src/coverage-makefile.txt" "coverage-makefile.txt"
fi
if [ "$MANUAL" -gt "$manual_before" ]; then
diff --git a/scripts/tests/install-lang.bats b/scripts/tests/install-lang.bats
index 523be99..a26c3d5 100644
--- a/scripts/tests/install-lang.bats
+++ b/scripts/tests/install-lang.bats
@@ -25,6 +25,23 @@ teardown() {
[ -d "$PROJECT/.claude/rules" ]
[ -d "$PROJECT/githooks" ]
[ -f "$PROJECT/CLAUDE.md" ]
+ # The coverage-summary script ships inside the gitignored .claude footprint.
+ [ -f "$PROJECT/.claude/scripts/coverage-summary.el" ]
+}
+
+@test "install-lang elisp: seeds the project-owned coverage Makefile fragment" {
+ run bash "$INSTALL_LANG" elisp "$PROJECT"
+
+ [ "$status" -eq 0 ]
+ [ -f "$PROJECT/coverage-makefile.txt" ]
+}
+
+@test "install-lang elisp: does not overwrite an existing fragment without FORCE" {
+ echo "MY OWN VERSION" > "$PROJECT/coverage-makefile.txt"
+ run bash "$INSTALL_LANG" elisp "$PROJECT"
+
+ [ "$status" -eq 0 ]
+ grep -qxF "MY OWN VERSION" "$PROJECT/coverage-makefile.txt"
}
@test "install-lang elisp: gitignores the full Claude tooling footprint" {
diff --git a/scripts/tests/sync-language-bundle.bats b/scripts/tests/sync-language-bundle.bats
index e641646..5e3b912 100644
--- a/scripts/tests/sync-language-bundle.bats
+++ b/scripts/tests/sync-language-bundle.bats
@@ -29,6 +29,10 @@ install_bundle() {
mkdir -p "$proj/.claude/hooks"
cp -r "$REAL_REPO/languages/$lang/claude/hooks/." "$proj/.claude/hooks/"
fi
+ if [ -d "$REAL_REPO/languages/$lang/claude/scripts" ]; then
+ mkdir -p "$proj/.claude/scripts"
+ cp -r "$REAL_REPO/languages/$lang/claude/scripts/." "$proj/.claude/scripts/"
+ fi
if [ -f "$REAL_REPO/languages/$lang/claude/settings.json" ]; then
cp "$REAL_REPO/languages/$lang/claude/settings.json" "$proj/.claude/settings.json"
fi
@@ -120,6 +124,64 @@ install_team_overlay() {
[ -x "$PROJ/.claude/hooks/validate-el.sh" ]
}
+# --- Auto-fix: .claude/scripts ---
+
+@test "sync: drifted bundle script is auto-fixed and restored" {
+ install_bundle elisp "$PROJ"
+ echo ";; junk drift" >> "$PROJ/.claude/scripts/coverage-summary.el"
+ run bash "$SCRIPT" "$PROJ"
+ [ "$status" -eq 0 ]
+ [[ "$output" == *".claude/scripts/coverage-summary.el"* ]]
+ matches_canonical ".claude/scripts/coverage-summary.el" "$REAL_REPO/languages/elisp/claude/scripts/coverage-summary.el"
+}
+
+@test "sync: missing bundle script is re-copied" {
+ install_bundle elisp "$PROJ"
+ rm "$PROJ/.claude/scripts/coverage-summary.el"
+ run bash "$SCRIPT" "$PROJ"
+ [ "$status" -eq 0 ]
+ [[ "$output" == *".claude/scripts/coverage-summary.el"* ]]
+ [ -f "$PROJ/.claude/scripts/coverage-summary.el" ]
+}
+
+# --- Project-owned: Makefile fragment via inbox ---
+
+@test "sync: coverage Makefile fragment is dropped into .ai/inbox when present" {
+ install_bundle elisp "$PROJ"
+ mkdir -p "$PROJ/.ai/inbox"
+ run bash "$SCRIPT" "$PROJ"
+ [ "$status" -eq 0 ]
+ [[ "$output" == *"inbox"* ]]
+ [ -f "$PROJ/.ai/inbox/from-rulesets-coverage-makefile.txt" ]
+ matches_canonical ".ai/inbox/from-rulesets-coverage-makefile.txt" "$REAL_REPO/languages/elisp/coverage-makefile.txt"
+}
+
+@test "sync: no .ai/inbox means no fragment drop and no empty .ai/ created" {
+ install_bundle elisp "$PROJ"
+ run bash "$SCRIPT" "$PROJ"
+ [ "$status" -eq 0 ]
+ [[ "$output" != *"inbox"* ]]
+ [ ! -d "$PROJ/.ai" ]
+}
+
+@test "sync: fragment already adopted at project root is not re-dropped" {
+ install_bundle elisp "$PROJ"
+ mkdir -p "$PROJ/.ai/inbox"
+ cp "$REAL_REPO/languages/elisp/coverage-makefile.txt" "$PROJ/coverage-makefile.txt"
+ run bash "$SCRIPT" "$PROJ"
+ [ "$status" -eq 0 ]
+ [ ! -f "$PROJ/.ai/inbox/from-rulesets-coverage-makefile.txt" ]
+}
+
+@test "sync: fragment already waiting in inbox is not duplicated" {
+ install_bundle elisp "$PROJ"
+ mkdir -p "$PROJ/.ai/inbox"
+ cp "$REAL_REPO/languages/elisp/coverage-makefile.txt" "$PROJ/.ai/inbox/from-rulesets-coverage-makefile.txt"
+ run bash "$SCRIPT" "$PROJ"
+ [ "$status" -eq 0 ]
+ [[ "$output" != *"inbox"* ]]
+}
+
# --- Surface-only: settings.json ---
@test "sync: drifted settings.json is surfaced, NOT modified, exit 3" {