From 3ea36bc93c5089e52c559dc965adc9c5418dc747 Mon Sep 17 00:00:00 2001 From: DariusIII Date: Wed, 5 Aug 2026 10:50:00 +0200 Subject: [PATCH] Add AI skills intaller --- .gitignore | 5 + README.md | 27 + scripts/generate-global-skills-installer.sh | 222 ++++++++ scripts/install-global-skills.sh | 265 +++++++++ scripts/test-global-skills-installer.sh | 176 ++++++ skills-lock.json | 600 ++++++++++++++++++++ 6 files changed, 1295 insertions(+) create mode 100644 scripts/generate-global-skills-installer.sh create mode 100644 scripts/install-global-skills.sh create mode 100644 scripts/test-global-skills-installer.sh create mode 100644 skills-lock.json diff --git a/.gitignore b/.gitignore index 4019e5c59..a3f9c2c7b 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,8 @@ composer.lock package-lock.json yarn.lock pnpm-lock.yaml + +# Agent skills are installed globally by scripts/install-global-skills.sh +/.agents/skills/ +/.claude/skills/ +/.cursor/skills/ diff --git a/README.md b/README.md index ff433817f..d5ce0c2f6 100755 --- a/README.md +++ b/README.md @@ -437,6 +437,33 @@ npm run build npm run lint ``` +### Agent Skills + +The repository keeps the shared agent skill baseline in `skills-lock.json`. The +generated Linux installer installs those skills globally under `~/.agents/skills` +and bridges them into Codex under `~/.codex/skills`. + +Prerequisites: Bash, Node.js/npm (`npx`), and network access to the skill +repositories. + +```bash +# Regenerate the installer after changing skills-lock.json +./scripts/generate-global-skills-installer.sh + +# Preview global installs and Codex bridge changes +./scripts/install-global-skills.sh --dry-run + +# Install globally and create Codex symlinks +./scripts/install-global-skills.sh + +# Copy skills into Codex instead of creating symlinks +./scripts/install-global-skills.sh --copy +``` + +Set `SKILLS_GLOBAL_DIR` or `CODEX_HOME` when using non-default global +directories. The installer never removes or overwrites unmanaged existing +skill paths; resolve reported conflicts manually and rerun it. + ## Troubleshooting ### Common Issues diff --git a/scripts/generate-global-skills-installer.sh b/scripts/generate-global-skills-installer.sh new file mode 100644 index 000000000..9961ac7d6 --- /dev/null +++ b/scripts/generate-global-skills-installer.sh @@ -0,0 +1,222 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +LOCK_FILE="${1:-${PROJECT_ROOT}/skills-lock.json}" +OUTPUT_FILE="${2:-${SCRIPT_DIR}/install-global-skills.sh}" + +die() { + printf 'Error: %s\n' "$1" >&2 + exit 1 +} + +for command_name in awk chmod mkdir mktemp mv rm sort; do + if ! command -v "${command_name}" >/dev/null 2>&1; then + die "Required command not found: ${command_name}" + fi +done + +[[ -f "${LOCK_FILE}" ]] || die "Skills lockfile not found: ${LOCK_FILE}" + +mapfile -t records < <( + awk ' + /^ "[^"]+": \{$/ { + skill = $1 + gsub(/[":]/, "", skill) + next + } + + $1 == "\"source\":" { + source = $2 + gsub(/[",]/, "", source) + print source "\t" skill + skill = "" + } + ' "${LOCK_FILE}" | LC_ALL=C sort -t $'\t' -k1,1 -k2,2 +) + +(( ${#records[@]} > 0 )) || die "No skills found in lockfile: ${LOCK_FILE}" + +temporary_file="$(mktemp "${OUTPUT_FILE}.tmp.XXXXXX")" +cleanup() { + rm -f -- "${temporary_file}" +} +trap cleanup EXIT + +{ + printf '%s\n' '#!/usr/bin/env bash' + printf '\n' + printf '%s\n' '# This file is generated by scripts/generate-global-skills-installer.sh.' + printf '%s\n' '# Do not edit manually; regenerate it after changing skills-lock.json.' + printf '%s\n' '# shellcheck shell=bash' + printf '\n' + printf '%s\n' 'set -Eeuo pipefail' + printf '\n' + printf 'SKILL_COUNT=%q\n' "${#records[@]}" + printf 'DRY_RUN=0\n' + printf 'COPY_MODE=0\n' + printf 'SHARED_SKILLS_DIR="${SKILLS_GLOBAL_DIR:-${HOME:?HOME must be set}/.agents/skills}"\n' + printf 'CODEX_HOME_DIR="${CODEX_HOME:-${HOME:?HOME must be set}/.codex}"\n' + printf 'CODEX_SKILLS_DIR="${CODEX_HOME_DIR}/skills"\n' + printf '\n' + printf '%s\n' 'usage() {' + printf '%s\n' ' cat <<'"'"'USAGE'"'"'' + printf '%s\n' 'Usage: scripts/install-global-skills.sh [options]' + printf '\n' + printf '%s\n' 'Install the repository skill baseline globally and bridge it into Codex.' + printf '\n' + printf '%s\n' 'Options:' + printf '%s\n' ' --dry-run Show commands and filesystem changes without applying them.' + printf '%s\n' ' --copy Copy skills into Codex instead of creating symlinks.' + printf '%s\n' ' --help Show this help text.' + printf '\n' + printf '%s\n' 'Environment:' + printf '%s\n' ' SKILLS_GLOBAL_DIR Shared global skills directory (default: ~/.agents/skills).' + printf '%s\n' ' CODEX_HOME Codex home directory (default: ~/.codex).' + printf '%s\n' 'USAGE' + printf '%s\n' '}' + printf '\n' + printf '%s\n' 'die() {' + printf '%s\n' ' printf '"'"'Error: %s\n'"'"' "$1" >&2' + printf '%s\n' ' exit 1' + printf '%s\n' '}' + printf '\n' + printf '%s\n' 'print_command() {' + printf '%s\n' ' printf '"'"'+'"'"'' + printf '%s\n' ' printf '"'"' %q'"'"' "$@"' + printf '%s\n' ' printf '"'"'\n'"'"'' + printf '%s\n' '}' + printf '\n' + printf '%s\n' 'run_command() {' + printf '%s\n' ' if (( DRY_RUN )); then' + printf '%s\n' ' print_command "$@"' + printf '%s\n' ' return 0' + printf '%s\n' ' fi' + printf '%s\n' ' "$@"' + printf '%s\n' '}' + printf '\n' + printf '%s\n' 'require_command() {' + printf '%s\n' ' command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"' + printf '%s\n' '}' + printf '\n' + printf '%s\n' 'while (( $# > 0 )); do' + printf '%s\n' ' case "$1" in' + printf '%s\n' ' --dry-run)' + printf '%s\n' ' DRY_RUN=1' + printf '%s\n' ' ;;' + printf '%s\n' ' --copy)' + printf '%s\n' ' COPY_MODE=1' + printf '%s\n' ' ;;' + printf '%s\n' ' --help|-h)' + printf '%s\n' ' usage' + printf '%s\n' ' exit 0' + printf '%s\n' ' ;;' + printf '%s\n' ' *)' + printf '%s\n' ' die "Unknown option: $1 (use --help for usage)"' + printf '%s\n' ' ;;' + printf '%s\n' ' esac' + printf '%s\n' ' shift' + printf '%s\n' 'done' + printf '\n' + printf '%s\n' 'require_command bash' + printf '%s\n' 'require_command npx' + printf '%s\n' 'require_command mkdir' + printf '%s\n' 'require_command ln' + printf '%s\n' 'require_command readlink' + printf '%s\n' 'if (( COPY_MODE )); then' + printf '%s\n' ' require_command cp' + printf '%s\n' ' require_command diff' + printf '%s\n' 'fi' + printf '\n' + printf '%s\n' 'printf '"'"'Installing %d skills globally...\n'"'"' "$SKILL_COUNT"' + printf '\n' + + current_source="" + source_skill_args=() + for record in "${records[@]}"; do + source="${record%%$'\t'*}" + skill="${record#*$'\t'}" + + if [[ -n "${current_source}" && "${source}" != "${current_source}" ]]; then + printf 'run_command npx --yes skills add %q --global --yes' "${current_source}" + for skill_arg in "${source_skill_args[@]}"; do + printf ' --skill %q' "${skill_arg}" + done + printf '\n\n' + source_skill_args=() + fi + + current_source="${source}" + source_skill_args+=("${skill}") + done + + printf 'run_command npx --yes skills add %q --global --yes' "${current_source}" + for skill_arg in "${source_skill_args[@]}"; do + printf ' --skill %q' "${skill_arg}" + done + printf '\n\n' + + printf '%s\n' 'SKILLS=(' + for record in "${records[@]}"; do + skill="${record#*$'\t'}" + printf ' %q\n' "${skill}" + done + printf '%s\n' ')' + printf '\n' + printf '%s\n' 'if (( DRY_RUN )); then' + printf '%s\n' ' printf '"'"'Would bridge skills from %s to %s\n'"'"' "$SHARED_SKILLS_DIR" "$CODEX_SKILLS_DIR"' + printf '%s\n' ' for skill in "${SKILLS[@]}"; do' + printf '%s\n' ' if (( COPY_MODE )); then' + printf '%s\n' ' printf '"'"'+ copy %s -> %s/%s\n'"'"' "$SHARED_SKILLS_DIR/$skill" "$CODEX_SKILLS_DIR" "$skill"' + printf '%s\n' ' else' + printf '%s\n' ' printf '"'"'+ ln -s %s %s/%s\n'"'"' "$SHARED_SKILLS_DIR/$skill" "$CODEX_SKILLS_DIR" "$skill"' + printf '%s\n' ' fi' + printf '%s\n' ' done' + printf '%s\n' ' exit 0' + printf '%s\n' 'fi' + printf '\n' + printf '%s\n' 'run_command mkdir -p "$CODEX_SKILLS_DIR"' + printf '%s\n' 'conflict_count=0' + printf '%s\n' 'for skill in "${SKILLS[@]}"; do' + printf '%s\n' ' source_path="$SHARED_SKILLS_DIR/$skill"' + printf '%s\n' ' target_path="$CODEX_SKILLS_DIR/$skill"' + printf '%s\n' ' [[ -d "$source_path" ]] || { printf '"'"'Error: Installed skill is missing: %s\n'"'"' "$source_path" >&2; conflict_count=$((conflict_count + 1)); continue; }' + printf '%s\n' ' if [[ -L "$target_path" ]]; then' + printf '%s\n' ' if [[ "$(readlink "$target_path")" == "$source_path" ]]; then' + printf '%s\n' ' printf '"'"'Already linked: %s\n'"'"' "$skill"' + printf '%s\n' ' continue' + printf '%s\n' ' fi' + printf '%s\n' ' printf '"'"'Error: Refusing to replace unmanaged symlink: %s\n'"'"' "$target_path" >&2' + printf '%s\n' ' conflict_count=$((conflict_count + 1))' + printf '%s\n' ' continue' + printf '%s\n' ' fi' + printf '%s\n' ' if [[ -e "$target_path" ]]; then' + printf '%s\n' ' if (( COPY_MODE )) && [[ -d "$target_path" ]] && diff -qr -- "$source_path" "$target_path" >/dev/null; then' + printf '%s\n' ' printf '"'"'Already copied: %s\n'"'"' "$skill"' + printf '%s\n' ' continue' + printf '%s\n' ' fi' + printf '%s\n' ' printf '"'"'Error: Refusing to overwrite existing path: %s\n'"'"' "$target_path" >&2' + printf '%s\n' ' conflict_count=$((conflict_count + 1))' + printf '%s\n' ' continue' + printf '%s\n' ' fi' + printf '%s\n' ' if (( COPY_MODE )); then' + printf '%s\n' ' run_command cp -R -- "$source_path" "$target_path"' + printf '%s\n' ' printf '"'"'Copied: %s\n'"'"' "$skill"' + printf '%s\n' ' else' + printf '%s\n' ' run_command ln -s -- "$source_path" "$target_path"' + printf '%s\n' ' printf '"'"'Linked: %s\n'"'"' "$skill"' + printf '%s\n' ' fi' + printf '%s\n' 'done' + printf '\n' + printf '%s\n' '(( conflict_count == 0 )) || die "${conflict_count} skill bridge conflict(s) require manual resolution"' + printf '%s\n' 'printf '"'"'Global skills installation complete.\n'"'"'' +} >"${temporary_file}" + +chmod 755 "${temporary_file}" +mkdir -p -- "$(dirname -- "${OUTPUT_FILE}")" +mv -- "${temporary_file}" "${OUTPUT_FILE}" +trap - EXIT + +printf 'Generated %s from %s (%d skills).\n' "${OUTPUT_FILE}" "${LOCK_FILE}" "${#records[@]}" diff --git a/scripts/install-global-skills.sh b/scripts/install-global-skills.sh new file mode 100644 index 000000000..c1c889c8c --- /dev/null +++ b/scripts/install-global-skills.sh @@ -0,0 +1,265 @@ +#!/usr/bin/env bash + +# This file is generated by scripts/generate-global-skills-installer.sh. +# Do not edit manually; regenerate it after changing skills-lock.json. +# shellcheck shell=bash + +set -Eeuo pipefail + +SKILL_COUNT=119 +DRY_RUN=0 +COPY_MODE=0 +SHARED_SKILLS_DIR="${SKILLS_GLOBAL_DIR:-${HOME:?HOME must be set}/.agents/skills}" +CODEX_HOME_DIR="${CODEX_HOME:-${HOME:?HOME must be set}/.codex}" +CODEX_SKILLS_DIR="${CODEX_HOME_DIR}/skills" + +usage() { + cat <<'USAGE' +Usage: scripts/install-global-skills.sh [options] + +Install the repository skill baseline globally and bridge it into Codex. + +Options: + --dry-run Show commands and filesystem changes without applying them. + --copy Copy skills into Codex instead of creating symlinks. + --help Show this help text. + +Environment: + SKILLS_GLOBAL_DIR Shared global skills directory (default: ~/.agents/skills). + CODEX_HOME Codex home directory (default: ~/.codex). +USAGE +} + +die() { + printf 'Error: %s\n' "$1" >&2 + exit 1 +} + +print_command() { + printf '+' + printf ' %q' "$@" + printf '\n' +} + +run_command() { + if (( DRY_RUN )); then + print_command "$@" + return 0 + fi + "$@" +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1" +} + +while (( $# > 0 )); do + case "$1" in + --dry-run) + DRY_RUN=1 + ;; + --copy) + COPY_MODE=1 + ;; + --help|-h) + usage + exit 0 + ;; + *) + die "Unknown option: $1 (use --help for usage)" + ;; + esac + shift +done + +require_command bash +require_command npx +require_command mkdir +require_command ln +require_command readlink +if (( COPY_MODE )); then + require_command cp + require_command diff +fi + +printf 'Installing %d skills globally...\n' "$SKILL_COUNT" + +run_command npx --yes skills add asyrafhussin/agent-skills --global --yes --skill api-design-patterns --skill clean-code-principles --skill code-slop --skill e2e-playwright-testing --skill git-workflow --skill laravel-ai-sdk --skill laravel-best-practices --skill laravel-database-optimization --skill laravel-inertia-react --skill laravel-mcp --skill laravel-owasp-security --skill laravel-queues --skill laravel-testing --skill php-best-practices --skill prd-writing --skill project-docs --skill react-vite-best-practices --skill seo-best-practices --skill state-management --skill tailwind-best-practices --skill technical-debt --skill testing-best-practices --skill typescript-react-patterns --skill web-design-guidelines + +run_command npx --yes skills add iserter/laravel-claude-agents --global --yes --skill eloquent-best-practices --skill laravel-api-resource-patterns --skill laravel-authorization-patterns --skill laravel-blade-component-patterns --skill laravel-brainstorming --skill laravel-caching-strategies --skill laravel-event-driven-architecture --skill laravel-feature-flags --skill laravel-middleware-patterns --skill laravel-notification-patterns --skill laravel-queue-patterns --skill laravel-systematic-debugging --skill laravel-task-scheduling --skill laravel-tdd --skill laravel-validation-patterns + +run_command npx --yes skills add jeffallan/claude-skills --global --yes --skill angular-architect --skill api-designer --skill architecture-designer --skill atlassian-mcp --skill chaos-engineer --skill cli-developer --skill cloud-architect --skill code-documenter --skill code-reviewer --skill cpp-pro --skill csharp-developer --skill database-optimizer --skill debugging-wizard --skill devops-engineer --skill django-expert --skill dotnet-core-expert --skill embedded-systems --skill fastapi-expert --skill feature-forge --skill fine-tuning-expert --skill flutter-expert --skill fullstack-guardian --skill game-developer --skill golang-pro --skill graphql-architect --skill java-architect --skill javascript-pro --skill kotlin-specialist --skill kubernetes-specialist --skill laravel-specialist --skill legacy-modernizer --skill mcp-developer --skill microservices-architect --skill ml-pipeline --skill monitoring-expert --skill nestjs-expert --skill nextjs-developer --skill pandas-pro --skill php-pro --skill playwright-expert --skill postgres-pro --skill prompt-engineer --skill python-pro --skill rag-architect --skill rails-expert --skill react-expert --skill react-native-expert --skill rust-engineer --skill salesforce-developer --skill secure-code-guardian --skill security-reviewer --skill shopify-expert --skill spark-engineer --skill spec-miner --skill spring-boot-engineer --skill sql-pro --skill sre-engineer --skill swift-expert --skill terraform-engineer --skill test-master --skill the-fool --skill typescript-pro --skill vue-expert --skill vue-expert-js --skill websocket-engineer --skill wordpress-pro + +run_command npx --yes skills add leeovery/claude-laravel --global --yes --skill laravel-quality + +run_command npx --yes skills add thienanblog/awesome-ai-agent-skills --global --yes --skill agents-md-generator --skill ask-questions-if-underspecified --skill design-system-generator --skill docker-local-dev --skill documentation-guidelines --skill find-scene --skill laravel-11-12-app-guidelines --skill noizai-voice-workflow --skill office-web-ui-system --skill project-development-mindset --skill symcli-skill --skill ui-mockup-visualizer --skill vps-docker-traefik-deploy + +SKILLS=( + api-design-patterns + clean-code-principles + code-slop + e2e-playwright-testing + git-workflow + laravel-ai-sdk + laravel-best-practices + laravel-database-optimization + laravel-inertia-react + laravel-mcp + laravel-owasp-security + laravel-queues + laravel-testing + php-best-practices + prd-writing + project-docs + react-vite-best-practices + seo-best-practices + state-management + tailwind-best-practices + technical-debt + testing-best-practices + typescript-react-patterns + web-design-guidelines + eloquent-best-practices + laravel-api-resource-patterns + laravel-authorization-patterns + laravel-blade-component-patterns + laravel-brainstorming + laravel-caching-strategies + laravel-event-driven-architecture + laravel-feature-flags + laravel-middleware-patterns + laravel-notification-patterns + laravel-queue-patterns + laravel-systematic-debugging + laravel-task-scheduling + laravel-tdd + laravel-validation-patterns + angular-architect + api-designer + architecture-designer + atlassian-mcp + chaos-engineer + cli-developer + cloud-architect + code-documenter + code-reviewer + cpp-pro + csharp-developer + database-optimizer + debugging-wizard + devops-engineer + django-expert + dotnet-core-expert + embedded-systems + fastapi-expert + feature-forge + fine-tuning-expert + flutter-expert + fullstack-guardian + game-developer + golang-pro + graphql-architect + java-architect + javascript-pro + kotlin-specialist + kubernetes-specialist + laravel-specialist + legacy-modernizer + mcp-developer + microservices-architect + ml-pipeline + monitoring-expert + nestjs-expert + nextjs-developer + pandas-pro + php-pro + playwright-expert + postgres-pro + prompt-engineer + python-pro + rag-architect + rails-expert + react-expert + react-native-expert + rust-engineer + salesforce-developer + secure-code-guardian + security-reviewer + shopify-expert + spark-engineer + spec-miner + spring-boot-engineer + sql-pro + sre-engineer + swift-expert + terraform-engineer + test-master + the-fool + typescript-pro + vue-expert + vue-expert-js + websocket-engineer + wordpress-pro + laravel-quality + agents-md-generator + ask-questions-if-underspecified + design-system-generator + docker-local-dev + documentation-guidelines + find-scene + laravel-11-12-app-guidelines + noizai-voice-workflow + office-web-ui-system + project-development-mindset + symcli-skill + ui-mockup-visualizer + vps-docker-traefik-deploy +) + +if (( DRY_RUN )); then + printf 'Would bridge skills from %s to %s\n' "$SHARED_SKILLS_DIR" "$CODEX_SKILLS_DIR" + for skill in "${SKILLS[@]}"; do + if (( COPY_MODE )); then + printf '+ copy %s -> %s/%s\n' "$SHARED_SKILLS_DIR/$skill" "$CODEX_SKILLS_DIR" "$skill" + else + printf '+ ln -s %s %s/%s\n' "$SHARED_SKILLS_DIR/$skill" "$CODEX_SKILLS_DIR" "$skill" + fi + done + exit 0 +fi + +run_command mkdir -p "$CODEX_SKILLS_DIR" +conflict_count=0 +for skill in "${SKILLS[@]}"; do + source_path="$SHARED_SKILLS_DIR/$skill" + target_path="$CODEX_SKILLS_DIR/$skill" + [[ -d "$source_path" ]] || { printf 'Error: Installed skill is missing: %s\n' "$source_path" >&2; conflict_count=$((conflict_count + 1)); continue; } + if [[ -L "$target_path" ]]; then + if [[ "$(readlink "$target_path")" == "$source_path" ]]; then + printf 'Already linked: %s\n' "$skill" + continue + fi + printf 'Error: Refusing to replace unmanaged symlink: %s\n' "$target_path" >&2 + conflict_count=$((conflict_count + 1)) + continue + fi + if [[ -e "$target_path" ]]; then + if (( COPY_MODE )) && [[ -d "$target_path" ]] && diff -qr -- "$source_path" "$target_path" >/dev/null; then + printf 'Already copied: %s\n' "$skill" + continue + fi + printf 'Error: Refusing to overwrite existing path: %s\n' "$target_path" >&2 + conflict_count=$((conflict_count + 1)) + continue + fi + if (( COPY_MODE )); then + run_command cp -R -- "$source_path" "$target_path" + printf 'Copied: %s\n' "$skill" + else + run_command ln -s -- "$source_path" "$target_path" + printf 'Linked: %s\n' "$skill" + fi +done + +(( conflict_count == 0 )) || die "${conflict_count} skill bridge conflict(s) require manual resolution" +printf 'Global skills installation complete.\n' diff --git a/scripts/test-global-skills-installer.sh b/scripts/test-global-skills-installer.sh new file mode 100644 index 000000000..472ea9658 --- /dev/null +++ b/scripts/test-global-skills-installer.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +INSTALLER="${SCRIPT_DIR}/install-global-skills.sh" +GENERATOR="${SCRIPT_DIR}/generate-global-skills-installer.sh" +LOCK_FILE="${PROJECT_ROOT}/skills-lock.json" +TEMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/global-skills-test.XXXXXX")" + +cleanup() { + rm -rf -- "${TEMP_ROOT}" +} +trap cleanup EXIT INT TERM + +fail() { + printf 'Test failure: %s\n' "$1" >&2 + exit 1 +} + +assert_contains() { + local needle="$1" + local haystack="$2" + + [[ "${haystack}" == *"${needle}"* ]] || fail "Expected output to contain: ${needle}" +} + +assert_file_exists() { + [[ -e "$1" ]] || fail "Expected file to exist: $1" +} + +assert_equals() { + local expected="$1" + local actual="$2" + local description="$3" + + [[ "${expected}" == "${actual}" ]] || fail "${description}: expected ${expected}, got ${actual}" +} + +expected_skill_count="$(awk ' + /^SKILLS=\(/ { inside = 1; next } + inside && /^\)/ { inside = 0 } + inside && /^ / { count++ } + END { print count + 0 } +' "${INSTALLER}")" +assert_equals 119 "${expected_skill_count}" "generated skill count" + +generated_installer="${TEMP_ROOT}/generated-installer.sh" +"${GENERATOR}" "${LOCK_FILE}" "${generated_installer}" >/dev/null +bash -n "${generated_installer}" +generated_count="$(awk ' + /^SKILLS=\(/ { inside = 1; next } + inside && /^\)/ { inside = 0 } + inside && /^ / { count++ } + END { print count + 0 } +' "${generated_installer}")" +assert_equals "${expected_skill_count}" "${generated_count}" "regenerated skill count" +assert_equals 5 "$(rg -c '^run_command npx --yes skills add' "${generated_installer}")" "source command count" +if ! diff -u \ + <(awk '/^ "[^"]+": \{$/ { skill = $1; gsub(/[":]/, "", skill); print skill }' "${LOCK_FILE}" | LC_ALL=C sort) \ + <(awk ' + /^SKILLS=\(/ { inside = 1; next } + inside && /^\)/ { inside = 0 } + inside && /^ / { print $1 } + ' "${generated_installer}" | LC_ALL=C sort); then + fail 'generated installer skill names differ from skills-lock.json' +fi + +mock_bin="${TEMP_ROOT}/bin" +mkdir -p -- "${mock_bin}" +cat >"${mock_bin}/npx" <<'MOCK_NPX' +#!/usr/bin/env bash + +set -Eeuo pipefail + +printf '%s\n' "$*" >>"${MOCK_NPX_LOG}" +[[ "${1:-}" == "--yes" ]] || exit 2 +shift +[[ "${1:-}" == "skills" ]] || exit 2 +shift +[[ "${1:-}" == "add" ]] || exit 2 +shift +shift + +while (( $# > 0 )); do + case "$1" in + --global|--yes) + shift + ;; + --skill) + skill="$2" + mkdir -p -- "${SKILLS_GLOBAL_DIR}/${skill}" + printf 'mock skill: %s\n' "${skill}" >"${SKILLS_GLOBAL_DIR}/${skill}/SKILL.md" + shift 2 + ;; + *) + shift + ;; + esac +done +MOCK_NPX +chmod 755 "${mock_bin}/npx" + +run_installer() { + local home_dir="$1" + local shared_dir="$2" + local codex_home="$3" + shift 3 + + HOME="${home_dir}" \ + SKILLS_GLOBAL_DIR="${shared_dir}" \ + CODEX_HOME="${codex_home}" \ + MOCK_NPX_LOG="${TEMP_ROOT}/npx.log" \ + PATH="${mock_bin}:${PATH}" \ + "${INSTALLER}" "$@" +} + +home_dir="${TEMP_ROOT}/home" +shared_dir="${TEMP_ROOT}/shared" +codex_home="${TEMP_ROOT}/codex" +mkdir -p -- "${home_dir}" "${codex_home}/skills/.system" +printf 'preserve me\n' >"${codex_home}/skills/.system/SKILL.md" +: >"${TEMP_ROOT}/npx.log" +run_installer "${home_dir}" "${shared_dir}" "${codex_home}" >"${TEMP_ROOT}/install.log" 2>&1 \ + || { cat "${TEMP_ROOT}/install.log" >&2; fail 'initial installation failed'; } + +assert_equals "${expected_skill_count}" "$(find "${codex_home}/skills" -mindepth 1 -maxdepth 1 -type l | wc -l | tr -d ' ')" "symlink count" +assert_file_exists "${codex_home}/skills/.system/SKILL.md" +system_contents="$(<"${codex_home}/skills/.system/SKILL.md")" +assert_contains 'preserve me' "${system_contents}" +npx_call_count="$(wc -l < "${TEMP_ROOT}/npx.log" | tr -d ' ')" +assert_equals 5 "${npx_call_count}" "global source install count" +npx_log_contents="$(<"${TEMP_ROOT}/npx.log")" +assert_contains '--global' "${npx_log_contents}" + +run_installer "${home_dir}" "${shared_dir}" "${codex_home}" >"${TEMP_ROOT}/rerun.log" 2>&1 \ + || { cat "${TEMP_ROOT}/rerun.log" >&2; fail 'idempotent symlink rerun failed'; } +assert_equals "${expected_skill_count}" "$(rg -c '^Already linked:' "${TEMP_ROOT}/rerun.log")" "idempotent link count" + +dry_home="${TEMP_ROOT}/dry-home" +dry_shared="${TEMP_ROOT}/dry-shared" +dry_codex="${TEMP_ROOT}/dry-codex" +dry_output="$(run_installer "${dry_home}" "${dry_shared}" "${dry_codex}" --dry-run 2>&1)" +assert_contains 'Would bridge skills' "${dry_output}" +[[ ! -e "${dry_shared}" ]] || fail 'dry run created the shared skill directory' +[[ ! -e "${dry_codex}" ]] || fail 'dry run created the Codex directory' + +copy_home="${TEMP_ROOT}/copy-home" +copy_shared="${TEMP_ROOT}/copy-shared" +copy_codex="${TEMP_ROOT}/copy-codex" +mkdir -p -- "${copy_home}" "${copy_codex}/skills/.system" +run_installer "${copy_home}" "${copy_shared}" "${copy_codex}" --copy >"${TEMP_ROOT}/copy.log" 2>&1 \ + || { cat "${TEMP_ROOT}/copy.log" >&2; fail 'copy installation failed'; } +assert_equals "${expected_skill_count}" "$(find "${copy_codex}/skills" -mindepth 1 -maxdepth 1 -type d ! -name .system | wc -l | tr -d ' ')" "copy count" +run_installer "${copy_home}" "${copy_shared}" "${copy_codex}" --copy >"${TEMP_ROOT}/copy-rerun.log" 2>&1 \ + || { cat "${TEMP_ROOT}/copy-rerun.log" >&2; fail 'idempotent copy rerun failed'; } +assert_equals "${expected_skill_count}" "$(rg -c '^Already copied:' "${TEMP_ROOT}/copy-rerun.log")" "idempotent copy count" + +conflict_home="${TEMP_ROOT}/conflict-home" +conflict_shared="${TEMP_ROOT}/conflict-shared" +conflict_codex="${TEMP_ROOT}/conflict-codex" +first_skill="$(awk ' + /^SKILLS=\(/ { inside = 1; next } + inside && /^ / { print $1; exit } +' "${INSTALLER}")" +mkdir -p -- "${conflict_home}" "${conflict_codex}/skills/${first_skill}" +printf 'unmanaged\n' >"${conflict_codex}/skills/${first_skill}/marker.txt" +if run_installer "${conflict_home}" "${conflict_shared}" "${conflict_codex}" >"${TEMP_ROOT}/conflict.log" 2>&1; then + cat "${TEMP_ROOT}/conflict.log" >&2 + fail 'unmanaged conflict was not rejected' +fi +conflict_log_contents="$(<"${TEMP_ROOT}/conflict.log")" +assert_contains 'Refusing to overwrite existing path' "${conflict_log_contents}" + +printf 'Global skills installer tests passed (%d skills).\n' "${expected_skill_count}" diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 000000000..4d11892ba --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,600 @@ +{ + "version": 1, + "skills": { + "agents-md-generator": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "b5b5d0dde3127aa10f6d7db1208d49919fdcbaf1374062b30ddc68dabb83b001" + }, + "angular-architect": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "d93b695494c81c076a7c837007762f6e04b204ee19eccd7ff77969ff76835dfd" + }, + "api-design-patterns": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "3f15c74774287c26486439158e2f7454907b6751fbd2e7842f034555e47df796" + }, + "api-designer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "06e7906fe1ca4d1db195ca244ff7cc05e87e2c8b634662abeedd501c4657e88e" + }, + "architecture-designer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "ecb729710037e01080fd2c342f37d2c25b3597bec70b8d529f9604abd8ccc142" + }, + "ask-questions-if-underspecified": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "79458ea62d3f88fccd2c9936ebfcb025ce9a43d3302417bbee9d0e2edec30109" + }, + "atlassian-mcp": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "4513b7e0d937979a80b774ef9220b0074c5c4230ea5cbb39cca4f1c402529491" + }, + "chaos-engineer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "bc0cdbf36e1860a66d4f8d967c78ee31b1daed917c1ab9d7a015237ff071e718" + }, + "clean-code-principles": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "4c22dca6110d58f1454523dc3f192ef20d91f538233da9132b407edd041a4762" + }, + "cli-developer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "8ac5b5dc1cc784f255f4ebde0c65d0185da4a7d6b02d8d38838be178b62cfd7a" + }, + "cloud-architect": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "9082e59c3dc8f05378bc52dc043724e22da525eea38916becb8778e2d320ac92" + }, + "code-documenter": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "49709db09641a9ab4196459835bfcbe565eb0581c23cb4eeb50ff80d2af88272" + }, + "code-reviewer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "57ab3eba6fa596bc52d73ca7312afc2f389d9622714d25b2ab3d4bbf03dc293a" + }, + "code-slop": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "a4362f272da2f81751108d0bbfb8b5128e5067329619645d24b100c95dc3edd9" + }, + "cpp-pro": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "a5632d28a479fe433c7fd9ab98903b9cd010926b3ddd73785ff0325d0277dbe6" + }, + "csharp-developer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "90bd8e86b9c139379d225f4692b4e9deb588db680055439dc3b463833ef6adfb" + }, + "database-optimizer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "2feb5b0765151417dab90bf014df41514386e4bdb25aad9ab38498c0bd18a1d8" + }, + "debugging-wizard": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "9f39c248ae9588423cd12ea5197adf1530d7308eaf51a6d018ad08d52658a2ed" + }, + "design-system-generator": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "402854c08c678a14b302a66bf48e43ea803bca8247fb1f6735fe9cabfe662535" + }, + "devops-engineer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "6de5e562151fe9c227234e81bdc1e647066d6b1822463eadf296a5344b47e35a" + }, + "django-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "f64e47ac397e308dde77144592cfc2fc2b60815c7ee653ef8bdc8cf860de3c27" + }, + "docker-local-dev": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "2d81a4cea16c22c5404fef6f932aa8b22e2fe5cddc0b7ba4650f26b812afa585" + }, + "documentation-guidelines": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "af9ad9dc6e7e18ab6896fad8bf23e19037671d2d06461452d7a6052119237334" + }, + "dotnet-core-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "ceb0245bce4c20ad0760d7f589311e3ac2cdf110636a1581fa6d3018c7b27890" + }, + "e2e-playwright-testing": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "ee097dfeaa4d731a35ab47bd930859f17d7d0d073d0df3720435b02d97d12210" + }, + "eloquent-best-practices": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "370e1502efc9d179f5df7470a45a10302de81de9cf378641403e4ade9eb7d67f" + }, + "embedded-systems": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "1631345781a5ed3ba2a780a710b8c23cb1b0abaaf576164c40d15b20683d2f6e" + }, + "fastapi-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "eb70cfcb3a06badfbdf51262ecb31b868a983963b9d6654e02856752828b312f" + }, + "feature-forge": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "a6ed33cfbfc0259c39f42ad69cc55ace984da805c4f4197df437770cf7c1b671" + }, + "find-scene": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "2a5ebaf950698a1751270337b9eb80dd37c3ebd459be3920d787506cc2455f84" + }, + "fine-tuning-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "8e70137cf3de9a6edba12d3f523c087c0407002f1206b0981b767323e0f5a54e" + }, + "flutter-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "c7849f39cf34d91999f573b8ad39315dd68c7717bcd38c42b5e49cd83785d9c1" + }, + "fullstack-guardian": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "2d3533f91951f2c8626eaeb7ffb33e40727bca85545a8e63f97e35939957e1a4" + }, + "game-developer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "5310da9a7910ae7e9d119abb7377b6213b1dc7767e4aacd818b8fdb60abab36f" + }, + "git-workflow": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "e6a28779208771a57940a14e1f9e463ab65c1f8e660fcc52d05b9ec05de3fab1" + }, + "golang-pro": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "12b4cac8f65228f82cbc26f0da9232aba87ffa45ccd01afa3998fb8ec61dc288" + }, + "graphql-architect": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "78c6dd863af986f7f409e26ba25dc14f76985f892a0340383dc56a7b8439ec43" + }, + "java-architect": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "02a7bcc716c9338aedcb0727be38a86d6addbadb1c17b485d019f654d2b04af6" + }, + "javascript-pro": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "d5d4526309a674a2ddfd99aa9fb31fe061af225a7b3750eed52a2d38c8ce6041" + }, + "kotlin-specialist": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "7119693c0ce1ebf7de447ccafef33cd319b9a8bfc057b6dfcb137caac2fea274" + }, + "kubernetes-specialist": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "9c5eb68fc9d553ee29e745258bc6198563158c4b2c57436d63647e61010b5f0a" + }, + "laravel-11-12-app-guidelines": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "9505833bc35846abbfbb5ab93e1c45a7da179e7cccc3122223a4881afefab7cd" + }, + "laravel-ai-sdk": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "6e7fb4360bcbe6eea2a634d7b96a46b41887f60eca806f54b7400f3efa508ce0" + }, + "laravel-api-resource-patterns": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "b140eaa32c967c9bbde994a47dc56491132efd97f06a534920e13dbbc36db887" + }, + "laravel-authorization-patterns": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "57418ddc3eea7b850c96dbcd431070e75b2abff5a4bd010f759133e225c5e216" + }, + "laravel-best-practices": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "6eb357774a2416b3e632cf4f7dec1ff9558af2d70c4b8ade802f5a65ce118fb5" + }, + "laravel-blade-component-patterns": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "4fe35eb514db82613a195b4c3d790618adb88e70e68a84ff9c50d086fa288759" + }, + "laravel-brainstorming": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "ff3aa9135e3e2573bb7edb664b611baefeef893c9425f52ccda87a56618e9089" + }, + "laravel-caching-strategies": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "7b5d23808f7355be3de5328f8c0f5ca80ab194b252aeed697c3ee505fc90b270" + }, + "laravel-database-optimization": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "564cbba91a281420e077178d7b367ef1c367576ff6f050f9ee6eaa99d30d2a1c" + }, + "laravel-event-driven-architecture": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "5b43a13e97cc0b9dcd5df9bf819a15b254f1dd016ccadb94bb5e6d3c77710de9" + }, + "laravel-feature-flags": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "46214cd3e065d109cfcba59dce63cb75563b97efc200deac8b6c1333bda48f75" + }, + "laravel-inertia-react": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "b05144f7f9df5fd3cd03ce13cba822ac61970449fdf7c4c4208684258e8b1724" + }, + "laravel-mcp": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "31de735b826f5c8e7b64ca133b10f2fc6b4a1789bb6b144aecccc948fe8ef689" + }, + "laravel-middleware-patterns": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "44414ad9a3dbe8753d0861673b8fdf10d9bec931564c0e552c9accecdefbedf3" + }, + "laravel-notification-patterns": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "658eaf3f166438e7d5e5403c3bd0b9e2670a2858a45d70da68289fa18c01ce60" + }, + "laravel-owasp-security": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "35f74b7fb37c9b0cb1a274e7e25f7425e793dacd73620c3507c7e9462e4c9c9d" + }, + "laravel-quality": { + "source": "leeovery/claude-laravel", + "sourceType": "github", + "computedHash": "bffa603f3f1128d2d3b1c2dcbbbffdec1bd239d4a799c74419deb627fa64a518" + }, + "laravel-queue-patterns": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "4bfe5b51ed89506df59a32de0f4be4ab2cc106356b98149953441a109bd2f951" + }, + "laravel-queues": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "75391b4d5635e0db3ef8a02bd512a2974b98bae97375fdfd9a18fe7ad936dbf8" + }, + "laravel-specialist": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "d58e2ed3488398cb94a039f25b9d270e55dc434e16102abff9035608bec3db90" + }, + "laravel-systematic-debugging": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "82db3f7d4e3cfc3b3d1605a96c06612f93121dd3c879208d9dba23f30495518d" + }, + "laravel-task-scheduling": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "833b36a2ae73833137431c6b7b5d9d393adfe93b3ab4d135e87acead31a7cc20" + }, + "laravel-tdd": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "f895a09949a45fb610d8f033c168fc7ed70ef33323c38ef176add410ddd58e8e" + }, + "laravel-testing": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "9627c977341aa7a9a11e5f500838409c1e39e1ca0714c6a6038fba4d3a5203c9" + }, + "laravel-validation-patterns": { + "source": "iserter/laravel-claude-agents", + "sourceType": "github", + "computedHash": "66fb92309a38bba54c8730a0efccc0f3092cf7dbfb71b294ff33c1becae91285" + }, + "legacy-modernizer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "703bc6b068b60f1ca32c2892f3aa3875c986fa17b65676f612810eb52c21b5eb" + }, + "mcp-developer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "e94458c5a3ca95938decbcf071e1fa3d1bb44a8b878c5a7cab7b48b7612dc5db" + }, + "microservices-architect": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "674b7d4c18eb009295213cbb49ccdc112db1d2f60bb65848ceb8771bdc69a25f" + }, + "ml-pipeline": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "5e80b6260cabb7e8705b04627f1ec1a70341ecf2e77cbc5df1b6fcb5242e18e5" + }, + "monitoring-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "928f8e068a5c3c85e4a8d515241f21e5cf6a38b39c597cf54b681197fa107853" + }, + "nestjs-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "0775f4ba14e4f372b894cc86feb0bf58a51b2038bc98b3ff18f2021565cea494" + }, + "nextjs-developer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "e53bdeaf766e6d5d29d1328f424777a8236f80f489134f1661bbda6c8c4149e9" + }, + "noizai-voice-workflow": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "1bf4fba1bd0c3bd4c453a9aec4f981409bc64b17775361db55c2cba7863cb881" + }, + "office-web-ui-system": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "574a097be28bab7f80d5bd998da1323501eb0adf755671c4020def825e5313d1" + }, + "pandas-pro": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "386d7588ba685df487079293be0f6d026467db579df9337c798aeb20e2d82a59" + }, + "php-best-practices": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "1b25a60572ddd8b206eaf68b067cde70a9d002df572de8fefda703532cecfdad" + }, + "php-pro": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "c163f6d5d26ddbf4a4d79e8dc95d3b41e447f1659486cf0aae8c070d950a9d86" + }, + "playwright-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "2715b8412ae840555d77d3d2c566e6f75ca42900637363d7574356af409ae10c" + }, + "postgres-pro": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "b5385f29408d4bd80666e608dbd7da3d4bed9153afa53b71bda94c6bf7ba8d51" + }, + "prd-writing": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "4c3141b2fdc77aac6a528bd9cee5e07d8122d75a89295f4020ceab112c1d44a9" + }, + "project-development-mindset": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "b8bf39985a51545efb84b396bf9c262bdd99f7b412be6d70a6f2ce28933324e3" + }, + "project-docs": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "d2da7474d61edf37883e91b31dec49efbcb5bc6979e8d132dceaa26c3fad1b61" + }, + "prompt-engineer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "c2574f1d0c4120117727dbfe79da8bf087b50635df30920ae1d2bc9b256ce7c6" + }, + "python-pro": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "fae3b07d03d2eb9f9112ceab0ef498ebafab33f1e095dff8c5a36de55ef68445" + }, + "rag-architect": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "650ce80b998ce2c3004c5276a1eabaa2ad4131d4256832eed9a5e06cdc98454d" + }, + "rails-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "e67a8886ff2a11da8133188a0bc0b48b9df213c6fc4bac4cbdd1ddacabb628c1" + }, + "react-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "498e8247c701522e0a63496a439680a409dafe67c3d8d7d5e4902db0e6222252" + }, + "react-native-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "f617f83be990728fef1ea2192c9b8ae85ef2c362e97f18782438f8bab357b02a" + }, + "react-vite-best-practices": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "82994934665a490b227fabdfec3d148b1fa291d77d82ccf978e761b9e1ff6e4c" + }, + "rust-engineer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "f8093c6253afe24bbac3c3328c3ca07e9ef089758896ba10221ed0f2078cb2a0" + }, + "salesforce-developer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "a4b5ae7acb98027b4aeb6a3eb31f301af20e30c6e0701b1d237b5714f159a479" + }, + "secure-code-guardian": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "36e0b6460df5ea86c0a6aeeb1d33ad6dfa8f8a404ba1dc47feb3cf93341cb2c6" + }, + "security-reviewer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "7a1ed4f4ca78a50b97e3e8c5725e494dc8aae0895bd1efde681a75ab22ff89d3" + }, + "seo-best-practices": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "4bc88f564290e835fd5f486bdacce1c3a867afaafa118363c9e80f6e6466a8d7" + }, + "shopify-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "b44431e08c55a0439ee41ed8893e6f6698f5e75e216b0a8f6c756cb5d461c016" + }, + "spark-engineer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "4c66f4104d2780f8075ca8e38084d009dfe7d2f8c594424f570611e50d4943b6" + }, + "spec-miner": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "91822456789ffa6f5641d4f586df7533757fbc02629b76cb6e68cce5e369b13a" + }, + "spring-boot-engineer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "ed065f5db45afe9dc2739cff713927d0b6b6ad3e667bdf7b40340d1a5543bb0f" + }, + "sql-pro": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "4bbeda009afc2d98d07cdcd5ad318ad7b8921073f74a3118b452b9c01947db0d" + }, + "sre-engineer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "f3bcde0381f316487e2475f01ed6951d6e1d5636c46c807c29f5efc5a802381e" + }, + "state-management": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "0bad07f74366ae633503a6f256eae7d7335c5ba9277110b4d788263d56a7a07a" + }, + "swift-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "3486707c8581c84d35d3da765ad9afd3744f93e9d208505bcb91c093e70d95c1" + }, + "symcli-skill": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "7e565fc813d15787deb8341c56e2bd964ce669ce8fbaad065b47841d635e28b6" + }, + "tailwind-best-practices": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "39145b4b9d12b154d9ff9261805d225557a1dcb5c881b1da8dcc027004aefd44" + }, + "technical-debt": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "a127f457928fb6dc439751ec0cdb6edef14c78e90b6fd6e26ff31eb18b2e90c3" + }, + "terraform-engineer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "04e717ca5d883a2839863f371f8ebba8b3f89cde3b2292cf90a3186c30b2d24a" + }, + "test-master": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "176d5be77c9fb4e64335a4ad9e38dfb82ccb029dc302c417db99a11e2d880180" + }, + "testing-best-practices": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "590eb4064d9fa41854f7871991a3c1ef566553593338632c6cf88d9984194d61" + }, + "the-fool": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "41aa64b8ccd474bd3c693454f4e0fc01a190512e01264817285a0527362d9756" + }, + "typescript-pro": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "b53b57340243eab4b75acee9096f0444319bb1b9e8530fdab4d1d75e1568fa6c" + }, + "typescript-react-patterns": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "650048e53310097d1e9029a668c0b39e5738ce70fbe3b23f35cd875fc05b48e5" + }, + "ui-mockup-visualizer": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "42248697304475f800e799899431c03845fb9f8841edb3f7b5fb9f8ba8fd1a10" + }, + "vps-docker-traefik-deploy": { + "source": "thienanblog/awesome-ai-agent-skills", + "sourceType": "github", + "computedHash": "fdb0cfdbebd039ddec2d8f3a2ee5734188c05e6b4bc85be377114f821f790895" + }, + "vue-expert": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "7276996f25e0e22316d480d12a51ea8f7441cc9855e79590ed0f5e2a25da4714" + }, + "vue-expert-js": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "f9480e6f932c64f0f87a609691bbf587978f21017f9104b4010c4890b03dea16" + }, + "web-design-guidelines": { + "source": "asyrafhussin/agent-skills", + "sourceType": "github", + "computedHash": "e27800d4c6a4cf98c50776af70d0b0ea1ebf5b81a03992b2019309f0ea9e7012" + }, + "websocket-engineer": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "8e5e1a8313d92d2d9631b163672291f7edd4cfdc4a7ede545fbcd863c0ab7f7b" + }, + "wordpress-pro": { + "source": "jeffallan/claude-skills", + "sourceType": "github", + "computedHash": "ef569ff0920fa0948aca9a464118e13b9a9d655f40fd5b61526b7e53a80f451c" + } + } +}