claude-code-ultimate-guide/scripts/validate-onboarding.sh
Florian BRUNIAUX c81180aec7 feat: adaptive onboarding architecture v2.0.0 (v3.23.0)
Major overhaul of onboarding system with adaptive topic selection based on
user context and keywords. Addresses 8 critical gaps identified by technical-
writer agent challenge.

Core Changes:
- Adaptive matrix: core topics (always) + adaptive topics (keyword-triggered)
- Security-first: moved sandbox_native_guide to beginner_5min (before commands)
- Time budget validation: all 18 profiles validated at 6-8 min/topic
- Quiz integration: positioned as exit activity in Phase 4 wrap-up
- New learn_security goal with 2 profiles (beginner_15min, advanced_60min)

Technical Improvements:
- Added onboarding_matrix_meta for version tracking and maintenance triggers
- Created validation script (validate-onboarding.sh) with 6 automated checks
- Created automation script (detect-new-onboarding-topics.sh) for monthly reviews
- Fixed 8 missing deep_dive keys (rules, workflow, fix, architecture, etc.)
- Removed duplicate deep_dive section causing validation failures

Documentation:
- README.md: version 3.23.0, harmonized counts (106 templates, 49 evaluations)
- CHANGELOG.md: comprehensive v3.23.0 entry with all changes
- Onboarding-prompt.md: updated Phase 1.5, 2, 4 with adaptive logic
- Reference.yaml: 180+ lines added for adaptive architecture

Validation:
- All 18 profiles pass time budget constraints (30-50% buffer maintained)
- All deep_dive keys verified (no missing references)
- Version synchronized across 6 files via sync-version.sh

Challenge: technical-writer agent identified 8 gaps in initial analysis
Result: Full adaptive approach implemented, all gaps addressed

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-05 22:19:58 +01:00

361 lines
10 KiB
Bash
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# validate-onboarding.sh
# Validates onboarding system integrity: YAML syntax, deep_dive keys, time budgets, etc.
# Part of adaptive onboarding architecture v2.0.0 (guide v3.23.0+)
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Paths
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
REFERENCE_YAML="$REPO_ROOT/machine-readable/reference.yaml"
VERSION_FILE="$REPO_ROOT/VERSION"
# Counters
CHECKS_TOTAL=0
CHECKS_PASSED=0
CHECKS_FAILED=0
# Helper functions
pass() {
((CHECKS_PASSED++))
((CHECKS_TOTAL++))
echo -e "${GREEN}$1${NC}"
}
fail() {
((CHECKS_FAILED++))
((CHECKS_TOTAL++))
echo -e "${RED}$1${NC}"
}
warn() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
info() {
echo -e "${NC} $1${NC}"
}
# Check 1: YAML syntax valid
check_yaml_syntax() {
info "Check 1: YAML syntax validation"
if python3 -c "import yaml; yaml.safe_load(open('$REFERENCE_YAML'))" 2>/dev/null; then
pass "YAML syntax valid"
else
fail "YAML syntax invalid - cannot parse reference.yaml"
return 1
fi
}
# Check 2: All deep_dive keys in onboarding_matrix exist
check_deep_dive_keys() {
info "Check 2: Verifying all deep_dive keys exist"
# Extract all keys referenced in onboarding_matrix (both core and adaptive)
local matrix_keys
export REFERENCE_YAML
matrix_keys=$(python3 <<'EOF'
import yaml
import sys
import os
with open(os.environ['REFERENCE_YAML']) as f:
data = yaml.safe_load(f)
matrix = data.get('onboarding_matrix', {})
deep_dive = data.get('deep_dive', {})
# Collect all keys from core and adaptive
referenced_keys = set()
for goal, levels in matrix.items():
for level, profile in levels.items():
# Handle both old format (list) and new format (dict with core/adaptive)
if isinstance(profile, dict):
# New adaptive format
if 'core' in profile:
referenced_keys.update(profile['core'])
if 'adaptive' in profile:
for item in profile['adaptive']:
if isinstance(item, dict) and 'topics' in item:
topics = item['topics']
if isinstance(topics, list):
referenced_keys.update(topics)
else:
referenced_keys.add(topics)
elif item != 'default': # Skip 'default' keyword
continue
elif isinstance(profile, list):
# Old static format
referenced_keys.update(profile)
# Check all keys exist in deep_dive
missing = []
for key in sorted(referenced_keys):
if key not in deep_dive and '.' not in key: # Allow context.zones style keys
missing.append(key)
if missing:
print(f"MISSING: {', '.join(missing)}")
sys.exit(1)
else:
print(f"OK: All {len(referenced_keys)} keys exist")
sys.exit(0)
EOF
"$REFERENCE_YAML")
local exit_code=$?
if [ $exit_code -eq 0 ]; then
pass "Deep_dive keys: $matrix_keys"
else
fail "Deep_dive keys: $matrix_keys"
return 1
fi
}
# Check 3: Time budgets respected
check_time_budgets() {
info "Check 3: Validating time budgets"
local validation_result
validation_result=$(python3 <<'EOF'
import yaml
import sys
import os
with open(os.environ['REFERENCE_YAML']) as f:
data = yaml.safe_load(f)
matrix = data.get('onboarding_matrix', {})
# Time budget rules (min per topic)
MIN_PER_TOPIC = {
'5min': 2,
'15min': 4,
'30min': 6,
'60min': 8,
'120min': 10
}
violations = []
checks = 0
for goal, levels in matrix.items():
for level, profile in levels.items():
if not isinstance(profile, dict):
continue # Skip old format or fix_problem.any_any
time_budget = profile.get('time_budget', 'N/A')
topics_max = profile.get('topics_max', 0)
if time_budget == 'N/A':
continue
# Extract numeric time
time_key = time_budget.split()[0] # "30 min" -> "30"
# Calculate required time
if time_key in MIN_PER_TOPIC:
min_per_topic = MIN_PER_TOPIC[time_key]
required_time = topics_max * min_per_topic
actual_time = int(time_key.replace('min', ''))
checks += 1
if required_time > actual_time * 1.1: # Allow 10% tolerance
violations.append(
f"{goal}.{level}: {topics_max} topics × {min_per_topic} min = {required_time} min > {actual_time} min budget"
)
if violations:
print(f"VIOLATIONS ({len(violations)}):")
for v in violations:
print(f" - {v}")
sys.exit(1)
else:
print(f"OK: {checks} profiles checked, all time budgets respected")
sys.exit(0)
EOF
"$REFERENCE_YAML")
local exit_code=$?
if [ $exit_code -eq 0 ]; then
pass "Time budgets: $validation_result"
else
fail "Time budgets: $validation_result"
return 1
fi
}
# Check 4: topics_max constraints not violated
check_topics_max() {
info "Check 4: Verifying topics_max constraints"
local result
result=$(python3 <<'EOF'
import yaml
import sys
import os
with open(os.environ['REFERENCE_YAML']) as f:
data = yaml.safe_load(f)
matrix = data.get('onboarding_matrix', {})
violations = []
checks = 0
for goal, levels in matrix.items():
for level, profile in levels.items():
if not isinstance(profile, dict):
continue
topics_max = profile.get('topics_max', 0)
# Count core topics
core_count = len(profile.get('core', []))
# Determine max adaptive topics based on adaptive section existence and note
adaptive_section = profile.get('adaptive', [])
if not adaptive_section:
# No adaptive section = no adaptive topics
max_adaptive = 0
else:
# Has adaptive section - check note for "picks up to 2"
note = profile.get('note', '')
if 'picks up to 2' in note or 'up to 2' in note:
max_adaptive = 2
else:
max_adaptive = 1 # Either 1 trigger OR default
# Max possible topics: core + max_adaptive
max_possible = core_count + max_adaptive
checks += 1
if max_possible > topics_max:
violations.append(
f"{goal}.{level}: max_possible={max_possible} (core={core_count} + adaptive/default) > topics_max={topics_max}"
)
if violations:
print(f"VIOLATIONS ({len(violations)}):")
for v in violations:
print(f" - {v}")
sys.exit(1)
else:
print(f"OK: {checks} profiles checked")
sys.exit(0)
EOF
"$REFERENCE_YAML")
local exit_code=$?
if [ $exit_code -eq 0 ]; then
pass "Topics_max constraints: $result"
else
fail "Topics_max: $result"
return 1
fi
}
# Check 5: question_flow references all goals
check_question_flow() {
info "Check 5: Verifying question_flow completeness"
local result
result=$(python3 <<'EOF'
import yaml
import sys
import os
with open(os.environ['REFERENCE_YAML']) as f:
data = yaml.safe_load(f)
matrix = data.get('onboarding_matrix', {})
questions = data.get('onboarding_questions', {})
question_flow = questions.get('question_flow', {})
matrix_goals = set(matrix.keys())
flow_goals = set(question_flow.keys())
missing = matrix_goals - flow_goals
extra = flow_goals - matrix_goals
if missing or extra:
if missing:
print(f"MISSING in question_flow: {', '.join(sorted(missing))}")
if extra:
print(f"EXTRA in question_flow: {', '.join(sorted(extra))}")
sys.exit(1)
else:
print(f"OK: All {len(matrix_goals)} goals present")
sys.exit(0)
EOF
"$REFERENCE_YAML")
local exit_code=$?
if [ $exit_code -eq 0 ]; then
pass "Question_flow: $result"
else
fail "Question_flow: $result"
return 1
fi
}
# Check 6: Version alignment
check_version_alignment() {
info "Check 6: Verifying version alignment"
local guide_version
guide_version=$(cat "$VERSION_FILE")
local yaml_version meta_aligned
yaml_version=$(python3 -c "import yaml; print(yaml.safe_load(open('$REFERENCE_YAML'))['version'])")
meta_aligned=$(python3 -c "import yaml; data=yaml.safe_load(open('$REFERENCE_YAML')); print(data.get('onboarding_matrix_meta', {}).get('aligned_with_guide', 'N/A'))")
if [ "$guide_version" = "$yaml_version" ] && [ "$guide_version" = "$meta_aligned" ]; then
pass "Version aligned: $guide_version (VERSION = reference.yaml = onboarding_matrix_meta)"
else
fail "Version mismatch: VERSION=$guide_version, reference.yaml=$yaml_version, meta=$meta_aligned"
return 1
fi
}
# Main execution
main() {
echo "════════════════════════════════════════════════════════════════"
echo " Onboarding System Validation (v2.0.0)"
echo "════════════════════════════════════════════════════════════════"
echo ""
check_yaml_syntax || true
check_deep_dive_keys || true
check_time_budgets || true
check_topics_max || true
check_question_flow || true
check_version_alignment || true
echo ""
echo "════════════════════════════════════════════════════════════════"
echo " Summary"
echo "════════════════════════════════════════════════════════════════"
echo -e "Total checks: $CHECKS_TOTAL"
echo -e "${GREEN}Passed: $CHECKS_PASSED${NC}"
if [ $CHECKS_FAILED -gt 0 ]; then
echo -e "${RED}Failed: $CHECKS_FAILED${NC}"
echo ""
echo -e "${RED}❌ Validation FAILED${NC}"
exit 1
else
echo -e "${GREEN}Failed: 0${NC}"
echo ""
echo -e "${GREEN}✅ All checks passed!${NC}"
exit 0
fi
}
main "$@"