ms-ai-architect/scripts/skill-gen/expand-categories.sh
Kjell Tore Guttormsen baa2d0220b feat(ultraplan-local): v1.6.0 — /ultraresearch-local deep research command
Add /ultraresearch-local for structured research combining local codebase
analysis with external knowledge via parallel agent swarms. Produces research
briefs with triangulation, confidence ratings, and source quality assessment.

New command: /ultraresearch-local with modes --quick, --local, --external, --fg.
New agents: research-orchestrator (opus), docs-researcher, community-researcher,
security-researcher, contrarian-researcher, gemini-bridge (all sonnet).
New template: research-brief-template.md.

Integration: --research flag in /ultraplan-local accepts pre-built research
briefs (up to 3), enriches the interview and exploration phases. Planning
orchestrator cross-references brief findings during synthesis.

Design principle: Context Engineering — right information to right agent at
right time. Research briefs are structured artifacts in the pipeline:
ultraresearch → brief → ultraplan --research → plan → ultraexecute.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 08:58:35 +02:00

301 lines
9.3 KiB
Bash
Executable file

#!/bin/bash
# expand-categories.sh — Expand skill categories into full manifest
#
# Uses claude --print to expand each category in categories.json
# into 15-25 individual skill titles, producing manifest.json
#
# Usage:
# ./expand-categories.sh # Expand all categories
# ./expand-categories.sh azure-ai-services # Expand single category
# ./expand-categories.sh --wave 1 # Expand wave 1 only
#
# Prerequisites:
# - claude CLI installed and authenticated
# - jq installed
# - categories.json in same directory
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
CATEGORIES_FILE="$SCRIPT_DIR/categories.json"
MANIFEST_FILE="$SCRIPT_DIR/manifest.json"
LOG_DIR="$SCRIPT_DIR/logs"
# Model for expansion (haiku is sufficient for generating titles)
MODEL="${MODEL:-haiku}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() { echo -e "${BLUE}[expand]${NC} $1" >&2; }
success() { echo -e "${GREEN}[expand]${NC} $1" >&2; }
warn() { echo -e "${YELLOW}[expand]${NC} $1" >&2; }
error() { echo -e "${RED}[expand]${NC} $1" >&2; }
# Check prerequisites
check_prereqs() {
if ! command -v claude &>/dev/null; then
error "claude CLI not found. Install: npm install -g @anthropic-ai/claude-code"
exit 1
fi
if ! command -v jq &>/dev/null; then
error "jq not found. Install: brew install jq"
exit 1
fi
if [[ ! -f "$CATEGORIES_FILE" ]]; then
error "categories.json not found at $CATEGORIES_FILE"
exit 1
fi
}
# Get list of existing reference files for context
get_existing_refs() {
local category_dir="$1"
local refs_dir="$PLUGIN_DIR/skills/ms-ai-engineering/references"
# List all existing reference files
find "$refs_dir" -name "*.md" -type f 2>/dev/null | while read -r f; do
basename "$f" .md
done | sort | tr '\n' ', '
}
# Expand a single category
expand_category() {
local category_key="$1"
local name description estimated examples existing_overlap notes
name=$(jq -r ".categories[\"$category_key\"].name" "$CATEGORIES_FILE")
description=$(jq -r ".categories[\"$category_key\"].description" "$CATEGORIES_FILE")
estimated=$(jq -r ".categories[\"$category_key\"].estimated_skills" "$CATEGORIES_FILE")
examples=$(jq -r ".categories[\"$category_key\"].examples | join(\", \")" "$CATEGORIES_FILE")
existing_overlap=$(jq -r ".categories[\"$category_key\"].existing_overlap | join(\", \")" "$CATEGORIES_FILE")
notes=$(jq -r ".categories[\"$category_key\"].notes" "$CATEGORIES_FILE")
local existing_refs
existing_refs=$(get_existing_refs "$category_key")
log "Expanding: $name ($estimated skills estimated)"
local prompt
prompt="Du er en Microsoft AI Solution Architect som planlegger en kunnskapsbase.
Kategorien **${name}** trenger individuelle kunnskapsfiler (skills).
## Kategori-beskrivelse
${description}
## Eksisterende filer i kunnskapsbasen (unngå duplikering)
${existing_refs}
## Eksisterende overlapp å ta hensyn til
${existing_overlap}
## Eksempel-titler (for inspirasjon, ikke begrens deg til disse)
${examples}
## Notater
${notes}
## Oppgave
Generer en JSON-array med NØYAKTIG ${estimated} skills for denne kategorien.
Hver skill skal ha:
- \`id\`: kebab-case filnavn (uten .md)
- \`title\`: Engelsk tittel (kortfattet, beskrivende)
- \`description\`: 1-2 setninger på norsk om hva filen dekker
- \`subtopics\`: 3-5 viktige undertemaer som array
Regler:
1. Ikke dupliser emner som allerede finnes i eksisterende filer
2. Sørg for bred dekning uten overlapp mellom skills
3. Titler skal være spesifikke (\"Azure AI Vision OCR and Document Processing\", ikke bare \"Vision\")
4. Prioriter mest nyttige emner for enterprise AI-arkitekter i norsk offentlig sektor
5. Returner KUN valid JSON-array, ingen annen tekst
Eksempel-format:
[
{
\"id\": \"example-skill-name\",
\"title\": \"Example Skill - Full Descriptive Title\",
\"description\": \"Beskrivelse av hva denne kunnskapsfilen dekker.\",
\"subtopics\": [\"subtopic-1\", \"subtopic-2\", \"subtopic-3\"]
}
]"
local output
output=$(claude --print --model "$MODEL" "$prompt" 2>"$LOG_DIR/expand-${category_key}.err")
# Extract JSON array from response (handles markdown code blocks, plain JSON, etc.)
local json_output
json_output=$(python3 -c "
import sys, json, re
text = sys.stdin.read()
# Try to find JSON array in code blocks first
m = re.search(r'\`\`\`(?:json)?\s*(\[[\s\S]*?\])\s*\`\`\`', text)
if m:
arr = json.loads(m.group(1))
print(json.dumps(arr))
sys.exit(0)
# Try to find bare JSON array
m = re.search(r'(\[[\s\S]*\])', text)
if m:
try:
arr = json.loads(m.group(1))
print(json.dumps(arr))
sys.exit(0)
except: pass
# Nothing found
sys.exit(1)
" <<< "$output" 2>/dev/null)
# Validate JSON
if ! echo "$json_output" | jq . &>/dev/null; then
error "Invalid JSON for $category_key. Raw output saved to $LOG_DIR/expand-${category_key}.raw"
echo "$output" > "$LOG_DIR/expand-${category_key}.raw"
return 1
fi
local count
count=$(echo "$json_output" | jq 'length')
success "$name: $count skills generated"
# Return the JSON
echo "$json_output"
}
# Build or update manifest
build_manifest() {
local categories_to_expand=("$@")
# Initialize manifest if it doesn't exist
if [[ ! -f "$MANIFEST_FILE" ]]; then
echo '{"version":"1.0","created":"'"$(date +%Y-%m-%d)"'","categories":{}}' | jq . > "$MANIFEST_FILE"
fi
local total=0
local failed=0
for category_key in "${categories_to_expand[@]}"; do
local skills_json
if skills_json=$(expand_category "$category_key"); then
# Add to manifest
local dir
dir=$(jq -r ".categories[\"$category_key\"].dir" "$CATEGORIES_FILE")
local name
name=$(jq -r ".categories[\"$category_key\"].name" "$CATEGORIES_FILE")
local priority
priority=$(jq -r ".categories[\"$category_key\"].priority" "$CATEGORIES_FILE")
local category_obj
category_obj=$(jq -n \
--arg name "$name" \
--arg dir "$dir" \
--arg priority "$priority" \
--argjson skills "$skills_json" \
'{name: $name, dir: $dir, priority: $priority, skills: $skills}')
# Merge into manifest
jq --arg key "$category_key" --argjson cat "$category_obj" \
'.categories[$key] = $cat' "$MANIFEST_FILE" > "$MANIFEST_FILE.tmp" \
&& mv "$MANIFEST_FILE.tmp" "$MANIFEST_FILE"
local count
count=$(echo "$skills_json" | jq 'length')
total=$((total + count))
else
failed=$((failed + 1))
fi
# Rate limiting: pause between API calls
sleep 2
done
echo "" >&2
log "═══════════════════════════════════════"
success "Total skills in manifest: $total"
[[ $failed -gt 0 ]] && error "Failed categories: $failed"
log "Manifest: $MANIFEST_FILE"
log "═══════════════════════════════════════"
}
# Parse arguments
parse_args() {
local wave=""
local single_category=""
while [[ $# -gt 0 ]]; do
case "$1" in
--wave)
wave="$2"
shift 2
;;
--model)
MODEL="$2"
shift 2
;;
--help|-h)
echo "Usage: $0 [category-key] [--wave N] [--model MODEL]"
echo ""
echo "Options:"
echo " category-key Expand single category"
echo " --wave N Expand all categories in wave N (1 or 2)"
echo " --model MODEL Claude model to use (default: haiku)"
echo ""
echo "Examples:"
echo " $0 # Expand all categories"
echo " $0 azure-ai-services # Expand single category"
echo " $0 --wave 1 # Expand HIGH priority only"
exit 0
;;
*)
single_category="$1"
shift
;;
esac
done
# Determine which categories to expand
if [[ -n "$single_category" ]]; then
echo "$single_category"
elif [[ -n "$wave" ]]; then
jq -r ".waves[] | select(.wave == $wave) | .categories[]" "$CATEGORIES_FILE"
else
jq -r '.categories | keys[]' "$CATEGORIES_FILE"
fi
}
# Main
main() {
check_prereqs
mkdir -p "$LOG_DIR"
log "Skill Category Expansion"
log "Model: $MODEL | Categories file: $CATEGORIES_FILE"
echo "" >&2
local categories=()
while IFS= read -r line; do
categories+=("$line")
done < <(parse_args "$@")
if [[ ${#categories[@]} -eq 0 ]]; then
error "No categories to expand"
exit 1
fi
log "Categories to expand: ${#categories[@]}"
for cat in "${categories[@]}"; do
log " - $cat"
done
echo "" >&2
build_manifest "${categories[@]}"
}
main "$@"