Skip to main content

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()