feat: install.sh — support double format bundle/legacy (amelioration.md)
Double détection dans scan_skills() : - Bundle : dossier avec SKILL.md, agents: lu dans le frontmatter, copie récursive du dossier complet (scripts/, templates/, references/) - Legacy : <agent>.md existant, agent déduit du nom de fichier - Priorité bundle sur legacy pour un même cat/skill/agent - Nouveau champ get_frontmatter_agents() pour parser agents: [...] - SKILLS_LIST étendu : ...|kind|source_path (10 champs) - install_selected() branche sur kind=bundle vs legacy - preview_script utilise source_path pour trouver le fichier à afficher ha-log-investigator (bundle avec scripts/ et references/) est maintenant détecté et installé correctement. Tests : section 5 ajoutée — 9 nouveaux cas (bundle, legacy, références ignorées, doublon, priorité, source_path, accessibilité fichiers). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+93
-38
@@ -234,6 +234,12 @@ get_frontmatter_tags() {
|
|||||||
| sed 's/^tags:[[:space:]]*//' | tr -d '[] ' | tr ',' '#' | sed 's/^/#/'
|
| sed 's/^tags:[[:space:]]*//' | tr -d '[] ' | tr ',' '#' | sed 's/^/#/'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Parse "agents: [claude-code, codex]" → une ligne par agent
|
||||||
|
get_frontmatter_agents() {
|
||||||
|
grep "^agents:" "$1" 2>/dev/null | head -1 \
|
||||||
|
| sed 's/^agents:[[:space:]]*//' | tr -d '[]' | tr ',' '\n' | tr -d ' ' | grep -v '^$'
|
||||||
|
}
|
||||||
|
|
||||||
# Retourne 0 (succès) si ver2 est plus récente que ver1
|
# Retourne 0 (succès) si ver2 est plus récente que ver1
|
||||||
version_is_newer() {
|
version_is_newer() {
|
||||||
local ver1="$1" ver2="$2"
|
local ver1="$1" ver2="$2"
|
||||||
@@ -264,13 +270,65 @@ get_local_version() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ── Scan des skills disponibles ───────────────────────────────────
|
# ── Scan des skills disponibles ───────────────────────────────────
|
||||||
# Format : "cat|skill|agent|etat|repo_version|local_version"
|
# Format : "cat|skill|agent|etat|repo_ver|local_ver|desc|tags|kind|source_path"
|
||||||
|
# kind=bundle → dossier SKILL.md+ressources | kind=legacy → fichier <agent>.md
|
||||||
SKILLS_LIST=()
|
SKILLS_LIST=()
|
||||||
|
|
||||||
|
_skill_agent_ok() {
|
||||||
|
local agent="$1"
|
||||||
|
[[ ${#DETECTED_AGENTS[@]} -eq 0 ]] && return 0
|
||||||
|
for a in "${DETECTED_AGENTS[@]}"; do [[ "$a" == "$agent" ]] && return 0; done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_build_entry() {
|
||||||
|
local cat="$1" skill="$2" agent="$3" kind="$4" source_path="$5" skill_file="$6"
|
||||||
|
local repo_ver; repo_ver=$(get_frontmatter_field "$skill_file" "version")
|
||||||
|
local local_ver; local_ver=$(get_local_version "$cat" "$skill" "$agent")
|
||||||
|
local etat
|
||||||
|
if [[ -z "$local_ver" ]]; then
|
||||||
|
etat="new"
|
||||||
|
elif version_is_newer "$local_ver" "$repo_ver"; then
|
||||||
|
etat="upd"
|
||||||
|
else
|
||||||
|
etat="ok"
|
||||||
|
fi
|
||||||
|
local desc; desc=$(get_frontmatter_desc "$skill_file")
|
||||||
|
local tags; tags=$(get_frontmatter_tags "$skill_file")
|
||||||
|
echo "${cat}|${skill}|${agent}|${etat}|${repo_ver}|${local_ver}|${desc}|${tags}|${kind}|${source_path}"
|
||||||
|
}
|
||||||
|
|
||||||
scan_skills() {
|
scan_skills() {
|
||||||
header "Scan des skills disponibles"
|
header "Scan des skills disponibles"
|
||||||
SKILLS_LIST=()
|
SKILLS_LIST=()
|
||||||
|
declare -A seen_combos
|
||||||
|
|
||||||
|
# ── Chemin 1 : bundles (dossier avec SKILL.md, agents: dans le frontmatter)
|
||||||
|
while IFS= read -r skill_file; do
|
||||||
|
local rel="${skill_file#${REPO_DIR}/skills/}"
|
||||||
|
local skill_dir="${rel%/SKILL.md}"
|
||||||
|
local cat="${skill_dir%%/*}"
|
||||||
|
local skill="${skill_dir#*/}"
|
||||||
|
local source_dir="${REPO_DIR}/skills/${cat}/${skill}"
|
||||||
|
|
||||||
|
local raw_agents; raw_agents=$(get_frontmatter_agents "$skill_file")
|
||||||
|
if [[ -z "$raw_agents" ]]; then
|
||||||
|
debug "Bundle sans agents: ignoré : $cat/$skill"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r agent; do
|
||||||
|
_skill_agent_ok "$agent" || continue
|
||||||
|
[[ -n "$SKILLS_TAG" ]] && { grep -q "$SKILLS_TAG" "$skill_file" || continue; }
|
||||||
|
local entry; entry=$(_build_entry "$cat" "$skill" "$agent" "bundle" "$source_dir" "$skill_file")
|
||||||
|
SKILLS_LIST+=("$entry")
|
||||||
|
seen_combos["${cat}|${skill}|${agent}"]=1
|
||||||
|
debug "Bundle : $cat/$skill [$agent]"
|
||||||
|
done <<< "$raw_agents"
|
||||||
|
done < <(find "${REPO_DIR}/skills" -name "SKILL.md" | sort)
|
||||||
|
|
||||||
|
# ── Chemin 2 : legacy (<agent>.md — agents connus uniquement)
|
||||||
|
local known_agents="claude-code gemini-cli codex hermes"
|
||||||
while IFS= read -r skill_file; do
|
while IFS= read -r skill_file; do
|
||||||
local rel="${skill_file#${REPO_DIR}/skills/}"
|
local rel="${skill_file#${REPO_DIR}/skills/}"
|
||||||
local agent_file="${rel##*/}"
|
local agent_file="${rel##*/}"
|
||||||
@@ -279,37 +337,21 @@ scan_skills() {
|
|||||||
local cat="${skill_path%%/*}"
|
local cat="${skill_path%%/*}"
|
||||||
local skill="${skill_path#*/}"
|
local skill="${skill_path#*/}"
|
||||||
|
|
||||||
# Filtre agent
|
# Ignorer les fichiers qui ne correspondent pas à un agent connu
|
||||||
local agent_detected=0
|
local is_known=0
|
||||||
if [[ ${#DETECTED_AGENTS[@]} -gt 0 ]]; then
|
for ka in $known_agents; do [[ "$ka" == "$agent" ]] && is_known=1; done
|
||||||
for a in "${DETECTED_AGENTS[@]}"; do
|
[[ "$is_known" -eq 0 ]] && continue
|
||||||
[[ "$a" == "$agent" ]] && agent_detected=1
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
[[ "$agent_detected" -eq 0 && ${#DETECTED_AGENTS[@]} -gt 0 ]] && continue
|
|
||||||
|
|
||||||
# Filtre tag
|
# Le bundle a priorité : ignorer si déjà vu
|
||||||
if [[ -n "$SKILLS_TAG" ]]; then
|
[[ -n "${seen_combos[${cat}|${skill}|${agent}]:-}" ]] && continue
|
||||||
grep -q "$SKILLS_TAG" "$skill_file" || continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
local repo_ver; repo_ver=$(get_frontmatter_field "$skill_file" "version")
|
_skill_agent_ok "$agent" || continue
|
||||||
local local_ver; local_ver=$(get_local_version "$cat" "$skill" "$agent")
|
[[ -n "$SKILLS_TAG" ]] && { grep -q "$SKILLS_TAG" "$skill_file" || continue; }
|
||||||
local etat
|
|
||||||
|
|
||||||
if [[ -z "$local_ver" ]]; then
|
local entry; entry=$(_build_entry "$cat" "$skill" "$agent" "legacy" "$skill_file" "$skill_file")
|
||||||
etat="new"
|
SKILLS_LIST+=("$entry")
|
||||||
elif version_is_newer "$local_ver" "$repo_ver"; then
|
debug "Legacy : $cat/$skill [$agent]"
|
||||||
etat="upd"
|
done < <(find "${REPO_DIR}/skills" -name "*.md" ! -name "SKILL.md" | sort)
|
||||||
else
|
|
||||||
etat="ok"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local desc; desc=$(get_frontmatter_desc "$skill_file")
|
|
||||||
local tags; tags=$(get_frontmatter_tags "$skill_file")
|
|
||||||
SKILLS_LIST+=("${cat}|${skill}|${agent}|${etat}|${repo_ver}|${local_ver}|${desc}|${tags}")
|
|
||||||
debug "Skill trouvé : $cat/$skill [$agent] état=$etat"
|
|
||||||
done < <(find "${REPO_DIR}/skills" -name "*.md" | sort)
|
|
||||||
|
|
||||||
ok "${#SKILLS_LIST[@]} skill(s) trouvé(s)"
|
ok "${#SKILLS_LIST[@]} skill(s) trouvé(s)"
|
||||||
}
|
}
|
||||||
@@ -588,8 +630,12 @@ td="$1"; type="${td%%:*}"; value="${td#*:}"
|
|||||||
if [[ "$type" == "s" ]]; then
|
if [[ "$type" == "s" ]]; then
|
||||||
entry="${SKILLS_LIST[$value]:-}"
|
entry="${SKILLS_LIST[$value]:-}"
|
||||||
[[ -z "$entry" ]] && exit 0
|
[[ -z "$entry" ]] && exit 0
|
||||||
IFS='|' read -r cat skill agent _ <<< "$entry"
|
IFS='|' read -r cat skill agent _ _ _ _ _ kind source_path <<< "$entry"
|
||||||
skill_file="${REPO_DIR}/skills/${cat}/${skill}/${agent}.md"
|
if [[ "$kind" == "bundle" ]]; then
|
||||||
|
skill_file="${source_path}/SKILL.md"
|
||||||
|
else
|
||||||
|
skill_file="$source_path"
|
||||||
|
fi
|
||||||
if [[ ! -f "$skill_file" ]]; then echo "Fichier introuvable : $skill_file"; exit 0; fi
|
if [[ ! -f "$skill_file" ]]; then echo "Fichier introuvable : $skill_file"; exit 0; fi
|
||||||
if command -v bat &>/dev/null; then
|
if command -v bat &>/dev/null; then
|
||||||
bat --style=numbers,header --color=always --language=markdown "$skill_file"
|
bat --style=numbers,header --color=always --language=markdown "$skill_file"
|
||||||
@@ -733,8 +779,8 @@ install_selected() {
|
|||||||
local count_install=0 count_update=0 count_skip=0
|
local count_install=0 count_update=0 count_skip=0
|
||||||
|
|
||||||
for entry in "${SKILLS_LIST[@]}"; do
|
for entry in "${SKILLS_LIST[@]}"; do
|
||||||
local cat skill agent etat repo_ver local_ver
|
local cat skill agent etat repo_ver local_ver desc tags kind source_path
|
||||||
IFS='|' read -r cat skill agent etat repo_ver local_ver <<< "$entry"
|
IFS='|' read -r cat skill agent etat repo_ver local_ver desc tags kind source_path <<< "$entry"
|
||||||
local key; key=$(make_key "$entry")
|
local key; key=$(make_key "$entry")
|
||||||
local action; action=$(state_get "$key")
|
local action; action=$(state_get "$key")
|
||||||
|
|
||||||
@@ -746,16 +792,25 @@ install_selected() {
|
|||||||
local scope="$action"
|
local scope="$action"
|
||||||
[[ "$action" == "update" ]] && scope="local"
|
[[ "$action" == "update" ]] && scope="local"
|
||||||
|
|
||||||
local src="${REPO_DIR}/skills/${cat}/${skill}/${agent}.md"
|
|
||||||
local dest; dest=$(get_dest_path "$cat" "$skill" "$agent" "$scope")
|
local dest; dest=$(get_dest_path "$cat" "$skill" "$agent" "$scope")
|
||||||
|
|
||||||
debug "Copie $src → $dest"
|
if [[ "$kind" == "bundle" ]]; then
|
||||||
|
local dest_dir; dest_dir="$(dirname "$dest")"
|
||||||
|
debug "Bundle $source_path/ → $dest_dir/"
|
||||||
if [[ "$SKILLS_DRY_RUN" == "1" ]]; then
|
if [[ "$SKILLS_DRY_RUN" == "1" ]]; then
|
||||||
info "[DRY-RUN] cp $src → $dest"
|
info "[DRY-RUN] cp -r $source_path/ → $dest_dir/"
|
||||||
|
else
|
||||||
|
mkdir -p "$dest_dir"
|
||||||
|
cp -r "${source_path}/." "$dest_dir/"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
debug "Legacy $source_path → $dest"
|
||||||
|
if [[ "$SKILLS_DRY_RUN" == "1" ]]; then
|
||||||
|
info "[DRY-RUN] cp $source_path → $dest"
|
||||||
else
|
else
|
||||||
mkdir -p "$(dirname "$dest")"
|
mkdir -p "$(dirname "$dest")"
|
||||||
cp "$src" "$dest"
|
cp "$source_path" "$dest"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$action" == "update" ]]; then
|
if [[ "$action" == "update" ]]; then
|
||||||
|
|||||||
@@ -161,6 +161,111 @@ assert_eq "cycle update→local" "local" "$(state_get "dev_debugging_claude_code
|
|||||||
rm -f "$STATE_FILE"
|
rm -f "$STATE_FILE"
|
||||||
unset STATE_FILE || true
|
unset STATE_FILE || true
|
||||||
|
|
||||||
|
# ── 5. Détection bundle / legacy ─────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "5. get_frontmatter_agents() / scan_skills() double format"
|
||||||
|
|
||||||
|
TMP_DIR=$(mktemp -d)
|
||||||
|
|
||||||
|
# Skill bundle : SKILL.md + agents: [claude-code, codex]
|
||||||
|
mkdir -p "$TMP_DIR/skills/infra/mon-bundle"
|
||||||
|
cat > "$TMP_DIR/skills/infra/mon-bundle/SKILL.md" << 'EOF'
|
||||||
|
---
|
||||||
|
name: mon-bundle
|
||||||
|
version: 1.2.0
|
||||||
|
agents: [claude-code, codex]
|
||||||
|
description: Skill bundle test
|
||||||
|
tags: [test]
|
||||||
|
---
|
||||||
|
# Mon bundle
|
||||||
|
EOF
|
||||||
|
mkdir -p "$TMP_DIR/skills/infra/mon-bundle/scripts"
|
||||||
|
touch "$TMP_DIR/skills/infra/mon-bundle/scripts/helper.sh"
|
||||||
|
|
||||||
|
# Skill legacy : claude-code.md
|
||||||
|
mkdir -p "$TMP_DIR/skills/dev/ancien"
|
||||||
|
cat > "$TMP_DIR/skills/dev/ancien/claude-code.md" << 'EOF'
|
||||||
|
---
|
||||||
|
name: ancien
|
||||||
|
version: 0.5.0
|
||||||
|
description: Skill legacy
|
||||||
|
tags: [legacy]
|
||||||
|
---
|
||||||
|
# Ancien skill
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Fichier Markdown de support (ne doit PAS être détecté comme skill)
|
||||||
|
mkdir -p "$TMP_DIR/skills/infra/mon-bundle/references"
|
||||||
|
echo "# Ref doc" > "$TMP_DIR/skills/infra/mon-bundle/references/notes.md"
|
||||||
|
|
||||||
|
assert_eq "agents: [claude-code, codex] → 2 agents" \
|
||||||
|
"claude-code
|
||||||
|
codex" \
|
||||||
|
"$(get_frontmatter_agents "$TMP_DIR/skills/infra/mon-bundle/SKILL.md")"
|
||||||
|
|
||||||
|
assert_eq "agents: absent → vide" \
|
||||||
|
"" \
|
||||||
|
"$(get_frontmatter_agents "$TMP_DIR/skills/dev/ancien/claude-code.md")"
|
||||||
|
|
||||||
|
# scan_skills avec les deux formats
|
||||||
|
REPO_DIR="$TMP_DIR"
|
||||||
|
DETECTED_AGENTS=()
|
||||||
|
SKILLS_TAG=""
|
||||||
|
STATE_FILE=$(mktemp)
|
||||||
|
scan_skills 2>/dev/null
|
||||||
|
|
||||||
|
# Compter les entrées trouvées
|
||||||
|
bundle_count=0; legacy_count=0; ref_count=0
|
||||||
|
for e in "${SKILLS_LIST[@]}"; do
|
||||||
|
IFS='|' read -r _ _ _ _ _ _ _ _ kind source_path <<< "$e"
|
||||||
|
[[ "$kind" == "bundle" ]] && (( bundle_count++ )) || true
|
||||||
|
[[ "$kind" == "legacy" ]] && (( legacy_count++ )) || true
|
||||||
|
# Vérifier qu'aucune entrée ne vient de references/notes.md
|
||||||
|
[[ "$source_path" == *"notes.md"* ]] && (( ref_count++ )) || true
|
||||||
|
done
|
||||||
|
|
||||||
|
assert_eq "bundle détecté 2 fois (2 agents)" "2" "$bundle_count"
|
||||||
|
assert_eq "legacy détecté 1 fois" "1" "$legacy_count"
|
||||||
|
assert_eq "fichier references/ ignoré" "0" "$ref_count"
|
||||||
|
|
||||||
|
# Vérifier que kind=bundle et source_path pointent sur le dossier
|
||||||
|
first_bundle=""
|
||||||
|
for e in "${SKILLS_LIST[@]}"; do
|
||||||
|
IFS='|' read -r _ _ _ _ _ _ _ _ kind source_path <<< "$e"
|
||||||
|
[[ "$kind" == "bundle" ]] && { first_bundle="$source_path"; break; }
|
||||||
|
done
|
||||||
|
assert_eq "source_path bundle = dossier du skill" \
|
||||||
|
"$TMP_DIR/skills/infra/mon-bundle" \
|
||||||
|
"$first_bundle"
|
||||||
|
|
||||||
|
# Vérifier que SKILL.md du bundle est lisible pour le preview
|
||||||
|
assert_true "SKILL.md bundle accessible" "[[ -f '${first_bundle}/SKILL.md' ]]"
|
||||||
|
assert_true "scripts/ du bundle accessible" "[[ -f '${first_bundle}/scripts/helper.sh' ]]"
|
||||||
|
|
||||||
|
# Priorité bundle sur legacy : si SKILL.md existe, le <agent>.md ne duplique pas
|
||||||
|
mkdir -p "$TMP_DIR/skills/dev/ancien"
|
||||||
|
cat > "$TMP_DIR/skills/dev/ancien/SKILL.md" << 'EOF'
|
||||||
|
---
|
||||||
|
name: ancien
|
||||||
|
version: 1.0.0
|
||||||
|
agents: [claude-code]
|
||||||
|
description: Skill migré en bundle
|
||||||
|
tags: [test]
|
||||||
|
---
|
||||||
|
# Ancien migré
|
||||||
|
EOF
|
||||||
|
SKILLS_LIST=()
|
||||||
|
scan_skills 2>/dev/null
|
||||||
|
count_ancien=0
|
||||||
|
for e in "${SKILLS_LIST[@]}"; do
|
||||||
|
[[ "$e" == *"|ancien|claude-code|"* ]] && (( count_ancien++ )) || true
|
||||||
|
done
|
||||||
|
assert_eq "pas de doublon bundle+legacy pour même agent" "1" "$count_ancien"
|
||||||
|
|
||||||
|
rm -f "$STATE_FILE"
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
unset REPO_DIR STATE_FILE SKILLS_TAG DETECTED_AGENTS
|
||||||
|
|
||||||
# ── Bilan ─────────────────────────────────────────────────────────
|
# ── Bilan ─────────────────────────────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "══════════════════════════════════"
|
echo "══════════════════════════════════"
|
||||||
|
|||||||
Reference in New Issue
Block a user