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>
This commit is contained in:
parent
de4b438a72
commit
c81180aec7
11 changed files with 980 additions and 52 deletions
196
scripts/detect-new-onboarding-topics.sh
Executable file
196
scripts/detect-new-onboarding-topics.sh
Executable file
|
|
@ -0,0 +1,196 @@
|
|||
#!/usr/bin/env bash
|
||||
# detect-new-onboarding-topics.sh
|
||||
# Detects CRITICAL/HIGH VALUE topics from resource evaluations not covered in onboarding_matrix
|
||||
# Part of adaptive onboarding maintenance automation (v2.0.0, guide v3.23.0+)
|
||||
# Usage: Run monthly or before releases to ensure new important topics are discoverable
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
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"
|
||||
EVALUATIONS_DIR="$REPO_ROOT/docs/resource-evaluations"
|
||||
|
||||
# Helper functions
|
||||
info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
gap() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
critical_gap() {
|
||||
echo -e "${RED}🚨 $1${NC}"
|
||||
}
|
||||
|
||||
# Main scan
|
||||
scan_evaluations() {
|
||||
info "Scanning resource evaluations for CRITICAL (5/5) and HIGH VALUE (4/5) topics..."
|
||||
echo ""
|
||||
|
||||
# Extract all keys referenced in onboarding_matrix
|
||||
local matrix_keys
|
||||
matrix_keys=$(python3 <<'EOF'
|
||||
import yaml
|
||||
import sys
|
||||
|
||||
with open(sys.argv[1]) as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
matrix = data.get('onboarding_matrix', {})
|
||||
|
||||
# Collect all keys
|
||||
referenced = set()
|
||||
for goal, levels in matrix.items():
|
||||
for level, profile in levels.items():
|
||||
if isinstance(profile, dict):
|
||||
if 'core' in profile:
|
||||
referenced.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.update(topics)
|
||||
else:
|
||||
referenced.add(topics)
|
||||
elif isinstance(profile, list):
|
||||
referenced.update(profile)
|
||||
|
||||
print('|'.join(sorted(referenced)))
|
||||
EOF
|
||||
"$REFERENCE_YAML")
|
||||
|
||||
# Check deep_dive keys
|
||||
local deep_dive_keys
|
||||
deep_dive_keys=$(python3 -c "import yaml; print('|'.join(yaml.safe_load(open('$REFERENCE_YAML')).get('deep_dive', {}).keys()))")
|
||||
|
||||
# Scan evaluation files
|
||||
local critical_topics=()
|
||||
local high_topics=()
|
||||
local gaps=()
|
||||
local critical_gaps=()
|
||||
|
||||
if [ ! -d "$EVALUATIONS_DIR" ]; then
|
||||
info "No evaluations directory found at $EVALUATIONS_DIR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
for eval_file in "$EVALUATIONS_DIR"/*.md; do
|
||||
if [ ! -f "$eval_file" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Extract score (look for "**Score**: X/5" or "Score: X/5")
|
||||
local score
|
||||
score=$(grep -oE '\*\*Score\*\*:?\s*[0-9]/5|Score:?\s*[0-9]/5' "$eval_file" | head -1 | grep -oE '[0-9]' || echo "0")
|
||||
|
||||
# Extract topic/key (look for deep_dive references in evaluation)
|
||||
local topic_key
|
||||
topic_key=$(basename "$eval_file" .md | sed 's/-evaluation$//' | sed 's/-/_/g')
|
||||
|
||||
# Check if it's CRITICAL or HIGH VALUE
|
||||
if [ "$score" -eq 5 ]; then
|
||||
critical_topics+=("$topic_key")
|
||||
# Check if in deep_dive
|
||||
if [[ "$deep_dive_keys" == *"$topic_key"* ]]; then
|
||||
# Check if in matrix
|
||||
if [[ "$matrix_keys" != *"$topic_key"* ]]; then
|
||||
critical_gaps+=("$topic_key (5/5 CRITICAL, file: $(basename "$eval_file"))")
|
||||
fi
|
||||
fi
|
||||
elif [ "$score" -eq 4 ]; then
|
||||
high_topics+=("$topic_key")
|
||||
# Check if in deep_dive
|
||||
if [[ "$deep_dive_keys" == *"$topic_key"* ]]; then
|
||||
# Check if in matrix
|
||||
if [[ "$matrix_keys" != *"$topic_key"* ]]; then
|
||||
gaps+=("$topic_key (4/5 HIGH VALUE, file: $(basename "$eval_file"))")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Report
|
||||
echo "📊 Summary:"
|
||||
echo " - CRITICAL (5/5) topics found: ${#critical_topics[@]}"
|
||||
echo " - HIGH VALUE (4/5) topics found: ${#high_topics[@]}"
|
||||
echo ""
|
||||
|
||||
if [ ${#critical_gaps[@]} -gt 0 ]; then
|
||||
critical_gap "Found ${#critical_gaps[@]} CRITICAL topics NOT in any onboarding profile:"
|
||||
for gap_item in "${critical_gaps[@]}"; do
|
||||
echo " - $gap_item"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ ${#gaps[@]} -gt 0 ]; then
|
||||
gap "Found ${#gaps[@]} HIGH VALUE topics NOT in any onboarding profile:"
|
||||
for gap_item in "${gaps[@]}"; do
|
||||
echo " - $gap_item"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ ${#critical_gaps[@]} -eq 0 ] && [ ${#gaps[@]} -eq 0 ]; then
|
||||
success "All CRITICAL and HIGH VALUE topics are covered in onboarding_matrix!"
|
||||
echo ""
|
||||
info "Covered topics:"
|
||||
for topic in "${critical_topics[@]}"; do
|
||||
if [[ "$matrix_keys" == *"$topic"* ]]; then
|
||||
echo " ✅ $topic (5/5 CRITICAL)"
|
||||
fi
|
||||
done
|
||||
for topic in "${high_topics[@]}"; do
|
||||
if [[ "$matrix_keys" == *"$topic"* ]]; then
|
||||
echo " ✅ $topic (4/5 HIGH VALUE)"
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
else
|
||||
echo "💡 Action required:"
|
||||
echo " Review the gaps above and consider adding them to relevant profiles in:"
|
||||
echo " $REFERENCE_YAML (onboarding_matrix section)"
|
||||
echo ""
|
||||
echo " See design doc: claudedocs/adaptive-onboarding-design.md"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Main
|
||||
main() {
|
||||
echo "════════════════════════════════════════════════════════════════"
|
||||
echo " Onboarding Topics Gap Detection (v2.0.0)"
|
||||
echo "════════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
scan_evaluations
|
||||
local exit_code=$?
|
||||
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════════════════════════"
|
||||
if [ $exit_code -eq 0 ]; then
|
||||
success "No gaps detected - onboarding is up-to-date!"
|
||||
else
|
||||
gap "Gaps detected - review and update onboarding_matrix"
|
||||
fi
|
||||
echo "════════════════════════════════════════════════════════════════"
|
||||
|
||||
exit $exit_code
|
||||
}
|
||||
|
||||
main "$@"
|
||||
361
scripts/validate-onboarding.sh
Executable file
361
scripts/validate-onboarding.sh
Executable file
|
|
@ -0,0 +1,361 @@
|
|||
#!/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 "$@"
|
||||
Loading…
Add table
Add a link
Reference in a new issue