Learn Something Framework
Turn subject into structured curriculum via interactive LLM session. Study via CLI: read lessons, explain-back, drill MCQs, spaced repetition.
Part A — Instruction Level
What to do when authoring content. Schema-level contract lives in Part B; every structural fact is enforced there by learn.sh validate.
A1. Pedagogy
Three theories fused:
| Theory | AI role | Problem solved |
|---|---|---|
| Marva Collins | Socratic tutor. Rigorous Q&A, high expectations, endless patience | Needs great teacher. AI never tires. |
| Feynman Technique | Gap detector. Learner explains simply → AI finds holes | Illusion of understanding |
| Desirable Difficulties | Spaced scheduler. FSRS retrieval + interleaved practice | Passive re-reading |
Session phase → theory mapping
| Phase | Theory | Learner | AI/CLI |
|---|---|---|---|
| Read | Marva | Study lesson | Write clear content |
| Explain | Feynman | Explain concept simply | Probe: "you said X, but Y — reconcile" |
| Drill | Marva + Desirable | Answer MCQs, justify | Grade, explain distractors |
| Judge | Marva | Critique, form opinion | Socratic follow-up |
| Review | Desirable | Active recall via FSRS | Schedule optimal intervals |
A2. Course design
Time budget (hard cap): every module ≤ 1.5h, regardless of level. Depth comes from more modules, not longer ones. Subject ≤ 40h guideline (deep courses may exceed; schema allows up to 200h).
| Level | Module time | Modules per course | Why |
|---|---|---|---|
| beginner | 30-45 min | more (~25-30) | More checkpoints = more frequent success feedback |
| intermediate | 1-1.5h | ~15-20 | Balance depth vs momentum |
| advanced | 1-1.5h | more (~20-30) | Long topics split short; open questions carry depth |
- SRS review: ~10-15 min daily
Question mix by level: beginner = mostly closed (MCQ/cloze, one right answer) for low-friction checkpoints; advanced = open-ended (explain trade-offs, design a solution, critique a claim, compare approaches).
A3. Content creation workflow
- Scope (5 min): Ask domain/level/time budget/lang/use case. Propose syllabus.
- Outline stage: Verify module DAG + prerequisite chain. Check time budget.
- Per module (10 min): Create module with
learn.sh create-module <topic> NN-name. Zero-padded two-digit number + kebab-case name (e.g.,01-intro,02-core-concepts). lesson.md H1 title is human-readable only:# Module NN: <name>using the syllabus modulename(e.g.# Module 09: Layout & Container Composition). Zero-paddedNN, colon, human title. Never embed the directory slug (NN-name) in the H1 — the slug is the folder's name, not the title. Write lesson.md + quiz.yaml + cloze.yaml. Apply Part A rules inline. Respect the module time budget (syllabus.yamltime_hours≤ 1.5h — the hard cap); if a lesson overflows its budget, split into additional modules and update syllabus. Lesson.md length is a soft guide only (WARN, never blocking; threshold user-settable viavalidate --max-chars). quiz.yaml: LLM writes option content + the correct option by content (letters as placeholders); runlearn.sh balance-quiz <topic> <module>to re-letter answers into a balanced, no-3-run spread (deterministic per topic-module, backup.bak). lesson.md: runlearn.sh checksyntax <topic> <module> [--render api|local]— lint mermaid blocks, code blocks (python/js/shell when interpreters present), and fence balance before continuing. User reviews. Proceed. - Validate: Run
learn.sh validate <topic> [module]. Fix all ERR + WARN before proceeding. Quality gate is blocking (all checks ERR).balance-quiz+checksyntaxare the generation-stage, per-module guards;validatestays the final gate. - Pre-publication: Load
content-verify.md. Run checklist. Fix violations. - Compile SRS (2 min): Extract MCQs → FSRS-6 deck.
- Cumulative quizzes: After every 3-4 modules (max span 4 — enforced by
validate), generatecumulative_quiz_XX-YY.yamlin subject root (zero-padded module range, e.g.cumulative_quiz_01-04.yaml). See B2 for exact shape. Filename must carry the covered module rangeXX-YY; single-filecumulative_quiz.yamlis rejected.
A4. Content principles + quality rules
Design principles
- CILO alignment: Every module serves ≥1 learning objective. No filler.
- Language: All content in syllabus.yaml language (
en/zh/yue). - Practical first: Start with concrete example before abstract definition.
- Domain-relevant: Scenarios from learner's industry.
- MCQ diversity: 40% recall, 40% application, 20% multi-step. Beginner skews recall; advanced shifts to open questions.
- Feynman prompt: Every lesson ends with explanation task + AI gap probe.
- Desirable difficulty: Same concept tested at different angles across modules.
- Progression: Build on prerequisites. Earlier modules foundational.
- Skip permitted: Learner gives sparse input? Generate content anyway with sensible defaults. Do not block.
- Socratic throughout: Every major concept followed by a question that makes learner stop and think — not only at module end. Pattern: state concept → ask "why?" / "what if?" → answer immediately so learner can self-check.
Quality rules (17) — enforced by learn.sh validate
All 17 below have an automated check in the quality gate (see B5). Mindmap (15) is ERR; the rest are WARN — including Module size (16), which is advisory only because the real size control is the module time budget, and Human-readable title (17), which catches the H1 slug leak.
| # | Rule | What to do | Automated check |
|---|---|---|---|
| 1 | Explain conventions | State why convention exists, not just what it is | WARN: conventions prose heuristic |
| 2 | Answer implicit Qs | Anticipate 1-3 questions learner hasn't asked | WARN: Q&A block presence |
| 3 | Pull-to-par intuition | Explain price → face value at maturity is mechanical | WARN |
| 4 | Causal chain first | Intuitive logic before formula | WARN |
| 5 | Practical context | Every number gets real meaning | WARN |
| 6 | "How likely" | Tell normal vs rare frequencies | WARN |
| 7 | Common misconceptions | Flag 1-2 specific errors beginners hold | WARN |
| 8 | Socratic throughout | Every concept section embeds Think question + immediate answer | WARN: > **Think**: blockquote in non-fence content |
| 9 | Dual coding | Every concept gets non-redundant diagram. Use the visual asset decision tree (§A4.1) to pick between Mermaid and markdown table. Mermaid palette: #5c7a99/#5c8a6a/#b8924a/#b86a4a/#7a5a8a/#888, strokes #333, theme neutral |
WARN: visual asset count ≥ 1 per concept section |
| 10 | Concrete-first | Start module with real-world example before abstract definition | WARN |
| 11 | Cloze deletions | 3-5 per module. Key terms blanked as {term}. Format: {blank} marks the term to fill in. Good cloze = conceptual context, not trivial blanking |
WARN: {...} in non-fence content |
| 12 | Predict-next | 2-3 per module. Learner commits to outcome before reveal | WARN: > **Predict**: blockquote |
| 13 | Error-spotting | 1-2 per module. Present plausible wrong solution | WARN: > **Spot the Mistake**: blockquote or ## Spot the Mistake heading with prose body |
| 14 | Graduated examples | Full worked → partial → independent | WARN |
| 15 | Module mindmap | Compulsory. Mermaid mindmap at top of lesson.md (after metadata, before Learning Objectives). ```mermaid\nmindmap\n root((Module Title)), max 3 levels deep |
ERR: mindmap present |
| 16 | Module size limit | Time budget is the hard cap (time_hours ≤1.5h); split if a lesson overflows it. Char count = soft guide (default 12,000; user sets own limit via validate --max-chars N). Exceeding it warns, never blocks |
WARN: char count > max_chars |
| 17 | Human-readable title | lesson.md H1 = # Module NN: <syllabus name> — zero-padded NN, colon, human title only. The directory slug (NN-name) never appears in the H1; the slug is the folder's name, not the title |
WARN: H1 missing, or module id is not a plain number (# Module NN-slug: ... fails) |
A4.1 Visual asset decision tree — when to use what
A lesson can carry 3 kinds of visual asset. Each fits a different purpose. Pick the one that matches the content, not the other way around.
| Asset | Best for | Avoid when |
|---|---|---|
| Markdown table | Categorical data, comparisons, lists with multiple attributes per row, anything that benefits from a columnar layout. Stays in the markdown — no extra render step. | You need to show process, flow, or relationship. |
| Mermaid diagram | Flow / process / sequence, hierarchies, cause-effect chains, state machines, anything with arrows and nodes. Renders server-side or via learn.sh render-diagrams. |
Static classification (use table). |
Decision flow
Is the content primarily:
│
├─ Categorical (rows × columns) → Markdown table
│
└─ Process / flow / sequence / hierarchy → Mermaid
Rules of thumb
- One asset per concept section. Don't stack the same idea in table + Mermaid. Pick the strongest.
- Mermaid is the workhorse: use it whenever a flow or tree beats prose.
- Markdown table wins for any list where the reader scans by attribute (Name / Definition / Example / Risk).
- Mixing OK when assets do different jobs: e.g. Mermaid for the 50/30/20 split + a table for the per-line breakdown. They reinforce, not duplicate.
- CJK safety: Mermaid labels must be quoted (
|"label"|) when they contain CJK + ASCII mix, or contain()etc. Unquoted labels triggerchecksyntaxWARN.
Anti-patterns to reject during review
- ASCII
┌─┐ │ └─┘boxes inside```textblocks that simply list categories — convert to a markdown table. - Mermaid
flowchartof a 2-3 item list — use a table instead.
A5. Study protocol
Session types
| Type | Duration | What to do |
|---|---|---|
| LEARN | 45-60 min | learn.sh start → read lesson → reframe → learn.sh quiz |
| EXPLAIN | 15-20 min | Pick concept. Explain simply aloud/in writing. AI probes gaps. Loop until holds. |
| BLURT | 10-15 min | learn.sh blurting <topic> <module> — brain-dump before review, AI shows gaps |
| REVIEW | 10-15 min | learn.sh review → due FSRS cards (interleaved across modules) |
| MIXED | 30-45 min | BLURT (10min) → REVIEW (10min) → LEARN (20min) → EXPLAIN (5min) |
FSRS rules
- Uses FSRS-6 algorithm (replaces FSRS-5 and SM-2). Cards tracked by: stability, difficulty, lapses, state, algorithm_version, desired_retention, learning_step.
- Correct (q≥4): stability grows with recall. Wrong (q<3): stability drops, difficulty rises.
- See
fsrs.pyfor full FSRS parameter set. learn.sh fsrs-predict <topic>shows avg stability/difficulty/retention per topic.- Old SM-2 decks auto-migrate on first review after upgrade.
Desirable Difficulties in practice
- Interleaved: Review session mixes 3+ module tags.
- Varied difficulty: Easy recall day 1 → harder scenario variants at next interval.
- Generation: Type answer before seeing options (optional CLI mode).
- Context variation: Year 2 uses different scenarios than year 1.
A6. CLI
# ── Content Creation ──────────────────────────────
learn.sh init <topic> [lang] [--depth survey|standard|deep] [--pretest]
# Initialize topic dir with syllabus template
# --depth: survey (~6 modules), standard (~18), deep (~28)
# --pretest: test first, skip known content
learn.sh start <topic> # Overview + module list
learn.sh create-module <topic> <id> # Create module from template
learn.sh create-cloze <topic> <module> # Create cloze.yaml from template
learn.sh enrich <topic> [module|--all] [--types cloze,predict,error,diagram,cloze-quiz] [--dry-run] [--render-mode api|local|off]
# Add cloze/predict/error/diagram to existing lessons
# Uses DeepSeek API. Backup as .md.bak
# --types: comma-separated subset of types (includes cloze-quiz for cloze.yaml generation)
# --render-mode: auto-render diagrams to PNG (default: api)
learn.sh render-diagrams <topic> [module] [--render-mode api|local] [--scale N]
# Render ```mermaid blocks to PNG in lesson.md
# --render-mode: api (mermaid.ink) or local (mmdc CLI)
# --scale: PNG scale factor (default 2 = 300dpi)
learn.sh mindmap <topic> <module> # Generate/regenerate Mermaid mindmap for module
learn.sh balance-quiz <topic> <module> # Re-letter quiz.yaml answers: balanced spread,
# no 3 consecutive same letter. Deterministic per
# topic-module (idempotent). Backup quiz.yaml.bak
learn.sh checksyntax <topic> <module> [--render api|local|off]
# Generation-stage lint: fence balance, mermaid blocks,
# code blocks (python/js/shell via interpreters)
# --render: smoke-render mermaid via render-diagrams
# Exit 1 on hard syntax errors
# ── Study ─────────────────────────────────────────
learn.sh quiz <topic> <module> [--adaptive] [--weak-only]
# MCQ drill
# --adaptive: weighted by ease, difficulty ramp, streak skip
# --weak-only: only cards with ease < 2.0
learn.sh cloze <topic> <module> [--adaptive] [--weak-only]
# Cloze (fill-in-blank) drill
# --adaptive: weighted by ease, difficulty ramp, streak skip
# --weak-only: only cards with ease < 2.0
learn.sh cumulative-quiz <topic> [--modules X-Y]
# Cross-module quiz (8-10 questions, mix of MCQ/cloze/T/F)
# --modules: filter to specific module range
learn.sh explain <topic> <module> # Feynman prompt guide
learn.sh review <topic> # FSRS spaced repetition
learn.sh blurting <topic> <module> # Brain-dump before review. AI compares to lesson
# ── Progress & Analytics ──────────────────────────
learn.sh stats <topic> # Progress + retention
learn.sh analytics <topic> # Mastery breakdown, weak modules, session history
learn.sh forecast <topic> # Cards due: now / week / month / later
learn.sh study-plan <topic> # Optimal session: due + weak, skip mastered
learn.sh fsrs-predict <topic> # Avg stability, difficulty, retention per topic
# ── Feedback ──────────────────────────────────────
learn.sh rate <topic> <module> <1-5> # Rate module clarity
learn.sh flag <topic> <module> <type> # Report error (wrong/outdated/confusing)
learn.sh feedback <topic> # Aggregate ratings + flag counts
# ── Export ────────────────────────────────────────
learn.sh export <topic> # Anki CSV export
learn.sh epub <topic> [file] [--local] [--description TEXT]
# Export to EPUB book
# --local: use mmdc CLI for Mermaid
# --description: cover page description
learn.sh epub-regen <topic> [file] [--local] [--description TEXT]
# Regenerate EPUB from cached markdown
learn.sh epub-verify <topic> [file] # Validate EPUB structure
learn.sh epub-list-themes # List available EPUB themes
learn.sh pdf <topic> [file] [--engine auto|weasyprint|pandoc|raw]
# Export to PDF
learn.sh pdf-regen <topic> [file] [--engine] # Regenerate PDF from cached book.md
# ── Sync ──────────────────────────────────────────
learn.sh sync <topic> # Export to Reader dir (~/.coursereader/subjects/)
learn.sh sync-pull <topic> # Import deck from Reader dir
# ── Validation ──────────────────────────────────────
learn.sh validate <topic> [module] # Full gate: schema + naming + content syntax + quality (see B5)
# --max-chars N: user-determined lesson.md WARN threshold (default 12,000)
learn.sh validate-content <topic> [module] # Deprecated alias → validate
A7. Cost, Integration, Trigger
Cost model (DeepSeek V4 Flash)
| Phase | Cost |
|---|---|
| Scope + syllabus | ~$0.01 |
| Per module (~15K tokens out) | ~$0.004 |
| Enrich per module (all 4 types) | ~$0.01 |
| Enrich per type (per module) | ~$0.002-0.004 |
| Full course (20 modules) | ~$0.08 |
| Full course + enrich | ~$0.28 |
| Study session / SRS review | $0 |
Integration
- Anki:
learn.sh export→ CSV/APKG - Obsidian/Notion: Markdown imports directly
- Print: Print lesson.md or quiz.yaml
- EPUB:
learn.sh epub <subject>generates EPUB 3 (hierarchical ToC, syntax highlighting, quizzes, Mermaid via mermaid.ink API default,--localfor offline mmdc). Workflow:epub(assemble + build) →epub-verify;epub-regenrebuilds from cachedbook.md. - PDF:
learn.sh pdf <subject>(stdlib fallback, optional weasyprint/pandoc).pdf-regenfrom cachedbook.md.
Trigger behavior
Enter content creation mode immediately:
- Confirm scope iteratively.
- Write module 1.
- Proceed module by module — never full course in one shot unless asked.
Part B — Schema Level Check
Machine-readable contract. Every artifact validates against learn-something-schema/schemas/ via learn.sh validate <topic>. Structural rules are enforced here, not in prose.
B1. Directory structure
<topic>/
├── syllabus.yaml # Course spec (see B2)
├── cumulative_quiz_XX-YY.yaml # Cross-module quizzes (after every 3-4 modules, span ≤4)
├── modules/
│ ├── NN-name/ # Dir name must match NN-kebab-case
│ │ ├── lesson.md # Core content + exercises (soft size guide, WARN only; time budget is the cap)
│ │ ├── quiz.yaml # 8-10 MCQs
│ │ └── cloze.yaml # 8-10 cloze questions
│ └── ...
└── srs/
├── deck.json # FSRS-6 cards {cards: {id: card}}
└── stats.json # History
B2. Canonical shapes
| File | Shape |
|---|---|
syllabus.yaml |
Top-level map: subject, language, time_budget_hours, target_level, modules (array of {id: int, name, time_hours ≤1.5, prerequisites: [int], topics: [str]}) |
quiz.yaml |
Top-level array (NOT wrapped in questions:). Each item: {id: "N.M", question, options: {a..d}, answer: a|b|c|d (lowercase), explanation, difficulty: 1-3, tags: []} |
cloze.yaml |
Top-level array. Each item: {id: "c.N", question, answer, explanation, difficulty, tags}. Answer auto-extractable from {...} braces |
cumulative_quiz_XX-YY.yaml |
Top-level array. Each item: {id: "cum.N", type: mcq|cloze|tf, source_modules: [int], question OR statement, options, answer, difficulty, tags} |
srs/deck.json |
{cards: {id: card}} map format, camelCase fields |
Field conventions (shared across all shapes)
- Difficulty:
1=recall, 2=comprehension, 3=application - Tags vocabulary: quiz →
terminology, concept, formula, scenario, comparison, calculation; cloze →terminology, concept, formula, process, comparison; cumulative →cross-module, recall, synthesis, misconception - Options: exactly 4 (
a,b,c,d), exactly 1 correct. Lowercase keys + answers are canonical. - Question-per-objective: at least 1 quiz question per syllabus learning objective.
- Difficulty mix: ~40% difficulty 1, 40% difficulty 2, 20% difficulty 3 per file.
- Answer distribution: no more than 2 consecutive questions share same answer letter; overall spread balanced across a/b/c/d (MCQ: roughly 1 each per 4).
- Cumulative mix: 4 MCQ + 3 cloze + 3 T/F = 10 items.
B3. Naming conventions
- Module dirs:
NN-kebab-case(zero-padded two-digit + kebab), e.g.01-intro. - Cumulative quizzes:
cumulative_quiz_XX-YY.yaml(zero-padded range, max span 4 modules,source_modulesmust stay insideXX-YY). Singlecumulative_quiz.yamlrejected. - IDs: quiz
N.M, clozec.N, cumulativecum.N. - JSON fields: camelCase. YAML keys: lowercase a-d.
B4. YAML gotchas (breaks parsing — always double-quote these)
@at start of a plain scalar (e.g.@ConfigurationProperties) — illegal in YAML plain scalars": "inside an unquoted list/map scalar (e.g.- Total: 3) — parsed as nested mapping{...}braces inside double-quoted scalars must be literal (no\{escapes)- Quiz/cloze/cumulative files must be top-level arrays —
questions:wrapper breaks CLI + validation
Auto-fix most of these + normalize shapes with python3 scripts/migrate_courses.py [topic] --apply (idempotent; run from subject dir).
B5. Validation (quality gate)
learn.sh validate <topic> [module] = single full gate. Four passes, all report-only (never writes to disk; legacy courses stay as-is but fail until regenerated):
| Pass | Checks | Severity |
|---|---|---|
| 1. Schema | quiz/cloze/cumulative/syllabus/deck/feedback vs JSON Schema; module dir naming; junk files | ERR |
| 2. Content syntax | markdown (pymarkdownlnt or basic) + mermaid via mmdc (required — no fallback; install npm i -g @mermaid-js/mermaid-cli) on lesson.md |
ERR |
| 3. Quality | Rule 15 ERR: mindmap present. Rule 16 WARN only: lesson.md char count > --max-chars (default 12,000) — time budget (time_hours ≤1.5h) is the real size cap. Rule 17 WARN only: H1 must be # Module NN: <name> — plain number, no dir slug |
ERR for 15; WARN for 16, 17 |
| 4. Quality (statistical) | Rules 1-14 signals: Think/Cloze/Predict/Spot-the-Mistake blocks, mermaid diagrams, difficulty mix, answer rotation/spread, item counts, Q-per-LO, cumulative coverage + type mix | ERR |
All checks are ERR — exit code 1 if any fails. No WARN tier, except rule 16 (lesson.md char count) and rule 17 (H1 title slug) which are WARN-only and never affect exit code. Schema sources: learn-something-schema/ (JSON Schema files, TypeScript types, python validator). validate-content = deprecated alias for backward compatibility.
Generated decks may carry ISO date-times (Reader-rewritten) or date-only (CLI-written) — both accepted by schema.