Commit 6765f9d06ae7

Vincent Demeester <vincent@sbr.pm>
2026-02-02 15:36:47
feat: Add agent-skills centralized project configuration system
Implement tool-agnostic skill management for AI coding assistants. - Add agent-skills configuration directory with projects.toml - Create agent-skill-manager tool for syncing project-specific skills - Integrate with dotfiles Makefile for installation - Add .claude/skills/ to gitignore (auto-synced, never committed) Skills are mapped to projects by git remote URL or path patterns. The tool copies required skills from ~/.config/claude/skills/ to project-local .claude/skills/ based on the central configuration. Works with Claude Code, and designed to support future tools (GitHub Copilot, OpenCode, pi, etc.) through shared config. Configured projects: - home (*/home): Homelab, Nix, Git, SystematicDebugging, CORE - nixpkgs: Nixpkgs, Nix, Git, SystematicDebugging, CORE - tektoncd/*: Tekton, Kubernetes, golang, Git, GitHub, TDD, CORE - Work projects (redhat/openshift/konflux): Jira, Tekton, K8s, CORE - Emacs: EmacsLisp, Git, CORE - Personal Go projects: golang, Git, GitHub, TDD, CORE
1 parent f9d4c13
dots/.config/agent-skills/agent-skill-manager
@@ -0,0 +1,461 @@
+#!/usr/bin/env bash
+# agent-skill-manager - Manage project-specific agent skills
+#
+# Usage:
+#   agent-skill-manager init [dir]      Initialize skills for project
+#   agent-skill-manager sync [dir]      Sync skills from config
+#   agent-skill-manager clean [dir]     Remove project skills
+#   agent-skill-manager status [dir]    Show active skills
+#   agent-skill-manager detect [dir]    Detect project config
+
+set -euo pipefail
+
+GLOBAL_SKILLS_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/claude/skills"
+PROJECTS_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/agent-skills/projects.toml"
+SKILLS_DIR=".claude/skills"
+
+# Colors
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m'
+
+log_info() { echo -e "${BLUE}ℹ${NC} $*"; }
+log_success() { echo -e "${GREEN}✓${NC} $*"; }
+log_warning() { echo -e "${YELLOW}⚠${NC} $*"; }
+log_error() { echo -e "${RED}✗${NC} $*" >&2; }
+
+# Detect project configuration by git remote or path
+detect_project() {
+    local project_dir="${1:-.}"
+    
+    cd "$project_dir" || exit 1
+    
+    if [[ ! -f "$PROJECTS_CONFIG" ]]; then
+        log_error "Projects config not found: $PROJECTS_CONFIG"
+        return 1
+    fi
+    
+    # Try git remote first
+    if git rev-parse --git-dir >/dev/null 2>&1; then
+        local remote_url
+        remote_url=$(git remote get-url origin 2>/dev/null || echo "")
+        
+        if [[ -n "$remote_url" ]]; then
+            # Normalize URL
+            remote_url=$(echo "$remote_url" | sed 's/\.git$//' | sed 's|git@github.com:|github.com/|' | sed 's|https://||')
+            
+            # Check patterns
+            local project_key
+            project_key=$(awk -F'"' '/^\[project\."/ {print $2}' "$PROJECTS_CONFIG" | while read -r pattern; do
+                local regex="${pattern//\*/.*}"
+                
+                if [[ "$remote_url" =~ $regex ]]; then
+                    echo "$pattern"
+                    break
+                fi
+            done | head -1)
+            
+            if [[ -n "$project_key" ]]; then
+                echo "$project_key"
+                return 0
+            fi
+        fi
+    fi
+    
+    # Try path-based matching
+    local current_path
+    current_path=$(pwd)
+    
+    local project_key
+    project_key=$(awk -F'"' '/^\[project\."/ {print $2}' "$PROJECTS_CONFIG" | while read -r pattern; do
+        if [[ "$pattern" == *"*"* ]]; then
+            local regex="${pattern//\*/.*}"
+            
+            if [[ "$current_path" =~ $regex ]]; then
+                echo "$pattern"
+                break
+            fi
+        fi
+    done | head -1)
+    
+    if [[ -n "$project_key" ]]; then
+        echo "$project_key"
+        return 0
+    fi
+    
+    return 1
+}
+
+# Parse required skills for a project from central config
+parse_project_skills() {
+    local project_key="$1"
+    local in_project=0
+    local in_array=0
+    local skills=""
+    
+    while IFS= read -r line; do
+        # Remove comments and trim
+        line=$(echo "$line" | sed 's/#.*//' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
+        
+        [[ -z "$line" ]] && continue
+        
+        # Check for project section
+        if [[ "$line" == "[project.\"$project_key\"]" ]]; then
+            in_project=1
+            continue
+        fi
+        
+        # Exit project section on next section header
+        if [[ $in_project -eq 1 ]] && [[ "$line" =~ ^\[.*\]$ ]]; then
+            break
+        fi
+        
+        # Start of required_skills array
+        if [[ $in_project -eq 1 ]] && [[ "$line" =~ ^required_skills[[:space:]]*=[[:space:]]*\[(.*) ]]; then
+            skills="${BASH_REMATCH[1]}"
+            # Check if array closes on same line
+            if [[ "$skills" =~ (.*)\][[:space:]]*$ ]]; then
+                # Single line array
+                skills="${BASH_REMATCH[1]}"
+                echo "$skills" | tr ',' '\n' | sed 's/^[[:space:]]*"//' | sed 's/"[[:space:]]*$//'
+                return 0
+            else
+                # Multi-line array - continue collecting
+                in_array=1
+                continue
+            fi
+        fi
+        
+        # Continue collecting array elements
+        if [[ $in_array -eq 1 ]]; then
+            if [[ "$line" =~ (.*)\][[:space:]]*$ ]]; then
+                # End of array
+                skills="$skills,${BASH_REMATCH[1]}"
+                echo "$skills" | tr ',' '\n' | sed 's/^[[:space:]]*"//' | sed 's/"[[:space:]]*$//' | sed '/^$/d'
+                return 0
+            else
+                # More array elements
+                skills="$skills,$line"
+            fi
+        fi
+    done < "$PROJECTS_CONFIG"
+}
+
+# Get project name from config
+get_project_name() {
+    local project_key="$1"
+    local in_project=0
+    
+    while IFS= read -r line; do
+        line=$(echo "$line" | sed 's/#.*//' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
+        [[ -z "$line" ]] && continue
+        
+        if [[ "$line" == "[project.\"$project_key\"]" ]]; then
+            in_project=1
+            continue
+        fi
+        
+        if [[ $in_project -eq 1 ]] && [[ "$line" =~ ^\[.*\]$ ]]; then
+            break
+        fi
+        
+        if [[ $in_project -eq 1 ]] && [[ "$line" =~ ^name[[:space:]]*=[[:space:]]*\"(.*)\" ]]; then
+            echo "${BASH_REMATCH[1]}"
+            return 0
+        fi
+    done < "$PROJECTS_CONFIG"
+    
+    echo "$project_key"
+}
+
+# Initialize project skills
+init_project_skills() {
+    local project_dir="${1:-.}"
+    
+    cd "$project_dir" || exit 1
+    
+    local project_key
+    project_key=$(detect_project "$project_dir") || {
+        log_error "No project configuration found for: $project_dir"
+        log_info "Add project to $PROJECTS_CONFIG"
+        return 1
+    }
+    
+    local project_name
+    project_name=$(get_project_name "$project_key")
+    
+    log_info "Initializing skills for: $project_name"
+    
+    mkdir -p "$SKILLS_DIR"
+    
+    local skills
+    mapfile -t skills < <(parse_project_skills "$project_key")
+    
+    if [[ ${#skills[@]} -eq 0 ]]; then
+        log_warning "No required skills found for: $project_name"
+        return 0
+    fi
+    
+    local copied=0
+    local skipped=0
+    
+    for skill in "${skills[@]}"; do
+        [[ -z "$skill" ]] && continue
+        
+        local source="$GLOBAL_SKILLS_DIR/$skill"
+        local target="$SKILLS_DIR/$skill"
+        
+        if [[ ! -d "$source" ]]; then
+            log_warning "Skill not found: $skill"
+            skipped=$((skipped + 1))
+            continue
+        fi
+        
+        if [[ -d "$target" ]]; then
+            log_info "Skill already exists: $skill"
+            skipped=$((skipped + 1))
+            continue
+        fi
+        
+        cp -r "$source" "$target"
+        log_success "Copied: $skill"
+        copied=$((copied + 1))
+    done
+    
+    log_success "Initialized $copied skills, skipped $skipped"
+    ensure_gitignore
+}
+
+# Sync skills
+sync_project_skills() {
+    local project_dir="${1:-.}"
+    
+    cd "$project_dir" || exit 1
+    
+    local project_key
+    project_key=$(detect_project "$project_dir") || {
+        return 0
+    }
+    
+    local project_name
+    project_name=$(get_project_name "$project_key")
+    
+    log_info "Syncing skills for: $project_name"
+    
+    local required_skills
+    mapfile -t required_skills < <(parse_project_skills "$project_key")
+    
+    declare -A required_map
+    for skill in "${required_skills[@]}"; do
+        [[ -z "$skill" ]] && continue
+        required_map["$skill"]=1
+    done
+    
+    for skill in "${required_skills[@]}"; do
+        [[ -z "$skill" ]] && continue
+        
+        local source="$GLOBAL_SKILLS_DIR/$skill"
+        local target="$SKILLS_DIR/$skill"
+        
+        if [[ ! -d "$source" ]]; then
+            log_warning "Skill not found: $skill"
+            continue
+        fi
+        
+        if [[ -d "$target" ]]; then
+            rsync -a --delete "$source/" "$target/"
+            log_success "Updated: $skill"
+        else
+            mkdir -p "$SKILLS_DIR"
+            cp -r "$source" "$target"
+            log_success "Added: $skill"
+        fi
+    done
+    
+    if [[ -d "$SKILLS_DIR" ]]; then
+        for existing in "$SKILLS_DIR"/*; do
+            [[ ! -d "$existing" ]] && continue
+            
+            local skill_name
+            skill_name=$(basename "$existing")
+            
+            if [[ ! -v required_map["$skill_name"] ]]; then
+                rm -rf "$existing"
+                log_info "Removed: $skill_name"
+            fi
+        done
+    fi
+    
+    ensure_gitignore
+}
+
+# Clean project skills
+clean_project_skills() {
+    local project_dir="${1:-.}"
+    
+    cd "$project_dir" || exit 1
+    
+    if [[ ! -d "$SKILLS_DIR" ]]; then
+        log_info "No skills directory found"
+        return 0
+    fi
+    
+    log_info "Cleaning skills"
+    rm -rf "$SKILLS_DIR"
+    log_success "Removed all project skills"
+}
+
+# Show status
+show_status() {
+    local project_dir="${1:-.}"
+    
+    cd "$project_dir" || exit 1
+    
+    echo "Project: $project_dir"
+    echo
+    
+    local project_key
+    project_key=$(detect_project "$project_dir") || {
+        echo "No project configuration found"
+        echo
+        if [[ -d "$SKILLS_DIR" ]]; then
+            echo "Project-local skills (manually managed):"
+            for skill in "$SKILLS_DIR"/*; do
+                [[ ! -d "$skill" ]] && continue
+                echo "  • $(basename "$skill")"
+            done
+        fi
+        return 0
+    }
+    
+    local project_name
+    project_name=$(get_project_name "$project_key")
+    
+    echo "Project: $project_name"
+    echo "Config: $project_key"
+    echo
+    
+    echo "Required skills:"
+    local skills
+    mapfile -t skills < <(parse_project_skills "$project_key")
+    
+    for skill in "${skills[@]}"; do
+        [[ -z "$skill" ]] && continue
+        
+        local source="$GLOBAL_SKILLS_DIR/$skill"
+        local target="$SKILLS_DIR/$skill"
+        
+        if [[ -d "$target" ]]; then
+            echo -e "  ${GREEN}✓${NC} $skill (active)"
+        else
+            if [[ -d "$source" ]]; then
+                echo -e "  ${YELLOW}○${NC} $skill (available)"
+            else
+                echo -e "  ${RED}✗${NC} $skill (not found)"
+            fi
+        fi
+    done
+}
+
+# Show detected project
+show_detect() {
+    local project_dir="${1:-.}"
+    
+    cd "$project_dir" || exit 1
+    
+    local project_key
+    project_key=$(detect_project "$project_dir") || {
+        echo "No project configuration detected"
+        return 1
+    }
+    
+    local project_name
+    project_name=$(get_project_name "$project_key")
+    
+    echo "Detected project: $project_name"
+    echo "Config key: $project_key"
+    echo "Required skills:"
+    parse_project_skills "$project_key" | while read -r skill; do
+        echo "  - $skill"
+    done
+}
+
+# Ensure gitignore
+ensure_gitignore() {
+    local gitignore=".gitignore"
+    
+    if [[ ! -f "$gitignore" ]]; then
+        echo ".claude/skills/" > "$gitignore"
+        log_success "Created .gitignore"
+    elif ! grep -q "^.claude/skills/" "$gitignore" 2>/dev/null; then
+        echo ".claude/skills/" >> "$gitignore"
+        log_success "Added .claude/skills/ to .gitignore"
+    fi
+}
+
+# Main
+main() {
+    local command="${1:-}"
+    shift || true
+    
+    case "$command" in
+        init)
+            init_project_skills "$@"
+            ;;
+        sync)
+            sync_project_skills "$@"
+            ;;
+        clean)
+            clean_project_skills "$@"
+            ;;
+        status)
+            show_status "$@"
+            ;;
+        detect)
+            show_detect "$@"
+            ;;
+        ""|--help|-h)
+            cat <<EOF
+agent-skill-manager - Manage project-specific Claude skills
+
+Usage:
+  agent-skill-manager init [dir]      Initialize skills for project
+  agent-skill-manager sync [dir]      Sync skills (update/add/remove)
+  agent-skill-manager clean [dir]     Remove all project skills
+  agent-skill-manager status [dir]    Show skill status
+  agent-skill-manager detect [dir]    Detect project configuration
+
+Configuration:
+  Central config: $PROJECTS_CONFIG
+  Global skills: $GLOBAL_SKILLS_DIR
+  Project skills: .claude/skills/ (gitignored)
+
+The central config maps git remotes and paths to required skills.
+Skills are automatically detected and synced based on your location.
+
+Examples:
+  # Detect what project you're in
+  agent-skill-manager detect
+
+  # Initialize skills for current project
+  agent-skill-manager init
+
+  # Sync skills after config changes
+  agent-skill-manager sync
+
+Git Hook Integration:
+  Add to .git/hooks/post-checkout:
+    #!/bin/bash
+    agent-skill-manager sync 2>/dev/null || true
+EOF
+            ;;
+        *)
+            log_error "Unknown command: $command"
+            exit 1
+            ;;
+    esac
+}
+
+main "$@"
dots/.config/agent-skills/projects.toml
@@ -0,0 +1,172 @@
+# Agent Skills Project Configuration
+# Master configuration for project-specific AI agent skills
+#
+# Location: ~/.config/agent-skills/projects.toml
+# Managed by: ~/src/home/dots/Makefile
+#
+# This configuration works with:
+# - Claude Code (via agent-skill-manager)
+# - Future: GitHub Copilot, OpenCode, pi, etc.
+
+# Projects are keyed by their git remote URL patterns or directory paths
+# When you enter a project directory, agent-skill-manager detects which
+# project config applies and activates the corresponding skills.
+
+# ============================================================================
+# Home Repository - Personal Infrastructure
+# ============================================================================
+[project."*/home"]
+name = "home"
+description = "Personal NixOS infrastructure and homelab"
+required_skills = [
+    "Homelab",
+    "Nix",
+    "Git",
+    "SystematicDebugging",
+    "CORE",
+]
+
+# ============================================================================
+# Nixpkgs - NixOS Package Repository
+# ============================================================================
+[project."github.com/NixOS/nixpkgs"]
+name = "nixpkgs"
+description = "Contributing to NixOS/nixpkgs"
+required_skills = [
+    "Nixpkgs",
+    "Nix",
+    "Git",
+    "SystematicDebugging",
+    "CORE",
+]
+
+# Alternative path-based matcher for local nixpkgs clones
+[project."*/nixpkgs"]
+name = "nixpkgs-local"
+description = "Local nixpkgs repository"
+required_skills = [
+    "Nixpkgs",
+    "Nix",
+    "Git",
+    "SystematicDebugging",
+    "CORE",
+]
+
+# ============================================================================
+# Tekton Projects - Upstream Contribution
+# ============================================================================
+[project."github.com/tektoncd/*"]
+name = "tekton"
+description = "Tekton upstream development"
+required_skills = [
+    "Tekton",
+    "Kubernetes",
+    "golang",
+    "Git",
+    "GitHub",
+    "TestDrivenDevelopment",
+    "SystematicDebugging",
+    "CORE",
+]
+
+# ============================================================================
+# Work Projects - Red Hat
+# ============================================================================
+[project."gitlab.com/redhat/*"]
+name = "redhat"
+description = "Red Hat internal projects"
+required_skills = [
+    "Jira",
+    "Tekton",
+    "Kubernetes",
+    "golang",
+    "Git",
+    "SystematicDebugging",
+    "CORE",
+]
+
+[project."github.com/openshift/*"]
+name = "openshift"
+description = "OpenShift projects"
+required_skills = [
+    "Jira",
+    "Tekton",
+    "Kubernetes",
+    "golang",
+    "Git",
+    "GitHub",
+    "SystematicDebugging",
+    "CORE",
+]
+
+[project."github.com/konflux-ci/*"]
+name = "konflux"
+description = "Konflux CI projects"
+required_skills = [
+    "Jira",
+    "Tekton",
+    "Kubernetes",
+    "golang",
+    "Git",
+    "GitHub",
+    "SystematicDebugging",
+    "CORE",
+]
+
+# ============================================================================
+# Emacs Configuration
+# ============================================================================
+[project."*/emacs.d"]
+name = "emacs"
+description = "Emacs configuration"
+required_skills = [
+    "EmacsLisp",
+    "Git",
+    "SystematicDebugging",
+    "CORE",
+]
+
+[project."*/tools/emacs"]
+name = "emacs-tools"
+description = "Emacs tools in home repository"
+required_skills = [
+    "EmacsLisp",
+    "Nix",
+    "Git",
+    "SystematicDebugging",
+    "CORE",
+]
+
+# ============================================================================
+# Go Projects - Personal
+# ============================================================================
+[project."github.com/vdemeester/*"]
+name = "personal-go"
+description = "Personal Go projects"
+required_skills = [
+    "golang",
+    "Git",
+    "GitHub",
+    "TestDrivenDevelopment",
+    "SystematicDebugging",
+    "CORE",
+]
+
+# ============================================================================
+# Default Fallback
+# ============================================================================
+# If no project matches, these skills are always available from:
+# - Claude: ~/.config/claude/skills/
+# - Future agents: their respective skill directories
+# 
+# Global skills that are ALWAYS loaded:
+# - CORE (personal AI infrastructure)
+# - Git (universal)
+# - GitHub (universal)
+# - Org (personal notes/TODOs)
+# - Email (personal)
+# - Journal (personal)
+# - TODOs (personal task management)
+# - Python, golang, Rust (languages)
+# - Nix (used across multiple projects)
+# - All others in global skills directory
dots/.config/agent-skills/README.md
@@ -0,0 +1,132 @@
+# Agent Skills Project Configuration
+
+Central configuration for managing project-specific AI agent skills across different tools (Claude Code, GitHub Copilot, OpenCode, pi, etc.).
+
+## Purpose
+
+This directory contains a single source of truth (`projects.toml`) that maps git repositories and directory paths to required skills. When you work in a project, the appropriate skills are automatically activated based on this configuration.
+
+## Configuration File
+
+**`projects.toml`**: Maps projects to required skills using git remote URLs or path patterns.
+
+Example:
+```toml
+[project."github.com/vdemeester/home"]
+name = "home"
+description = "Personal NixOS infrastructure"
+required_skills = ["Homelab", "Nix", "Git", "CORE"]
+```
+
+## How It Works
+
+### For Claude Code
+
+1. **Global skills**: `~/.config/claude/skills/` (always loaded)
+2. **Project skills**: `.claude/skills/` (project-specific, gitignored)
+3. **Tool**: `agent-skill-manager` syncs skills from global to project based on `projects.toml`
+
+When you work in a project:
+- Git hooks automatically sync the required skills to `.claude/skills/`
+- Both global and project skills are available
+- Project `.gitignore` prevents committing skill content
+- Configuration is centralized in this directory
+
+### For Future Tools
+
+The same `projects.toml` can be used by other AI coding tools:
+- GitHub Copilot: Could load project-specific instructions
+- OpenCode: Could activate project-specific plugins
+- pi: Could enable project-specific extensions
+
+## File Structure
+
+```
+~/.config/agent-skills/
+├── projects.toml       # Central configuration (THIS FILE)
+└── README.md           # This documentation
+
+~/src/home/
+├── .claude/
+│   └── skills/         # Auto-synced based on projects.toml
+│       ├── Homelab/    # (gitignored, copied from global)
+│       └── Nix/        # (gitignored, copied from global)
+└── .gitignore          # Contains: .claude/skills/
+
+~/.config/claude/skills/  # Global skills library
+├── CORE/
+├── Git/
+├── Homelab/
+├── Nix/
+└── ...
+```
+
+## Usage
+
+### agent-skill-manager
+
+```bash
+# Detect what project you're in
+agent-skill-manager detect
+
+# Initialize skills for current project
+agent-skill-manager init
+
+# Sync skills (after config changes or git operations)
+agent-skill-manager sync
+
+# Check skill status
+agent-skill-manager status
+
+# Remove all project skills
+agent-skill-manager clean
+```
+
+### Adding a New Project
+
+1. Edit `~/.config/agent-skills/projects.toml`
+2. Add a `[project."pattern"]` section
+3. List required skills in `required_skills = [...]`
+4. Run `agent-skill-manager sync` in the project
+
+Example:
+```toml
+[project."github.com/myorg/myrepo"]
+name = "myrepo"
+description = "My awesome project"
+required_skills = ["Python", "Git", "CORE"]
+```
+
+### Git Hook Integration
+
+Add to `.git/hooks/post-checkout`:
+```bash
+#!/bin/bash
+agent-skill-manager sync 2>/dev/null || true
+```
+
+Or use the provided hook templates in `~/src/home/dots/.config/agent-skills/hooks/`.
+
+## Pattern Matching
+
+Projects are matched by:
+1. **Git remote URL** (exact or wildcard): `github.com/user/repo` or `github.com/tektoncd/*`
+2. **Directory path** (wildcard): `*/nixpkgs` or `*/emacs.d`
+
+Wildcards (`*`) match any characters in that segment.
+
+## Benefits
+
+- **Single source of truth**: One config for all projects
+- **Tool-agnostic**: Works with Claude, Copilot, OpenCode, pi, etc.
+- **No committed skills**: Skills are gitignored, only config is versioned
+- **Automatic sync**: Git hooks keep skills up to date
+- **Context-aware**: Different skills for different projects
+- **Centrally managed**: Update once in dotfiles, applies everywhere
+
+## Related Files
+
+- `~/src/home/dots/Makefile`: Manages dotfile installation
+- `~/.config/copilot-hooks/`: Similar pattern for Copilot-specific hooks
+- `~/.config/claude/`: Claude-specific configuration
+- `~/.config/opencode/`: OpenCode-specific configuration
dots/Makefile
@@ -53,13 +53,19 @@ lazypr : ~/.config/lazypr/config.toml
 all += gh-news
 gh-news : ~/.config/gh-news/config.toml
 
-all += git-template copilot-hooks opencode-plugin pi-agent agent-skills agent-skill-manager
+all += git-template copilot-hooks opencode-plugin pi-agent agent-skills agent-skill-manager-bin
 git-template : ~/.config/git/template
 copilot-hooks : ~/.config/copilot-hooks
 opencode-plugin : ~/.config/opencode/plugin
 pi-agent : ~/.pi/agent/extensions ~/.pi/agent/AGENTS.md ~/.pi/agent/README.md
 agent-skills : ~/.config/agent-skills
-agent-skill-manager : ~/bin/agent-skill-manager
+agent-skill-manager-bin : ~/bin/agent-skill-manager
+
+# Agent skill manager tool
+~/bin/agent-skill-manager : $(dotfiles)/.config/agent-skills/agent-skill-manager force
+	@echo "📋 Linking $(dotfiles)/.config/agent-skills/agent-skill-manager → ~/bin/agent-skill-manager"
+	@mkdir -p ~/bin
+	@ln -snf $(dotfiles)/.config/agent-skills/agent-skill-manager ~/bin/agent-skill-manager
 
 # Backward compatibility: symlink ~/.claude to ~/.config/claude
 ~/.claude : force
.gitignore
@@ -28,3 +28,4 @@ hardware-configuration.nix
 .claude/settings.local.json
 /tools/gcal-to-org/gcal-to-org
 .playwright-mcp/
+.claude/skills/