diff --git a/skills/setup.sh b/skills/setup.sh index ec5512e8c5..c24706d718 100755 --- a/skills/setup.sh +++ b/skills/setup.sh @@ -1,10 +1,10 @@ #!/bin/bash # Setup AI Skills for Prowler development # Configures AI coding assistants that follow agentskills.io standard: -# - Claude Code: .claude/skills/ symlink + CLAUDE.md copies -# - Gemini CLI: .gemini/skills/ symlink + GEMINI.md copies +# - Claude Code: .claude/skills/ symlink + CLAUDE.md symlink +# - Gemini CLI: .gemini/skills/ symlink + GEMINI.md symlink # - Codex (OpenAI): .codex/skills/ symlink + AGENTS.md (native) -# - GitHub Copilot: .github/copilot-instructions.md copy +# - GitHub Copilot: .github/copilot-instructions.md symlink # # Usage: # ./setup.sh # Interactive mode (select AI assistants) @@ -37,6 +37,28 @@ SETUP_COPILOT=false # HELPER FUNCTIONS # ============================================================================= +add_to_gitignore() { + local pattern="$1" + local gitignore_file="$REPO_ROOT/.gitignore" + local header="# AI Coding assistants assets" + + # Create .gitignore if it doesn't exist + if [ ! -f "$gitignore_file" ]; then + touch "$gitignore_file" + fi + + # Check if pattern exists (exact match or at end of file) + if ! grep -qxF "$pattern" "$gitignore_file"; then + # Check if header exists + if ! grep -qxF "$header" "$gitignore_file"; then + echo -e "\n\n$header" >> "$gitignore_file" + fi + + echo "$pattern" >> "$gitignore_file" + echo -e "${GREEN} ✓ Added $pattern to .gitignore${NC}" + fi +} + show_help() { echo "Usage: $0 [OPTIONS]" echo "" @@ -109,6 +131,7 @@ setup_claude() { if [ ! -d "$REPO_ROOT/.claude" ]; then mkdir -p "$REPO_ROOT/.claude" fi + add_to_gitignore ".claude/skills" if [ -L "$target" ]; then rm "$target" @@ -119,8 +142,9 @@ setup_claude() { ln -s "$SKILLS_SOURCE" "$target" echo -e "${GREEN} ✓ .claude/skills -> skills/${NC}" - # Copy AGENTS.md to CLAUDE.md - copy_agents_md "CLAUDE.md" + # Link AGENTS.md to CLAUDE.md + link_agents_md "CLAUDE.md" + add_to_gitignore "CLAUDE.md" } setup_gemini() { @@ -129,6 +153,7 @@ setup_gemini() { if [ ! -d "$REPO_ROOT/.gemini" ]; then mkdir -p "$REPO_ROOT/.gemini" fi + add_to_gitignore ".gemini/skills" if [ -L "$target" ]; then rm "$target" @@ -139,8 +164,9 @@ setup_gemini() { ln -s "$SKILLS_SOURCE" "$target" echo -e "${GREEN} ✓ .gemini/skills -> skills/${NC}" - # Copy AGENTS.md to GEMINI.md - copy_agents_md "GEMINI.md" + # Link AGENTS.md to GEMINI.md + link_agents_md "GEMINI.md" + add_to_gitignore "GEMINI.md" } setup_codex() { @@ -149,6 +175,7 @@ setup_codex() { if [ ! -d "$REPO_ROOT/.codex" ]; then mkdir -p "$REPO_ROOT/.codex" fi + add_to_gitignore ".codex/skills" if [ -L "$target" ]; then rm "$target" @@ -164,12 +191,19 @@ setup_codex() { setup_copilot() { if [ -f "$REPO_ROOT/AGENTS.md" ]; then mkdir -p "$REPO_ROOT/.github" - cp "$REPO_ROOT/AGENTS.md" "$REPO_ROOT/.github/copilot-instructions.md" + + # Link AGENTS.md -> .github/copilot-instructions.md + local target="$REPO_ROOT/.github/copilot-instructions.md" + ln -sf "../AGENTS.md" "$target" + echo -e "${GREEN} ✓ AGENTS.md -> .github/copilot-instructions.md${NC}" + + # Add specifically the file, NOT the .github folder + add_to_gitignore ".github/copilot-instructions.md" fi } -copy_agents_md() { +link_agents_md() { local target_name="$1" local agents_files local count=0 @@ -179,11 +213,15 @@ copy_agents_md() { for agents_file in $agents_files; do local agents_dir agents_dir=$(dirname "$agents_file") - cp "$agents_file" "$agents_dir/$target_name" + + # Create relative symlink + # Since files are in same dir, we can just link to basename + (cd "$agents_dir" && ln -sf "$(basename "$agents_file")" "$target_name") + count=$((count + 1)) done - echo -e "${GREEN} ✓ Copied $count AGENTS.md -> $target_name${NC}" + echo -e "${GREEN} ✓ Linked $count AGENTS.md -> $target_name${NC}" } # ============================================================================= @@ -302,4 +340,4 @@ echo "Configured:" [ "$SETUP_COPILOT" = true ] && echo " • GitHub Copilot: .github/copilot-instructions.md" echo "" echo -e "${BLUE}Note: Restart your AI assistant to load the skills.${NC}" -echo -e "${BLUE} AGENTS.md is the source of truth - edit it, then re-run this script.${NC}" +echo -e "${BLUE} AGENTS.md is the source of truth - changes are reflected automatically via symlinks.${NC}" diff --git a/skills/setup_test.sh b/skills/setup_test.sh index c0e80afe99..db4749ed3f 100755 --- a/skills/setup_test.sh +++ b/skills/setup_test.sh @@ -201,40 +201,40 @@ test_symlink_not_created_without_flag() { } # ============================================================================= -# TESTS: AGENTS.md COPYING +# TESTS: AGENTS.md LINKING # ============================================================================= -test_copy_claude_agents_md() { +test_link_claude_agents_md() { run_setup --claude > /dev/null - assert_file_exists "$TEST_DIR/CLAUDE.md" "Root CLAUDE.md should exist" && \ - assert_file_exists "$TEST_DIR/api/CLAUDE.md" "api/CLAUDE.md should exist" && \ - assert_file_exists "$TEST_DIR/ui/CLAUDE.md" "ui/CLAUDE.md should exist" + assert_symlink_exists "$TEST_DIR/CLAUDE.md" "Root CLAUDE.md should be a symlink" && \ + assert_symlink_exists "$TEST_DIR/api/CLAUDE.md" "api/CLAUDE.md should be a symlink" && \ + assert_symlink_exists "$TEST_DIR/ui/CLAUDE.md" "ui/CLAUDE.md should be a symlink" } -test_copy_gemini_agents_md() { +test_link_gemini_agents_md() { run_setup --gemini > /dev/null - assert_file_exists "$TEST_DIR/GEMINI.md" "Root GEMINI.md should exist" && \ - assert_file_exists "$TEST_DIR/api/GEMINI.md" "api/GEMINI.md should exist" && \ - assert_file_exists "$TEST_DIR/ui/GEMINI.md" "ui/GEMINI.md should exist" + assert_symlink_exists "$TEST_DIR/GEMINI.md" "Root GEMINI.md should be a symlink" && \ + assert_symlink_exists "$TEST_DIR/api/GEMINI.md" "api/GEMINI.md should be a symlink" && \ + assert_symlink_exists "$TEST_DIR/ui/GEMINI.md" "ui/GEMINI.md should be a symlink" } -test_copy_copilot_to_github() { +test_link_copilot_to_github() { run_setup --copilot > /dev/null - assert_file_exists "$TEST_DIR/.github/copilot-instructions.md" "Copilot instructions should exist" + assert_symlink_exists "$TEST_DIR/.github/copilot-instructions.md" "Copilot instructions should be a symlink" } -test_copy_codex_no_extra_files() { +test_link_codex_no_extra_files() { run_setup --codex > /dev/null assert_file_not_exists "$TEST_DIR/CODEX.md" "CODEX.md should not be created" } -test_copy_not_created_without_flag() { +test_link_not_created_without_flag() { run_setup --codex > /dev/null - assert_file_not_exists "$TEST_DIR/CLAUDE.md" "CLAUDE.md should not exist" && \ - assert_file_not_exists "$TEST_DIR/GEMINI.md" "GEMINI.md should not exist" + assert_symlink_not_exists "$TEST_DIR/CLAUDE.md" "CLAUDE.md should not exist" && \ + assert_symlink_not_exists "$TEST_DIR/GEMINI.md" "GEMINI.md should not exist" } -test_copy_content_matches_source() { +test_link_content_matches_source() { run_setup --claude > /dev/null local source_content target_content source_content=$(cat "$TEST_DIR/AGENTS.md") @@ -272,7 +272,7 @@ test_idempotent_multiple_runs() { run_setup --claude > /dev/null run_setup --claude > /dev/null assert_symlink_exists "$TEST_DIR/.claude/skills" "Symlink should still exist after second run" && \ - assert_file_exists "$TEST_DIR/CLAUDE.md" "CLAUDE.md should still exist after second run" + assert_symlink_exists "$TEST_DIR/CLAUDE.md" "CLAUDE.md should still be a symlink after second run" } # =============================================================================