Add skill-manager skill (migrated from skill-creator) with deploy action

- Skill content moved from ~/.agents/skills/skill-creator, renamed to skill-manager
- New deploy CLI action: installs any skill via absolute-path symlinks (or copies)
  into $HOME/.agents/skills, $HOME/.claude/skills and $HOME/.cline/skills
- Taskfile + shell module wrappers (task deploy / cli:deploy)
- SKILL.md: deploy docs, origin-repository/origin-path metadata

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 22:32:42 +03:00
co-authored by Claude Fable 5
parent cc30987dd2
commit 6042272ccc
62 changed files with 3474 additions and 1 deletions
+11
View File
@@ -0,0 +1,11 @@
# https://taskfile.dev
version: "3"
tasks:
environment:show:
desc: "Show environment vars"
cmds:
- |
./.scripts/base/api/environment-show.sh {{ .CLI_ARGS }}
silent: true
@@ -0,0 +1,9 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/base/lib/--index.sh
_base_environment_show "$@"
@@ -0,0 +1,28 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
export LOCAL_HOME_DIR=$(pwd)
export LOCAL_HOME_DIR_NAME=${PWD##*/}
export LOCAL_GIT_ROOT_DIR=$(dirname "${LOCAL_HOME_DIR}")
TMP_BASE_FILE_DOTENV=".env"
if
[ -e "${TMP_BASE_FILE_DOTENV}" ]
then
export $(
grep -v '^#' "${TMP_BASE_FILE_DOTENV}" | xargs
) >/dev/null 2>&1
fi
ALL_ARGS=("$@")
while [[ "$#" -gt 0 ]]; do
case $1 in
*) ;;
esac
shift
done
set -- "${ALL_ARGS[@]}"
@@ -0,0 +1,10 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/base/lib/--index-api.sh
# No required environment variables for skill-manager base
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/base/lib/-ensure-environment-variable.sh
. ./.scripts/base/lib/-environment-show.sh
. ./.scripts/base/lib/-mask.sh
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/--index.sh
#
# --> passed parameters are read & exported environment variables
#
. ./.scripts/base/lib/--env-vars-reader.sh
#
# --> required environment variables are validated for existence
#
. ./.scripts/base/lib/--env-vars-validator.sh
#
# --> available functions are imported/exported
#
. ./.scripts/base/lib/--index-api.sh
# -------------------------------------------------------------------------------------
@@ -0,0 +1,18 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/--index-api.sh
_base_ensureEnvironmentVariable() {
local FUNCTION_NAME="_base_ensureEnvironmentVariable"
local ENV_VAR_NAME="$1"
_loggers_info "${FUNCTION_NAME}" "ENV_VAR_NAME: ${ENV_VAR_NAME}"
if [ -z "${!ENV_VAR_NAME}" ]; then
_loggers_error "${FUNCTION_NAME}" "Missing required environment variable: ${ENV_VAR_NAME}. Check .env or .env-* files!"
exit 1
fi
}
@@ -0,0 +1,16 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/--index-api.sh
_base_environment_show() {
local FUNCTION_NAME="_base_environment_show"
_loggers_info "${FUNCTION_NAME}" "LOCAL_HOME_DIR: ${LOCAL_HOME_DIR}"
_loggers_info "${FUNCTION_NAME}" "LOCAL_HOME_DIR_NAME: ${LOCAL_HOME_DIR_NAME}"
}
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_base_mask() {
local original_value="$1"
local RET_VAL
if
[[ -n "$original_value" && ${#original_value} -gt 2 ]]
then
RET_VAL="${original_value:0:1}$(printf '%*s' $((${#original_value} - 2)) '' | tr ' ' '*')${original_value: -1}"
else
RET_VAL="$(printf '%*s' ${#original_value} '' | tr ' ' '*')"
fi
echo "${RET_VAL}"
}
+38
View File
@@ -0,0 +1,38 @@
# https://taskfile.dev
version: "3"
tasks:
build:
desc: Compile TypeScript source to dist/
cmds:
- |
./.scripts/cli/api/build.sh {{ .CLI_ARGS }}
silent: true
validate:
desc: |
Validate a skill directory against the agentskills.io spec.
Usage: task cli:validate -- --skill-dir="/absolute/path/to/skill"
cmds:
- |
./.scripts/cli/api/validate.sh {{ .CLI_ARGS }}
silent: true
scaffold:
desc: |
Scaffold a new skill directory from the built-in template.
Usage: task cli:scaffold -- --skill-name="my-skill" [--output-dir="/path/to/.agents/skills"]
cmds:
- |
./.scripts/cli/api/scaffold.sh {{ .CLI_ARGS }}
silent: true
deploy:
desc: |
Deploy a skill into $HOME/.agents/skills, $HOME/.claude/skills and $HOME/.cline/skills (absolute paths).
Usage: task cli:deploy -- --skill-dir="/absolute/path/to/skill" [--mode=symlink|copy] [--force]
cmds:
- |
./.scripts/cli/api/deploy.sh {{ .CLI_ARGS }}
silent: true
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
set -e
. ./.scripts/cli/lib/--index.sh
_cli__build "$@"
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
set -e
. ./.scripts/cli/lib/--index.sh
_cli__deploy "$@"
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
set -e
. ./.scripts/cli/lib/--index.sh
_cli__scaffold "$@"
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
set -e
. ./.scripts/cli/lib/--index.sh
_cli__validate "$@"
@@ -0,0 +1,15 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
ALL_ARGS=("$@")
while [[ "$#" -gt 0 ]]; do
case $1 in
*) ;;
esac
shift
done
set -- "${ALL_ARGS[@]}"
@@ -0,0 +1,10 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/cli/lib/--index-api.sh
# No required environment variables for cli module
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/cli/lib/-build.sh
. ./.scripts/cli/lib/-validate.sh
. ./.scripts/cli/lib/-scaffold.sh
. ./.scripts/cli/lib/-deploy.sh
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/--index.sh
. ./.scripts/base/lib/--index.sh
#
# --> passed parameters are read & exported environment variables
#
. ./.scripts/cli/lib/--env-vars-reader.sh
#
# --> required environment variables are validated for existence
#
. ./.scripts/cli/lib/--env-vars-validator.sh
#
# --> available functions are imported/exported
#
. ./.scripts/cli/lib/--index-api.sh
# -------------------------------------------------------------------------------------
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_cli__build() {
local FUNCTION_NAME="_cli__build"
_loggers_info "${FUNCTION_NAME}" "Compiling TypeScript source to dist/"
pnpm run build
}
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_cli__deploy() {
local FUNCTION_NAME="_cli__deploy"
_loggers_info "${FUNCTION_NAME}" "Deploying skill into agent skill directories via TypeScript CLI"
npx tsx src/cli/commands.ts --action deploy "$@"
}
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_cli__scaffold() {
local FUNCTION_NAME="_cli__scaffold"
_loggers_info "${FUNCTION_NAME}" "Running skill scaffolder via TypeScript CLI"
npx tsx src/cli/commands.ts --action scaffold "$@"
}
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_cli__validate() {
local FUNCTION_NAME="_cli__validate"
_loggers_info "${FUNCTION_NAME}" "Running skill validator via TypeScript CLI"
npx tsx src/cli/commands.ts --action validate "$@"
}
@@ -0,0 +1,18 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
export LOGGER_TRAILING_NEW_LINE_ENABLED="TRUE"
export LOGGER_IS_ENABLED_ERROR=true
export LOGGER_IS_ENABLED_INFO=true
export LOGGER_IS_ENABLED_WARN=true
export LOGGER_IS_ENABLED_DEBUG=true
export LOGGER_IS_ENABLED_TRACE=false
export LOCAL_STRING_005_SPACES=" "
export LOCAL_STRING_010_SPACES="${LOCAL_STRING_005_SPACES}${LOCAL_STRING_005_SPACES}"
export LOCAL_STRING_050_SPACES="${LOCAL_STRING_010_SPACES}${LOCAL_STRING_010_SPACES}${LOCAL_STRING_010_SPACES}${LOCAL_STRING_010_SPACES}${LOCAL_STRING_010_SPACES}"
export LOCAL_STRING_070_SPACES="${LOCAL_STRING_050_SPACES}${LOCAL_STRING_010_SPACES}${LOCAL_STRING_010_SPACES}"
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/--env-vars-reader.sh
. ./.scripts/loggers/lib/--index.sh
@@ -0,0 +1,22 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
. ./.scripts/loggers/lib/-info.sh
. ./.scripts/loggers/lib/-trace.sh
. ./.scripts/loggers/lib/-debug.sh
. ./.scripts/loggers/lib/-error.sh
. ./.scripts/loggers/lib/-warn.sh
. ./.scripts/loggers/lib/-printf--debug.sh
. ./.scripts/loggers/lib/-printf--info.sh
. ./.scripts/loggers/lib/-printf--trace.sh
. ./.scripts/loggers/lib/-printf--warn.sh
. ./.scripts/loggers/lib/-empty-line.sh
. ./.scripts/loggers/lib/-waiting-dot.sh
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
#
# --> passed parameters are read & exported environment variables
#
. ./.scripts/loggers/lib/--env-vars-reader.sh
#
# --> available functions are imported/exported
#
. ./.scripts/loggers/lib/--index-api.sh
# -------------------------------------------------------------------------------------
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_debug() {
_loggers_enableLoggerTrailingNewLine
if
[ "${LOGGER_IS_ENABLED_DEBUG}" = true ]
then
local TMP_1ST_PARAM="${1}${LOCAL_STRING_070_SPACES}"
TMP_1ST_PARAM="${TMP_1ST_PARAM:0:70}"
local TMP_LINE="# DEBUG # ${TMP_1ST_PARAM} # ${2}"
echo -e "\033[1;36m${TMP_LINE}\033[0m" >&2
fi
}
@@ -0,0 +1,13 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_emptyLine() {
_loggers_enableLoggerTrailingNewLine
echo -e "" >&2
}
@@ -0,0 +1,15 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
_loggers_enableLoggerTrailingNewLine() {
if
[ -z "$LOGGER_TRAILING_NEW_LINE_DISABLED" ]
then
echo "" >&2
export LOGGER_TRAILING_NEW_LINE_DISABLED="TRUE"
fi
}
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_error() {
_loggers_enableLoggerTrailingNewLine
if
[ "${LOGGER_IS_ENABLED_ERROR}" = true ]
then
local TMP_1ST_PARAM="${1}${LOCAL_STRING_070_SPACES}"
TMP_1ST_PARAM="${TMP_1ST_PARAM:0:70}"
local TMP_LINE="# ERROR # ${TMP_1ST_PARAM} # $2"
echo -e "\033[1;31m${TMP_LINE}\033[0m" >&2
fi
}
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_info() {
_loggers_enableLoggerTrailingNewLine
if
[ "${LOGGER_IS_ENABLED_INFO}" = true ]
then
local TMP_1ST_PARAM="${1}${LOCAL_STRING_070_SPACES}"
TMP_1ST_PARAM="${TMP_1ST_PARAM:0:70}"
local TMP_LINE="# INFO # ${TMP_1ST_PARAM} # ${2}"
echo -e "${TMP_LINE}" >&2
fi
}
@@ -0,0 +1,14 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_loggers_printf__debug() {
if
[ "${LOGGER_IS_ENABLED_DEBUG}" = true ]
then
printf '\033[1;36m%s\033[0m\n' "$*" >&2
fi
}
@@ -0,0 +1,14 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_loggers_printf__info() {
if
[ "${LOGGER_IS_ENABLED_INFO}" = true ]
then
printf '\033[1;32m%s\033[0m\n' "$*" >&2
fi
}
@@ -0,0 +1,14 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_loggers_printf__trace() {
if
[ "${LOGGER_IS_ENABLED_TRACE}" = true ]
then
printf '\033[0;90m%s\033[0m\n' "$*" >&2
fi
}
@@ -0,0 +1,14 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_loggers_printf__warn() {
if
[ "${LOGGER_IS_ENABLED_WARN}" = true ]
then
printf '\033[1;33m%s\033[0m\n' "$*" >&2
fi
}
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_trace() {
_loggers_enableLoggerTrailingNewLine
if
[ "${LOGGER_IS_ENABLED_TRACE}" = true ]
then
local TMP_1ST_PARAM="${1}${LOCAL_STRING_070_SPACES}"
TMP_1ST_PARAM="${TMP_1ST_PARAM:0:70}"
local TMP_LINE="# TRACE # ${TMP_1ST_PARAM} # $2"
echo -e "\033[0;94m${TMP_LINE}\033[0m" >&2
fi
}
@@ -0,0 +1,13 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_waitingDot() {
echo -n "." >&2
export LOGGER_TRAILING_NEW_LINE_DISABLED=""
}
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_warn() {
_loggers_enableLoggerTrailingNewLine
if
[ "${LOGGER_IS_ENABLED_WARN}" = true ]
then
local TMP_1ST_PARAM="${1}${LOCAL_STRING_070_SPACES}"
TMP_1ST_PARAM="${TMP_1ST_PARAM:0:70}"
local TMP_LINE="# WARN # ${TMP_1ST_PARAM} # $2"
echo -e "\033[1;33m${TMP_LINE}\033[0m" >&2
fi
}
+11
View File
@@ -0,0 +1,11 @@
# https://taskfile.dev
version: "3"
tasks:
execute:
desc: -- --skill-dir="<path>"
cmds:
- |
./.scripts/validator/api/skill--execute.sh {{ .CLI_ARGS }}
silent: true
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
# validator/api/skill--execute.sh — thin API wrapper for skill validation
. ./.scripts/validator/lib/--index.sh
_validator__skill__execute "$@"
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
# validate/lib/--env-vars-reader.sh — environment loading for the validate module
# This module operates without env files (all input via CLI flags).
# No env loading required.
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
# validate/lib/--env-vars-validator.sh — env validation for the validate module
# This module takes all input via --skill-dir CLI flag.
# No environment variables required.
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
. ./.scripts/validator/lib/-skill--execute.sh
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/base/lib/--index.sh
#
# --> passed parameters are read & exported environment variables
#
. ./.scripts/validator/lib/--env-vars-reader.sh
#
# --> required environment variables are validated for existence
#
. ./.scripts/validator/lib/--env-vars-validator.sh
#
# --> available functions are imported/exported
#
. ./.scripts/validator/lib/--index-api.sh
# -------------------------------------------------------------------------------------
+212
View File
@@ -0,0 +1,212 @@
#!/bin/bash
# validator/lib/-skill--execute.sh — validate a skill directory against the agentskills.io spec
_validator__skill__execute() {
local FUNCTION_NAME="_validator__skill__execute"
local SKILL_DIR=""
# ── Parse args ──────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--skill-dir=*) SKILL_DIR="${1#*=}"; shift ;;
--skill-dir) SKILL_DIR="$2"; shift 2 ;;
--help|-h)
cat <<EOF
Usage: task -t scripts/Taskfile.yml validate-shell -- --skill-dir="<path>"
Validate an Agent Skill directory against the agentskills.io specification.
Options:
--skill-dir PATH Path to the skill directory to validate (required)
Checks:
- SKILL.md exists
- YAML frontmatter is present (--- delimiters)
- 'name' field is present and valid (a-z, 0-9, hyphens only)
- 'name' matches the directory name
- 'name' is <= 64 characters, no leading/trailing/consecutive hyphens
- 'description' field is present and <= 1024 characters
- SKILL.md body is <= 500 lines (warning if exceeded)
- Optional directories: scripts/, references/, assets/
Exit codes:
0 All checks passed
1 One or more checks failed
2 Usage error
Examples:
task -t scripts/Taskfile.yml validate-shell -- --skill-dir=".agents/skills/my-skill"
EOF
return 0
;;
*) echo "Error: unknown argument: $1" >&2; return 2 ;;
esac
done
# ── Validate required args ──────────────────────────────────────────────────
if [[ -z "$SKILL_DIR" ]]; then
echo "Error: --skill-dir is required." >&2
echo "Usage: task -t scripts/Taskfile.yml validate-shell -- --skill-dir=\"<path>\"" >&2
return 2
fi
SKILL_DIR="${SKILL_DIR%/}" # strip trailing slash
# ── Counters ────────────────────────────────────────────────────────────────
local PASS=0 FAIL=0 WARN=0
_pass() { echo " ✅ $1"; ((PASS++)) || true; }
_fail() { echo " ❌ $1"; ((FAIL++)) || true; }
_warn() { echo " ⚠️ $1"; ((WARN++)) || true; }
_info() { echo " ℹ️ $1"; }
echo ""
echo "Validating skill: $SKILL_DIR"
echo "──────────────────────────────────────────"
# ── Check: directory exists ─────────────────────────────────────────────────
if [[ ! -d "$SKILL_DIR" ]]; then
echo "Error: directory not found: $SKILL_DIR" >&2
return 2
fi
local SKILL_FILE="$SKILL_DIR/SKILL.md"
local DIR_NAME
DIR_NAME="$(basename "$SKILL_DIR")"
# ── Check: SKILL.md exists ──────────────────────────────────────────────────
if [[ ! -f "$SKILL_FILE" ]]; then
_fail "SKILL.md not found in $SKILL_DIR"
echo ""; echo "Result: ❌ FAILED"; return 1
fi
_pass "SKILL.md exists"
# ── Check: frontmatter delimiters ───────────────────────────────────────────
local CONTENT
CONTENT="$(cat "$SKILL_FILE")"
if ! echo "$CONTENT" | grep -q "^---$"; then
_fail "SKILL.md is missing YAML frontmatter (--- delimiters)"
echo ""; echo "Result: ❌ FAILED"; return 1
fi
_pass "YAML frontmatter delimiters found"
# ── Extract frontmatter ─────────────────────────────────────────────────────
local FRONTMATTER
FRONTMATTER="$(awk '/^---$/{if(p==0){p=1;next}else{exit}} p{print}' "$SKILL_FILE")"
# ── Check: name field ───────────────────────────────────────────────────────
local NAME_RAW NAME
NAME_RAW="$(echo "$FRONTMATTER" | grep "^name:" | head -1 | sed 's/^name:[[:space:]]*//')"
NAME="$(echo "$NAME_RAW" | tr -d "'\"" | tr -d '[:space:]')"
if [[ -z "$NAME" ]]; then
_fail "'name' field is missing or empty"
else
_pass "'name' field is present: $NAME"
if [[ "$NAME" != "$DIR_NAME" ]]; then
_fail "'name' ($NAME) does not match directory name ($DIR_NAME)"
else
_pass "'name' matches directory name"
fi
local NAME_LEN="${#NAME}"
if [[ $NAME_LEN -gt 64 ]]; then
_fail "'name' is $NAME_LEN characters (max 64)"
else
_pass "'name' length is $NAME_LEN (max 64)"
fi
if echo "$NAME" | grep -q '[^a-z0-9-]'; then
_fail "'name' contains invalid characters (only a-z, 0-9, - allowed)"
else
_pass "'name' uses only allowed characters (a-z, 0-9, -)"
fi
if [[ "$NAME" == -* ]]; then
_fail "'name' must not start with a hyphen"
else
_pass "'name' does not start with a hyphen"
fi
if [[ "$NAME" == *- ]]; then
_fail "'name' must not end with a hyphen"
else
_pass "'name' does not end with a hyphen"
fi
if echo "$NAME" | grep -q "\-\-"; then
_fail "'name' must not contain consecutive hyphens (--)"
else
_pass "'name' has no consecutive hyphens"
fi
fi
# ── Check: description field ────────────────────────────────────────────────
local DESC_LINE
DESC_LINE="$(echo "$FRONTMATTER" | grep "^description:" | head -1)"
if [[ -z "$DESC_LINE" ]]; then
_fail "'description' field is missing"
else
local DESC_VALUE
DESC_VALUE="$(echo "$DESC_LINE" | sed 's/^description:[[:space:]]*//')"
local DESC_FULL
if [[ "$DESC_VALUE" == ">" || "$DESC_VALUE" == "|" || -z "$DESC_VALUE" ]]; then
local DESC_BODY
DESC_BODY="$(awk '/^description:/{found=1; next} found && /^ /{print; next} found && /^[^ ]/{exit}' "$SKILL_FILE" | sed 's/^ //')"
DESC_FULL="$(echo "$DESC_BODY" | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
else
DESC_FULL="$DESC_VALUE"
fi
if [[ -z "$DESC_FULL" ]]; then
_fail "'description' is empty"
else
local DESC_LEN="${#DESC_FULL}"
_pass "'description' is present (${DESC_LEN} chars)"
if [[ $DESC_LEN -gt 1024 ]]; then
_fail "'description' is $DESC_LEN characters (max 1024)"
else
_pass "'description' length is $DESC_LEN (max 1024)"
fi
fi
fi
# ── Check: body length ──────────────────────────────────────────────────────
local BODY_LINES
BODY_LINES="$(awk '/^---$/{n++} n>=2{print}' "$SKILL_FILE" | wc -l | tr -d ' ')"
if [[ $BODY_LINES -gt 500 ]]; then
_warn "SKILL.md body is $BODY_LINES lines (recommended max 500). Consider moving detail to references/."
else
_pass "SKILL.md body is $BODY_LINES lines (max 500 recommended)"
fi
# ── Optional directories ────────────────────────────────────────────────────
echo ""
_info "Optional directories:"
for dir in scripts references assets; do
if [[ -d "$SKILL_DIR/$dir" ]]; then
local FILE_COUNT
FILE_COUNT="$(find "$SKILL_DIR/$dir" -type f | wc -l | tr -d ' ')"
_info " $dir/ — found ($FILE_COUNT files)"
else
_info " $dir/ — not present"
fi
done
# ── Summary ─────────────────────────────────────────────────────────────────
echo ""
echo "──────────────────────────────────────────"
echo "Passed: $PASS | Failed: $FAIL | Warnings: $WARN"
echo ""
if [[ $FAIL -gt 0 ]]; then
echo "Result: ❌ FAILED"; return 1
elif [[ $WARN -gt 0 ]]; then
echo "Result: ✅ PASSED (with warnings)"; return 0
else
echo "Result: ✅ PASSED"; return 0
fi
}