scripts-onboarding-progress-manager
#!/usr/bin/env python3 """β
title: "Get script directory for path resolution (works from any cwd)" component_type: script version: "1.0.0" audience: contributor status: stable summary: "CODITECT Onboarding Progress Manager" keywords: ['automation', 'manager', 'onboarding', 'progress'] tokens: ~500 created: 2025-12-22 updated: 2025-12-22 script_name: "onboarding-progress-manager.py" language: python executable: true usage: "python3 scripts/onboarding-progress-manager.py [options]" python_version: "3.10+" dependencies: [] modifies_files: false network_access: false requires_auth: falseβ
CODITECT Onboarding Progress Manager
Manages progress persistence for the expanded onboarding system. Supports exit/resume, progress tracking, and badge achievements.
Usage: python3 scripts/onboarding-progress-manager.py --status python3 scripts/onboarding-progress-manager.py --init python3 scripts/onboarding-progress-manager.py --complete-phase 3 python3 scripts/onboarding-progress-manager.py --reset python3 scripts/onboarding-progress-manager.py --award-badge "commands_master" """
import argparse import json import os import sys import subprocess from datetime import datetime, timezone from pathlib import Path from typing import Optional
Get script directory for path resolution (works from any cwd)
SCRIPT_DIR = Path(file).resolve().parent CORE_ROOT = SCRIPT_DIR.parent
Progress file location - relative to coditect-core root
PROGRESS_FILE = CORE_ROOT / ".coditect" / "onboarding-full-progress.json" TELEMETRY_SCRIPT = CORE_ROOT / "scripts" / "onboarding-telemetry.py"
def record_telemetry(event_type: str, data: Optional[dict] = None): """Record telemetry event if script available.""" if TELEMETRY_SCRIPT.exists(): try: cmd = ["python3", str(TELEMETRY_SCRIPT), "--record-event", event_type] if data: cmd.extend(["--data", json.dumps(data)]) subprocess.run(cmd, capture_output=True, timeout=5) except Exception: pass # Telemetry is optional, don't fail on errors
Phase definitions
PHASES = { 0: {"name": "welcome", "title": "Welcome & Assessment", "duration_min": 3}, 1: {"name": "big_picture", "title": "The Big Picture", "duration_min": 2}, 2: {"name": "commands", "title": "Commands", "duration_min": 3, "badge": "command_explorer"}, 3: {"name": "agents", "title": "Agents", "duration_min": 5, "badge": "agent_invoker"}, 4: {"name": "skills", "title": "Skills", "duration_min": 3, "badge": "skill_spotter"}, 5: {"name": "scripts", "title": "Scripts", "duration_min": 2, "badge": "script_runner"}, 6: {"name": "tools", "title": "Tools", "duration_min": 2, "badge": "tool_aware"}, 7: {"name": "workflows", "title": "Workflows", "duration_min": 3, "badge": "workflow_designer"}, 8: {"name": "cookbook", "title": "Cookbook", "duration_min": 3, "badge": "recipe_finder"}, 9: {"name": "integration", "title": "Integration", "duration_min": 2}, 10: {"name": "final_check", "title": "Final Check", "duration_min": 2, "badge": "onboarding_complete"}, }
Badge definitions
BADGES = { "command_explorer": {"icon": "π»", "title": "Command Explorer", "description": "Mastered slash commands"}, "agent_invoker": {"icon": "π€", "title": "Agent Invoker", "description": "Successfully invoked agents with Task()"}, "skill_spotter": {"icon": "β‘", "title": "Skill Spotter", "description": "Understands background automation"}, "script_runner": {"icon": "π", "title": "Script Runner", "description": "Ran Python scripts successfully"}, "tool_aware": {"icon": "π§", "title": "Tool Aware", "description": "Understands Claude's capabilities"}, "workflow_designer": {"icon": "π", "title": "Workflow Designer", "description": "Can design multi-component workflows"}, "recipe_finder": {"icon": "π", "title": "Recipe Finder", "description": "Knows how to use the Cookbook"}, "onboarding_complete": {"icon": "π", "title": "CODITECT Graduate", "description": "Completed full onboarding"}, "speed_learner": {"icon": "β‘", "title": "Speed Learner", "description": "Completed onboarding in under 20 min"}, "perfect_score": {"icon": "π―", "title": "Perfect Score", "description": "Got 7/7 on final quiz"}, }
def get_timestamp() -> str: """Get current ISO timestamp.""" return datetime.now(timezone.utc).isoformat()
def create_empty_progress() -> dict: """Create a new empty progress structure.""" return { "version": "1.0.0", "started_at": get_timestamp(), "last_activity": get_timestamp(), "profile": { "experience_level": None, "ai_familiarity": None, "learning_goal": None, "learning_style": None }, "phases": { f"{i}_{PHASES[i]['name']}": { "status": "pending", "score": None, "completed_at": None, "time_spent_seconds": 0 } for i in range(11) }, "current_phase": 0, "badges_earned": [], "exercises_completed": [], "quiz_scores": {}, "total_time_seconds": 0, "exit_points": [], "completed_at": None }
def load_progress() -> Optional[dict]: """Load existing progress from file.""" if PROGRESS_FILE.exists(): try: with open(PROGRESS_FILE, 'r') as f: return json.load(f) except json.JSONDecodeError: print("Warning: Progress file corrupted, starting fresh") return None return None
def save_progress(progress: dict) -> None: """Save progress to file.""" PROGRESS_FILE.parent.mkdir(parents=True, exist_ok=True) progress["last_activity"] = get_timestamp() with open(PROGRESS_FILE, 'w') as f: json.dump(progress, f, indent=2)
def init_progress(profile: Optional[dict] = None) -> dict: """Initialize new progress, optionally with profile.""" progress = create_empty_progress() if profile: progress["profile"].update(profile) save_progress(progress) record_telemetry("session_start", {"profile": profile}) return progress
def get_phase_key(phase_num: int) -> str: """Get the phase key for a phase number.""" return f"{phase_num}_{PHASES[phase_num]['name']}"
def complete_phase(progress: dict, phase_num: int, score: Optional[int] = None) -> dict: """Mark a phase as completed.""" phase_key = get_phase_key(phase_num) progress["phases"][phase_key]["status"] = "completed" progress["phases"][phase_key]["completed_at"] = get_timestamp() if score is not None: progress["phases"][phase_key]["score"] = score
# Record telemetry
record_telemetry("phase_complete", {"phase": phase_num, "score": score})
# Award badge if phase has one
if "badge" in PHASES[phase_num]:
badge_id = PHASES[phase_num]["badge"]
if badge_id not in progress["badges_earned"]:
progress["badges_earned"].append(badge_id)
print(f"π Badge earned: {BADGES[badge_id]['icon']} {BADGES[badge_id]['title']}")
# Update current phase
if phase_num < 10:
progress["current_phase"] = phase_num + 1
next_key = get_phase_key(phase_num + 1)
progress["phases"][next_key]["status"] = "in_progress"
else:
progress["completed_at"] = get_timestamp()
save_progress(progress)
return progress
def record_exit(progress: dict, reason: str = "user_requested") -> dict: """Record an exit point for analytics and resume.""" progress["exit_points"].append({ "phase": progress["current_phase"], "reason": reason, "at": get_timestamp() }) save_progress(progress) record_telemetry("session_exit", {"phase": progress["current_phase"], "reason": reason}) return progress
def award_badge(progress: dict, badge_id: str) -> dict: """Award a badge to the user.""" if badge_id in BADGES and badge_id not in progress["badges_earned"]: progress["badges_earned"].append(badge_id) badge = BADGES[badge_id] print(f"π Badge earned: {badge['icon']} {badge['title']} - {badge['description']}") save_progress(progress) record_telemetry("badge_earned", {"badge": badge_id}) return progress
def get_status_display(progress: dict) -> str: """Generate a status display string.""" completed = sum(1 for p in progress["phases"].values() if p["status"] == "completed") total = len(progress["phases"]) percent = int((completed / total) * 100)
# Progress bar
bar_width = 20
filled = int(bar_width * completed / total)
bar = "β" * filled + "β" * (bar_width - filled)
# Phase status
phase_lines = []
for i in range(11):
key = get_phase_key(i)
status = progress["phases"][key]["status"]
icon = "β
" if status == "completed" else "π" if status == "in_progress" else "βΈοΈ"
phase_lines.append(f" {icon} Phase {i}: {PHASES[i]['title']}")
# Badges
badge_icons = " ".join(BADGES[b]["icon"] for b in progress["badges_earned"]) or "None yet"
return f"""
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β CODITECT ONBOARDING PROGRESS β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£ β β β Progress: [{bar}] {percent}% β β Completed: {completed} of {total} phases β β Current Phase: {progress['current_phase']} - {PHASES[progress['current_phase']]['title']:<20} β β β β Started: {progress['started_at'][:10]} β β Last Activity: {progress['last_activity'][:10]} β β β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£ β PHASES β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£ {chr(10).join(f"β{line:<60}β" for line in phase_lines)} β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£ β BADGES EARNED β β {badge_icons:<58}β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ """
def get_resume_prompt(progress: dict) -> str: """Generate the resume prompt.""" current = progress["current_phase"] return f""" Welcome back! π
Last session: {progress['last_activity'][:10]} Progress: {sum(1 for p in progress['phases'].values() if p['status'] == 'completed')} of 11 phases completed Current phase: Phase {current} - {PHASES[current]['title']}
Options: β’ Type 'continue' to pick up where you left off β’ Type 'recap' to get a summary of what you've learned β’ Type 'restart' to start from the beginning β’ Type 'skip' to skip to the next phase
What would you like to do? """
def get_exit_message(progress: dict) -> str: """Generate the exit message.""" completed = sum(1 for p in progress['phases'].values() if p['status'] == 'completed') current = progress["current_phase"]
return f"""
Saving your progress... β
Progress saved successfully!
You've completed: {completed} of 11 phases Current phase: Phase {current} - {PHASES[current]['title']}
To resume: /onboard-full --resume To status: /onboard-full --status
See you next time! π """
def main(): parser = argparse.ArgumentParser(description="CODITECT Onboarding Progress Manager") parser.add_argument("--status", action="store_true", help="Show current progress") parser.add_argument("--init", action="store_true", help="Initialize new progress") parser.add_argument("--reset", action="store_true", help="Reset all progress") parser.add_argument("--complete-phase", type=int, help="Mark a phase as completed") parser.add_argument("--score", type=int, help="Score for the phase (use with --complete-phase)") parser.add_argument("--exit", action="store_true", help="Record exit and show message") parser.add_argument("--resume", action="store_true", help="Show resume prompt") parser.add_argument("--award-badge", type=str, help="Award a badge") parser.add_argument("--set-profile", type=str, help="Set profile as JSON string") parser.add_argument("--json", action="store_true", help="Output as JSON")
args = parser.parse_args()
# Load existing progress
progress = load_progress()
if args.init or args.reset:
if args.reset and progress:
confirm = input("Are you sure you want to reset all progress? (yes/no): ")
if confirm.lower() != "yes":
print("Reset cancelled.")
return
profile = None
if args.set_profile:
try:
profile = json.loads(args.set_profile)
except json.JSONDecodeError:
print("Error: Invalid JSON for profile")
return
progress = init_progress(profile)
print("β
Progress initialized" if args.init else "β
Progress reset")
return
if not progress:
print("No progress found. Use --init to start.")
print("Or run: /onboard-full")
return
if args.status:
if args.json:
print(json.dumps(progress, indent=2))
else:
print(get_status_display(progress))
return
if args.complete_phase is not None:
if 0 <= args.complete_phase <= 10:
progress = complete_phase(progress, args.complete_phase, args.score)
print(f"β
Phase {args.complete_phase} completed!")
else:
print("Error: Phase must be 0-10")
return
if args.exit:
progress = record_exit(progress)
print(get_exit_message(progress))
return
if args.resume:
print(get_resume_prompt(progress))
return
if args.award_badge:
if args.award_badge in BADGES:
progress = award_badge(progress, args.award_badge)
else:
print(f"Error: Unknown badge '{args.award_badge}'")
print(f"Available badges: {', '.join(BADGES.keys())}")
return
# Default: show status
print(get_status_display(progress))
if name == "main": main()