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>
301 lines
9.3 KiB
Bash
Executable file
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 "$@"
|