Skill: jacky-wzj/master-teacher (version 2.8.0)

--- name: master-teacher description: "Systematic teaching skill for AI agents. Transforms the agent into a master-level instructor using mastery learning, Socratic questioning, and structured lesson delivery. Use when: (1) user asks to 'learn', 'study', or 'systematically understand' a topic, (2) multi-lesson curriculum is needed (≥3 lessons), (3) user wants progress tracking across fragmented learning sessions. Triggers on: 'teach me', 'create a course', 'I want to learn', 'systematic study'. NOT for: single Q&A, one-off tasks, casual chat." --- # Master Teacher Systematic teaching skill: prep → profile → outline → content authoring → teach → verify → track. ## Scripts Deterministic operations use Python scripts. The model handles teaching and judgment; scripts handle state. | Script | Purpose | When to call | |--------|---------|-------------| | `scripts/init_course.py` | Create course directory and state files | Phase 2: after student confirms outline | | `scripts/track_progress.py start` | Mark a lesson as in-progress | Phase 3: when starting a lesson | | `scripts/track_progress.py step` | Record a completed step within a lesson | Phase 3: after each step (position/concepts/code/practice) | | `scripts/track_progress.py complete` | Mark a lesson as completed with score | Phase 3: after student passes verification | | `scripts/show_progress.py` | Display visual progress overview | Phase 4: after every lesson completion | | `scripts/lesson_report.py` | Generate and save lesson report | Phase 4: after every lesson completion (before show_progress) | | `scripts/resume.py` | Load resume state and suggest review | Phase 5: when student returns after a break | State lives in `progress/state.json` (single source of truth). `tracking.md` is auto-rebuilt by scripts. **Never manually write or edit tracking.md or state.json.** ## Execution Flow ### Phase 0: Prep **Prep is a research-heavy phase. It may span multiple sessions.** **Step 1: Collect materials** - Search for courses, tutorials, blogs, books, videos, and source code analysis on the topic - Save to course directory: ``` <course>/prep/ ├── sources.md ← All reference links + one-line evaluation ├── repos/ ← Cloned repositories ├── articles/ ← Saved articles/PDFs └── notes.md ← Research notes per source ``` - Search iteratively, cross-validate across sources **Step 2: Study materials** - Read each source, write findings into notes.md - Compare viewpoints (who is right? whose angle is unique?) - Identify consensus and disagreements - Do not rush this step **Step 3: Synthesize** - Design your knowledge tree (concepts, sequence, dependencies) - Borrow proven structures from existing materials - Fill gaps (what others missed or explained poorly) - Generate prep/synthesis.md: draft teaching plan When prep is complete, tell the student: "Prep done. Studied X sources. Ready to outline." ### Phase 1: Profile the Student Check USER.md / MEMORY.md first. Only ask what is missing: - Learning goal (what problem to solve) - Learning style (fragmented / continuous / daily) ### Phase 2: Outline 1. Split knowledge tree into lessons. Each lesson = one independently understandable concept unit 2. Define dependencies between lessons 3. Write README.md: goals, outline, one-line summary per lesson 4. Get student confirmation 5. **Run:** `scripts/init_course.py <dir> --title "..." --lessons '[...]'` ### Phase 2.5: Lesson Authoring (generate detailed lesson content) **Run this immediately after Phase 2 outline is confirmed.** This phase generates all lesson content (sections, media, practice) before any teaching begins. Do NOT proceed to Phase 3 until all lessons are fully authored. **This is where most of the actual content work happens. It is a content-heavy long task that may span multiple sessions.** Do NOT skip or rush. **Resume:** If Phase 2.5 spans multiple sessions, **before starting:** 1. Check `lessons/` directory — what lesson directories already exist 2. For each existing lesson, read `overview.md` to get the planned structure (sections and topics) 3. Compare planned structure against what actually exists in `sections/`, `media/`, `practice.md` 4. Resume from the first incomplete lesson/section 5. Do NOT regenerate already completed content **Important: Save as you go.** Every topic you generate, write it to the appropriate file immediately. Do NOT hold content in memory and save at the end — if interrupted, you lose it all. **Goal:** 教材级深度——参考正式教材的详尽程度，每课内容应充分展开，不能停留在摘要层面。 **目录结构：** ``` <course>/lessons/ └── lesson-NN-<name>/ ├── overview.md ← 本章概要 + 学习目标 ├── sections/ ← 小节，数量按需确定 │ ├── section-1.md ← 小节 1（可拆分为多个主题） │ ├── section-2.md ← 小节 2 │ └── ... ← 数量由内容复杂度决定 ├── media/ ← 预生成的多媒体素材 │ ├── diagrams/ ← 可视化素材 │ ├── audio/ ← 章节语音总结 │ └── video/ ← 复杂动态流程演示 └── practice.md ← 练习题 + 实践任务 ``` **Step 1: 分解章节结构（按需拆分）** - 根据每课内容的复杂度，**按需**拆分为若干小节（sections） - 每个小节**按需**拆分为若干主题（topics） - 拆分原则： ① 避免单次生成内容过长导致截断 ② 每个主题应是一个可独立理解的知识点 ③ 复杂概念多拆，简单概念少拆——由内容本身决定 - **每课第一步：创建 overview.md**，内容包括： - 本课在课程中的位置（Position） - 学习目标 - 章节结构计划（有哪些小节，每个小节包含哪些主题） - overview.md 是每课的“施工图”，后续生成内容时对照它执行，中断恢复时也靠它判断进度 **Step 2: 逐主题生成内容** 对每个主题充分展开，**不要停留在概述层面**。以下是可用的内容维度，按需选择组合： - **概念引入**：从 "为什么需要" 开始，再讲 "是什么" - **深入讲解**：技术细节、设计意图、trade-off 分析 - **代码/示例**：具体代码片段，关键行注释，伪代码 → 真实代码 - **对比分析**：为什么选 A 而不是 B - **联系实际**：如何应用到学生的实际工作中 - **小结**：本节要点回顾 不是每个主题都需要所有维度——有的主题重代码，有的重概念，有的重对比。由内容本身决定。 **Step 3: 预生成多媒体素材** > **原则：提前生成 + 上课时按需展示。该画图就画图，该录音频就录音频，该做视频就做视频——以"最能帮助学生理解"为唯一标准。** 以下情况**应该生成图片**（不是硬性要求，如果文字已经足够清晰也可以不画）： - 抽象概念难以用文字清晰描述 - 需要展示整体架构或鸟瞰视图 - 需要表达流程、时序、状态变化 - 需要对比多个方案/组件的关系 - 需要画示意图帮助理解 媒体类型选择原则： - **Image**：当画图能显著帮助理解时就画，画什么类型由内容决定 - **Audio**：章节要点回顾，适合学生路上听 - **Video**：当动态过程用静态图无法清晰表达时才用 **多媒体生成规则：** - 数量不设上限，该几张就几张 - 优先画图 > 纯文字描述（当画图能显著降低理解成本时） - 所有图片必须有文字说明（accessibility） - 保存到 `lessons/lesson-NN-*/media/` 目录 **Step 4: 编写练习题** - 练习题应覆盖理解、应用、分析等层次，数量由课程内容复杂度决定 - 设计实践任务（产出物：代码 / 设计 / 分析），数量按需 ### Phase 3: Teach (per lesson) **Before starting Phase 3, verify Phase 2.5 output:** - Confirm `lessons/` directory exists and contains content for the lesson you're about to teach - **If content is missing or incomplete, DO NOT generate it during Phase 3.** Stop teaching, go back to Phase 2.5, complete the missing content first, then return to Phase 3. - **Phase 3 is delivery only — never generate new lesson content during class.** The only content you may create on the fly is supplementary (e.g., answering an unexpected student question, generating a quick clarification diagram if needed). All core lesson content (sections, media, practice) must be pre-authored. **On lesson start, run:** `scripts/track_progress.py start <dir> <lesson>` > **Phase 3 is now delivery mode. All content was authored in Phase 2.5.** > The teacher presents pre-written content in digestible chunks, one topic at a time. **Delivery order per lesson:** **① Position** — Read from `overview.md`: where this lesson sits in the course, learning objectives → Run: `scripts/track_progress.py step <dir> <lesson> position` **② Present topics sequentially** — Read from `lessons/lesson-NN-*/sections/`, present one topic at a time - Present as markdown content (text + code + tables) - Show pre-generated multimedia (diagrams, audio, video) at appropriate points within the content — multimedia is part of the delivery, not a separate step - If 3+ consecutive theory paragraphs → insert a question → Run: `scripts/track_progress.py step <dir> <lesson> concepts` (after presenting core concepts) → Run: `scripts/track_progress.py step <dir> <lesson> code` (when the topic includes code/examples) → Run: `scripts/track_progress.py step <dir> <lesson> practice` (when the topic includes practice content) > Only call `step` scripts when the corresponding content actually exists for this topic. **③ Audio recap** — 课末提供语音总结（如果已预生成） **④ Verify** — Use pre-written verification questions from `practice.md` - 从 practice.md 中选取适量题目，覆盖理解、应用、分析等层次 - Teacher grades and gives feedback - Pass (多数答对) → proceed to Phase 4 - Fail → re-teach weak parts from different angle, then follow-up questions **On pass, run:** `scripts/track_progress.py complete <dir> <lesson> --score <X> [--weak "..."]` **Delivery rules:** - One topic per message — don't dump the entire lesson in one go - Wait for student to digest before advancing to next topic - Student can say "继续" to advance, or ask questions on current topic - Student can say "详细讲 X" to expand on a sub-topic - Student can say "没听懂" → re-explain from a different angle - Student can ask questions at any time — answer them before continuing ### Phase 4: Report and Progress After each lesson completion, in this order: 1. **Run:** `scripts/lesson_report.py <dir> <lesson> --strengths "..." --takeaway "..." [--weak "..."]` 2. **Run:** `scripts/show_progress.py <dir>` 3. If image generation is available: also generate a visual progress map image ### Phase 5: Resume When the student returns after any break: 1. **Run:** `scripts/resume.py <dir>` — read output to determine state 2. Based on output: - `STATUS: mid_lesson` → ask a recall question on what was covered, then continue from the indicated next step - `STATUS: between_lessons` → ask a recall question on the last completed lesson, then start the next - `SPACED_REVIEW_DUE` → do a quick review of indicated lessons before continuing 3. If student recalls well → continue. If fuzzy → brief recap. If forgot → re-teach before moving forward. ### Phase 6: Review and Wrap-up - **Spaced review**: handled automatically by `resume.py` (triggers at lessons 3, 6, midpoint) - **Final report**: on course completion, generate a comprehensive summary — mastery level per lesson, overall weak points, next steps ## Constraints ### Teaching principles 1. **Mastery first**: do not advance if current lesson is not mastered 2. **Student must produce**: every lesson needs at least one student output (write/draw/design) 3. **Question-driven**: guide with questions, do not give conclusions - Good: "Why do you think they designed it this way?" - Bad: "They designed it this way because..." 4. **Affirm then correct**: acknowledge valid reasoning first, then point out the gap 5. **Match technical depth**: for technical students, go deep by default 6. **Technical analogies**: use architecture/design-pattern analogies, not daily life 7. **Content volume matters**: 教材级深度，充分展开每个知识点。薄摘要不是课程，具体篇幅由内容本身决定。 8. **Hierarchical delivery**: present one sub-topic at a time, let student digest before advancing. 9. **Pre-generate, don't improvise**: multimedia, diagrams, examples — all prepared before teaching begins. 10. **Never generate lesson content during class**: Phase 3 is delivery mode only. If content is incomplete, pause teaching, complete Phase 2.5 first, then resume delivery. 11. **State facts before comparing**: don't assume the student knows the result. Always tell them the fact first, then do the comparison. Example: don't jump into a comparison table — first say "Claude Code uses Bun", then explain what Bun is, then compare with Node.js. 12. **State facts before questioning**: always present the information before asking a question. Don't open a section with a question the student can't yet answer. The flow should be: fact → explanation → question. ### Quality checklist (before starting Phase 3: teaching) **Pre-delivery checklist (must pass all before teaching):** - [ ] `lessons/` 目录结构正确（每课有 overview.md、sections/、media/、practice.md） - [ ] 每课内容达到教材级深度（充分展开，不是摘要） - [ ] 如果生图工具可用，需要可视化的概念都已生成图片；如果工具不可用，至少用 ASCII 图或文字图示替代 - [ ] 每课有语音 recap（如果 TTS 可用） - [ ] 每课有练习题和实践任务 - [ ] 实际内容与 README.md 大纲一致 ### Edge cases | Situation | Action | |-----------|--------| | "too easy" | Skip verification, advance | | "don't understand" | Re-explain from different angle | | Goes off-topic | Acknowledge briefly, redirect | | Returns after break | Run `scripts/resume.py`, follow Phase 5 | | Teacher lacks depth | Pause, research, then resume |

path: README.md
content: # Master Teacher 🎓

Systematic teaching skill for AI agents — mastery learning, Socratic questioning, and progress tracking.

Transforms your AI agent into a master-level instructor for teaching complex technical topics (architecture, source code analysis, system design).

## Features

- **Prep before teaching** — agent researches the topic before lecturing
- **Mastery learning** — students must pass verification (≥2/3) before advancing
- **Socratic questioning** — guide with questions, don't give conclusions
- **Structured lessons** — position → theory → code → relate to practice → verify
- **Progress tracking** — persistent across fragmented learning sessions
- **Adaptive pacing** — handles "too easy", "don't understand", off-topic, silence
- **Mid-course review** — revisit weak points at halfway mark
- **Final report** — summary of mastery, weak points, and next steps

## Install

### Claude Code

```bash
# Option 1: Clone to personal skills
git clone https://github.com/jacky-wzj/master-teacher.git ~/.claude/skills/master-teacher

# Option 2: Clone to project skills
git clone https://github.com/jacky-wzj/master-teacher.git .claude/skills/master-teacher
```

Then in Claude Code, the skill activates when you say things like:
- "教我 X"
- "teach me about X"
- "我要系统学习 X"
- "create a course on X"

Or invoke directly: `/master-teacher`

### OpenClaw

```bash
# From ClawHub
clawhub install master-teacher

# Or clone manually
git clone https://github.com/jacky-wzj/master-teacher.git ~/.openclaw/skills/master-teacher
```

The skill triggers when the user asks to systematically learn a topic.

## Usage

Once installed, just ask the agent to teach you something:

```
我想系统学习 Claude Code 的架构设计
```

The agent will:
1. **Prep** — research the topic first
2. **Profile** — check what it already knows about you, ask only what's missing
3. **Outline** — propose a curriculum, wait for your confirmation
4. **Teach** — deliver lessons one at a time with verification
5. **Track** — save progress so you can resume anytime

## Course files

Each course is stored in the workspace:

```
<workspace>/<course-name>/
├── README.md              # Course outline
├── lessons/
│   ├── lesson-01-xxx.md   # Lesson content
│   └── lesson-02-xxx.md
└── progress/
    └── tracking.md        # Learning progress
```

## License

MIT


path: scripts/init_course.py
content: #!/usr/bin/env python3
"""Initialize a course directory structure."""

import argparse
import os
import json
from datetime import datetime


def init_course(course_dir: str, title: str, lessons: list[dict]):
    """
    Create course directory with README, progress tracking, and prep folder.
    
    Args:
        course_dir: path to course root
        title: course title
        lessons: list of {"number": int, "title": str, "summary": str}
    """
    os.makedirs(os.path.join(course_dir, "lessons"), exist_ok=True)
    os.makedirs(os.path.join(course_dir, "progress"), exist_ok=True)
    os.makedirs(os.path.join(course_dir, "prep", "repos"), exist_ok=True)
    os.makedirs(os.path.join(course_dir, "prep", "articles"), exist_ok=True)

    # README.md
    lesson_table = "| # | Title | Summary |\n|---|-------|--------|\n"
    for l in lessons:
        lesson_table += f"| {l['number']} | {l['title']} | {l['summary']} |\n"

    readme = f"""# {title}

Created: {datetime.now().strftime('%Y-%m-%d')}

## Lessons

{lesson_table}
"""
    with open(os.path.join(course_dir, "README.md"), "w") as f:
        f.write(readme)

    # progress/tracking.md
    status_rows = ""
    for l in lessons:
        status_rows += f"| {l['number']} | {l['title']} | pending | - | - | - |\n"

    tracking = f"""# Progress: {title}

## Current State
- Completed: 0/{len(lessons)}
- Current lesson: 1
- Last session: not started
- Overall: 0%

## Lessons
| # | Title | Status | Date | Score | Weak points |
|---|-------|--------|------|-------|-------------|
{status_rows}
## Session Log
"""
    with open(os.path.join(course_dir, "progress", "tracking.md"), "w") as f:
        f.write(tracking)

    # progress/state.json (machine-readable state)
    state = {
        "title": title,
        "total_lessons": len(lessons),
        "completed": 0,
        "current_lesson": 1,
        "current_step": None,
        "lessons": {
            str(l["number"]): {
                "title": l["title"],
                "status": "pending",
                "score": None,
                "weak_points": [],
                "started_at": None,
                "completed_at": None,
                "steps_completed": [],
                "last_student_response": None,
                "key_points_covered": [],
                "key_points_remaining": []
            }
            for l in lessons
        }
    }
    with open(os.path.join(course_dir, "progress", "state.json"), "w") as f:
        json.dump(state, f, indent=2, ensure_ascii=False)

    print(f"Course initialized: {course_dir}")
    print(f"  Lessons: {len(lessons)}")
    print(f"  Files: README.md, progress/tracking.md, progress/state.json")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Initialize a course")
    parser.add_argument("course_dir", help="Course directory path")
    parser.add_argument("--title", required=True, help="Course title")
    parser.add_argument("--lessons", required=True, help='JSON array: [{"number":1,"title":"...","summary":"..."}]')
    args = parser.parse_args()

    lessons = json.loads(args.lessons)
    init_course(args.course_dir, args.title, lessons)


path: scripts/lesson_report.py
content: #!/usr/bin/env python3
"""Generate lesson report after completion."""

import argparse
import json
import os
from datetime import datetime


def lesson_report(course_dir: str, lesson_num: int, strengths: str, weak_points: str, takeaway: str):
    path = os.path.join(course_dir, "progress", "state.json")
    with open(path) as f:
        state = json.load(f)

    key = str(lesson_num)
    l = state["lessons"][key]
    total = state["total_lessons"]
    completed = state["completed"]
    pct = round(completed / total * 100) if total > 0 else 0

    started = l.get("started_at", "?")
    ended = l.get("completed_at", datetime.now().strftime("%Y-%m-%d %H:%M"))
    score = l.get("score", "?")

    report = f"""
╔══════════════════════════════════════════╗
║          📋 Lesson Report                ║
╠══════════════════════════════════════════╣
║  Lesson:    {lesson_num} — {l['title']:<28}║
║  Score:     {score}/3                            ║
║  Started:   {started:<28}║
║  Completed: {ended:<28}║
╠══════════════════════════════════════════╣
║  ✅ Strengths:                           ║
║     {strengths:<36}║
║  ⚠️  Weak points:                        ║
║     {weak_points:<36}║
║  💡 Key takeaway:                        ║
║     {takeaway:<36}║
╠══════════════════════════════════════════╣
║  Overall: {completed}/{total} lessons ({pct}%)               ║
╚══════════════════════════════════════════╝
"""
    print(report)

    # Also save to file
    report_path = os.path.join(course_dir, "progress", f"report-lesson-{lesson_num:02d}.md")
    with open(report_path, "w") as f:
        f.write(f"# Lesson {lesson_num} Report: {l['title']}\n\n")
        f.write(f"- Date: {ended}\n")
        f.write(f"- Score: {score}/3\n")
        f.write(f"- Strengths: {strengths}\n")
        f.write(f"- Weak points: {weak_points}\n")
        f.write(f"- Key takeaway: {takeaway}\n")
        f.write(f"- Overall progress: {completed}/{total} ({pct}%)\n")

    print(f"Report saved: {report_path}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Generate lesson report")
    parser.add_argument("course_dir", help="Course directory path")
    parser.add_argument("lesson", type=int, help="Lesson number")
    parser.add_argument("--strengths", required=True, help="What student understood well")
    parser.add_argument("--weak", default="none", help="What needs revisiting")
    parser.add_argument("--takeaway", required=True, help="One sentence key takeaway")
    args = parser.parse_args()
    lesson_report(args.course_dir, args.lesson, args.strengths, args.weak, args.takeaway)


path: scripts/resume.py
content: #!/usr/bin/env python3
"""Load resume state — determine where student left off and what to review."""

import argparse
import json
import os


def resume(course_dir: str):
    path = os.path.join(course_dir, "progress", "state.json")
    with open(path) as f:
        state = json.load(f)

    completed = state["completed"]
    total = state["total_lessons"]
    current = state["current_lesson"]
    current_key = str(current)

    print(f"Course: {state['title']}")
    print(f"Progress: {completed}/{total} completed")
    print()

    if current_key not in state["lessons"]:
        print(f"STATUS: course_complete")
        print(f"All {total} lessons completed.")
        return

    l = state["lessons"][current_key]

    if l["status"] == "in-progress":
        # Mid-lesson resume
        print(f"STATUS: mid_lesson")
        print(f"LESSON: {current} — {l['title']}")
        print(f"STARTED: {l.get('started_at', '?')}")
        print(f"STEPS_DONE: {', '.join(l.get('steps_completed', []))}")
        print(f"NEXT_STEP: {_next_step(l.get('steps_completed', []))}")
        if l.get("key_points_covered"):
            print(f"COVERED: {', '.join(l['key_points_covered'])}")
        if l.get("key_points_remaining"):
            print(f"REMAINING: {', '.join(l['key_points_remaining'])}")
        if l.get("last_student_response"):
            print(f"LAST_RESPONSE: {l['last_student_response']}")

        # Suggest review of what was covered
        if l.get("key_points_covered"):
            print(f"\nREVIEW_SUGGESTION: Ask student to recall: {l['key_points_covered'][0]}")

    elif l["status"] == "pending":
        # Between lessons
        print(f"STATUS: between_lessons")
        print(f"NEXT_LESSON: {current} — {l['title']}")

        # Find last completed lesson for review
        prev_key = str(current - 1)
        if prev_key in state["lessons"] and state["lessons"][prev_key]["status"] == "completed":
            prev = state["lessons"][prev_key]
            print(f"LAST_COMPLETED: {current - 1} — {prev['title']} (score: {prev['score']}/3)")
            if prev.get("weak_points"):
                print(f"WEAK_POINTS: {', '.join(prev['weak_points'])}")
                print(f"REVIEW_SUGGESTION: Review weak points from lesson {current - 1} before starting")
            else:
                print(f"REVIEW_SUGGESTION: Quick recall question on lesson {current - 1}")
    else:
        print(f"STATUS: unknown ({l['status']})")

    # Check if spaced review is due
    spaced_review = _check_spaced_review(state)
    if spaced_review:
        print(f"\nSPACED_REVIEW_DUE: Revisit lessons {', '.join(map(str, spaced_review))}")


def _next_step(completed_steps: list) -> str:
    all_steps = ["position", "concepts", "code", "practice", "multimedia", "verify"]
    for s in all_steps:
        if s not in completed_steps:
            return s
    return "all_done"


def _check_spaced_review(state: dict) -> list:
    """Check if spaced review is due based on current progress."""
    completed = state["completed"]
    review_lessons = []

    # At lesson 3, review lesson 1
    if completed == 2:
        review_lessons.append(1)
    # At lesson 6, review lessons 1-3
    elif completed == 5:
        review_lessons.extend([1, 2, 3])
    # At midpoint, review all weak points
    elif completed == state["total_lessons"] // 2:
        for num, l in state["lessons"].items():
            if l.get("weak_points"):
                review_lessons.append(int(num))

    return review_lessons


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Load resume state")
    parser.add_argument("course_dir", help="Course directory path")
    args = parser.parse_args()
    resume(args.course_dir)


path: scripts/show_progress.py
content: #!/usr/bin/env python3
"""Show course progress overview — ASCII visualization."""

import argparse
import json
import os


def show_progress(course_dir: str):
    path = os.path.join(course_dir, "progress", "state.json")
    with open(path) as f:
        state = json.load(f)

    total = state["total_lessons"]
    completed = state["completed"]
    pct = round(completed / total * 100) if total > 0 else 0

    # Progress bar
    bar_width = 30
    filled = round(bar_width * completed / total) if total > 0 else 0
    bar = "=" * filled + "." * (bar_width - filled)
    print(f"\n  Course: {state['title']}")
    print(f"  Progress: [{bar}] {completed}/{total} ({pct}%)\n")

    # Lesson list
    for num in sorted(state["lessons"].keys(), key=int):
        l = state["lessons"][num]
        status = l["status"]
        if status == "completed":
            icon = "✅"
            suffix = f" — {l['score']}/3"
        elif status == "in-progress":
            icon = "🔵"
            steps = ", ".join(l.get("steps_completed", []))
            suffix = f" ← in progress ({steps})"
        else:
            icon = "⬜"
            suffix = ""

        # Mark current
        marker = ""
        if int(num) == state["current_lesson"] and status != "completed":
            marker = "  👈 you are here"

        print(f"  {icon} Lesson {num}: {l['title']}{suffix}{marker}")

    # Weak points summary
    all_weak = []
    for num in sorted(state["lessons"].keys(), key=int):
        l = state["lessons"][num]
        if l["weak_points"]:
            for w in l["weak_points"]:
                all_weak.append(f"L{num}: {w}")

    if all_weak:
        print(f"\n  ⚠️  Weak points to revisit:")
        for w in all_weak:
            print(f"     - {w}")

    print()


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Show course progress")
    parser.add_argument("course_dir", help="Course directory path")
    args = parser.parse_args()
    show_progress(args.course_dir)


path: scripts/track_progress.py
content: #!/usr/bin/env python3
"""Update course progress — lesson completion, intra-lesson state, scores."""

import argparse
import json
import os
from datetime import datetime


def load_state(course_dir: str) -> dict:
    path = os.path.join(course_dir, "progress", "state.json")
    with open(path) as f:
        return json.load(f)


def save_state(course_dir: str, state: dict):
    path = os.path.join(course_dir, "progress", "state.json")
    with open(path, "w") as f:
        json.dump(state, f, indent=2, ensure_ascii=False)
    rebuild_tracking_md(course_dir, state)


def rebuild_tracking_md(course_dir: str, state: dict):
    """Rebuild tracking.md from state.json (single source of truth)."""
    completed = state["completed"]
    total = state["total_lessons"]
    pct = round(completed / total * 100) if total > 0 else 0

    lines = [
        f"# Progress: {state['title']}\n",
        f"## Current State",
        f"- Completed: {completed}/{total}",
        f"- Current lesson: {state['current_lesson']}",
        f"- Overall: {pct}%\n",
        f"## Lessons",
        "| # | Title | Status | Date | Score | Weak points |",
        "|---|-------|--------|------|-------|-------------|",
    ]

    for num in sorted(state["lessons"].keys(), key=int):
        l = state["lessons"][num]
        date = l.get("completed_at") or l.get("started_at") or "-"
        score = f"{l['score']}/3" if l["score"] is not None else "-"
        weak = ", ".join(l["weak_points"]) if l["weak_points"] else "-"
        lines.append(f"| {num} | {l['title']} | {l['status']} | {date} | {score} | {weak} |")

    lines.append("\n## Session Log")

    # Append in-progress details if any
    cur = str(state["current_lesson"])
    if state["lessons"].get(cur, {}).get("status") == "in-progress":
        l = state["lessons"][cur]
        lines.append(f"\n### Lesson {cur} — in progress")
        lines.append(f"- Started: {l.get('started_at', '-')}")
        lines.append(f"- Steps completed: {', '.join(l.get('steps_completed', []))}")
        if l.get("key_points_covered"):
            lines.append(f"- Covered: {', '.join(l['key_points_covered'])}")
        if l.get("key_points_remaining"):
            lines.append(f"- Remaining: {', '.join(l['key_points_remaining'])}")

    path = os.path.join(course_dir, "progress", "tracking.md")
    with open(path, "w") as f:
        f.write("\n".join(lines) + "\n")


def start_lesson(course_dir: str, lesson_num: int):
    state = load_state(course_dir)
    key = str(lesson_num)
    state["lessons"][key]["status"] = "in-progress"
    state["lessons"][key]["started_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
    state["current_lesson"] = lesson_num
    state["current_step"] = "position"
    save_state(course_dir, state)
    print(f"Lesson {lesson_num} started")


def update_step(course_dir: str, lesson_num: int, step: str, points_covered: list = None, points_remaining: list = None, last_response: str = None):
    state = load_state(course_dir)
    key = str(lesson_num)
    l = state["lessons"][key]
    if step not in l["steps_completed"]:
        l["steps_completed"].append(step)
    if points_covered:
        l["key_points_covered"] = points_covered
    if points_remaining:
        l["key_points_remaining"] = points_remaining
    if last_response:
        l["last_student_response"] = last_response
    state["current_step"] = step
    save_state(course_dir, state)
    print(f"Lesson {lesson_num}: step '{step}' recorded")


def complete_lesson(course_dir: str, lesson_num: int, score: int, weak_points: list = None):
    state = load_state(course_dir)
    key = str(lesson_num)
    state["lessons"][key]["status"] = "completed"
    state["lessons"][key]["completed_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
    state["lessons"][key]["score"] = score
    state["lessons"][key]["weak_points"] = weak_points or []
    state["lessons"][key]["steps_completed"] = ["position", "concepts", "code", "practice", "verify"]
    state["completed"] += 1
    state["current_lesson"] = lesson_num + 1
    state["current_step"] = None
    save_state(course_dir, state)
    print(f"Lesson {lesson_num} completed. Score: {score}/3. Progress: {state['completed']}/{state['total_lessons']}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Update course progress")
    sub = parser.add_subparsers(dest="command")

    p_start = sub.add_parser("start", help="Start a lesson")
    p_start.add_argument("course_dir")
    p_start.add_argument("lesson", type=int)

    p_step = sub.add_parser("step", help="Record a completed step within a lesson")
    p_step.add_argument("course_dir")
    p_step.add_argument("lesson", type=int)
    p_step.add_argument("step_name", help="position|concepts|code|practice|multimedia|verify")
    p_step.add_argument("--covered", nargs="*", help="Key points covered so far")
    p_step.add_argument("--remaining", nargs="*", help="Key points remaining")
    p_step.add_argument("--response", help="Student's last response snippet")

    p_complete = sub.add_parser("complete", help="Complete a lesson")
    p_complete.add_argument("course_dir")
    p_complete.add_argument("lesson", type=int)
    p_complete.add_argument("--score", type=int, required=True, help="Score out of 3")
    p_complete.add_argument("--weak", nargs="*", help="Weak points")

    args = parser.parse_args()

    if args.command == "start":
        start_lesson(args.course_dir, args.lesson)
    elif args.command == "step":
        update_step(args.course_dir, args.lesson, args.step_name,
                    points_covered=args.covered, points_remaining=args.remaining,
                    last_response=args.response)
    elif args.command == "complete":
        complete_lesson(args.course_dir, args.lesson, args.score, weak_points=args.weak)


path: templates/lesson.md
content: ---
## Lesson template — copy and fill for each lesson

# Lesson X: [Title]

> One-line summary

## Position
Where this sits in the course. What it depends on. What it unlocks.

## Core Concepts

### Design intent (why)

### Comparison (why A not B)

### Key ideas

## Code / Examples

```
# annotated snippet
```

## How This Applies to Our Work

## Verification

| # | Question | Type | Answer key |
|---|----------|------|-----------|
| 1 | | Comprehension | |
| 2 | | Application | |
| 3 | | Analysis | |

### Student answers & grading
(filled during teaching)


path: templates/progress.md
content: ---
## Progress template — copy for each course

# Progress: [Course Name]

## Current State
- Completed: 0/N
- Current lesson: 1
- Last session: not started
- Next: Lesson 1

## Lessons
| # | Title | Status | Date | Score | Weak points |
|---|-------|--------|------|-------|-------------|

## Session Log
(appended after each lesson)


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)