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:
+95
-40
@@ -234,6 +234,12 @@ get_frontmatter_tags() {
|
||||
| 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
|
||||
version_is_newer() {
|
||||
local ver1="$1" ver2="$2"
|
||||
@@ -264,13 +270,65 @@ get_local_version() {
|
||||
}
|
||||
|
||||
# ── 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=()
|
||||
|
||||
_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() {
|
||||
header "Scan des skills disponibles"
|
||||
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
|
||||
local rel="${skill_file#${REPO_DIR}/skills/}"
|
||||
local agent_file="${rel##*/}"
|
||||
@@ -279,37 +337,21 @@ scan_skills() {
|
||||
local cat="${skill_path%%/*}"
|
||||
local skill="${skill_path#*/}"
|
||||
|
||||
# Filtre agent
|
||||
local agent_detected=0
|
||||
if [[ ${#DETECTED_AGENTS[@]} -gt 0 ]]; then
|
||||
for a in "${DETECTED_AGENTS[@]}"; do
|
||||
[[ "$a" == "$agent" ]] && agent_detected=1
|
||||
done
|
||||
fi
|
||||
[[ "$agent_detected" -eq 0 && ${#DETECTED_AGENTS[@]} -gt 0 ]] && continue
|
||||
# Ignorer les fichiers qui ne correspondent pas à un agent connu
|
||||
local is_known=0
|
||||
for ka in $known_agents; do [[ "$ka" == "$agent" ]] && is_known=1; done
|
||||
[[ "$is_known" -eq 0 ]] && continue
|
||||
|
||||
# Filtre tag
|
||||
if [[ -n "$SKILLS_TAG" ]]; then
|
||||
grep -q "$SKILLS_TAG" "$skill_file" || continue
|
||||
fi
|
||||
# Le bundle a priorité : ignorer si déjà vu
|
||||
[[ -n "${seen_combos[${cat}|${skill}|${agent}]:-}" ]] && continue
|
||||
|
||||
local repo_ver; repo_ver=$(get_frontmatter_field "$skill_file" "version")
|
||||
local local_ver; local_ver=$(get_local_version "$cat" "$skill" "$agent")
|
||||
local etat
|
||||
_skill_agent_ok "$agent" || continue
|
||||
[[ -n "$SKILLS_TAG" ]] && { grep -q "$SKILLS_TAG" "$skill_file" || continue; }
|
||||
|
||||
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")
|
||||
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)
|
||||
local entry; entry=$(_build_entry "$cat" "$skill" "$agent" "legacy" "$skill_file" "$skill_file")
|
||||
SKILLS_LIST+=("$entry")
|
||||
debug "Legacy : $cat/$skill [$agent]"
|
||||
done < <(find "${REPO_DIR}/skills" -name "*.md" ! -name "SKILL.md" | sort)
|
||||
|
||||
ok "${#SKILLS_LIST[@]} skill(s) trouvé(s)"
|
||||
}
|
||||
@@ -588,8 +630,12 @@ td="$1"; type="${td%%:*}"; value="${td#*:}"
|
||||
if [[ "$type" == "s" ]]; then
|
||||
entry="${SKILLS_LIST[$value]:-}"
|
||||
[[ -z "$entry" ]] && exit 0
|
||||
IFS='|' read -r cat skill agent _ <<< "$entry"
|
||||
skill_file="${REPO_DIR}/skills/${cat}/${skill}/${agent}.md"
|
||||
IFS='|' read -r cat skill agent _ _ _ _ _ kind source_path <<< "$entry"
|
||||
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 command -v bat &>/dev/null; then
|
||||
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
|
||||
|
||||
for entry in "${SKILLS_LIST[@]}"; do
|
||||
local cat skill agent etat repo_ver local_ver
|
||||
IFS='|' read -r cat skill agent etat repo_ver local_ver <<< "$entry"
|
||||
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 desc tags kind source_path <<< "$entry"
|
||||
local key; key=$(make_key "$entry")
|
||||
local action; action=$(state_get "$key")
|
||||
|
||||
@@ -746,16 +792,25 @@ install_selected() {
|
||||
local scope="$action"
|
||||
[[ "$action" == "update" ]] && scope="local"
|
||||
|
||||
local src="${REPO_DIR}/skills/${cat}/${skill}/${agent}.md"
|
||||
local dest; dest=$(get_dest_path "$cat" "$skill" "$agent" "$scope")
|
||||
|
||||
debug "Copie $src → $dest"
|
||||
|
||||
if [[ "$SKILLS_DRY_RUN" == "1" ]]; then
|
||||
info "[DRY-RUN] cp $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
|
||||
info "[DRY-RUN] cp -r $source_path/ → $dest_dir/"
|
||||
else
|
||||
mkdir -p "$dest_dir"
|
||||
cp -r "${source_path}/." "$dest_dir/"
|
||||
fi
|
||||
else
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
cp "$src" "$dest"
|
||||
debug "Legacy $source_path → $dest"
|
||||
if [[ "$SKILLS_DRY_RUN" == "1" ]]; then
|
||||
info "[DRY-RUN] cp $source_path → $dest"
|
||||
else
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
cp "$source_path" "$dest"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$action" == "update" ]]; then
|
||||
|
||||
@@ -161,6 +161,111 @@ assert_eq "cycle update→local" "local" "$(state_get "dev_debugging_claude_code
|
||||
rm -f "$STATE_FILE"
|
||||
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 ─────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "══════════════════════════════════"
|
||||
|
||||
Reference in New Issue
Block a user