Lesson builder
Generates bilingual TEFL/ESL courses with lessons, exams, flashcards, conversations, pronunciation drills, worksheets, and syllabi for any language pair.From its SKILL.md
npx -y skills add ddtraveller/lesson-builderAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
36.5 KB, ~8.9k tokens by cl100k_base, as published. Nobody here has run it
When to use
Use this skill when:
- The user asks to create a lesson, course, syllabus, or curriculum
- The user asks to generate activities, exams, flashcards, or other course pages
- The user says "build lesson", "create course", "generate syllabus", etc.
Step 0: Check Backend Availability (ALWAYS RUN FIRST)
On every invocation, before asking any questions, run the shared backend-availability script and display its output to the user:
python scripts/backend_probes.py
This probes every backend the skill supports (node, tavily, notebooklm, flux, heygen, remotion, capcut, webm) and prints a status line per backend plus an "N/M backends available" summary. The script is the single source of truth for auth and availability checks — do not duplicate its logic in prose.
Downstream behavior based on the report:
- Research: if tavily+notebooklm both
OK→ Tavily→NotebookLM bridge; if only notebooklmOK→ plain NotebookLMsource add-research; if only tavilyOK→ Tavily standalone; if neither → Web Search fallback. - Images: if flux
SKIP→images.backendfalls back tooffregardless of config. - Video: each chosen
video.backendmust reportOK; otherwise warn and fall back tooff(the availability-check contract is in §Interactive Mode below).
If any backend reports SKIP and the user wants to enable it, see:
- NotebookLM auth troubleshooting → templates/notebooklm/query_patterns.md
- Tavily login / install → run
tvly login --api-key ...orcurl -fsSL https://cli.tavily.com/install.sh | bash - Replicate token → add
REPLICATE_API_TOKEN=r8_...to.env
Then proceed to interactive mode.
Interactive Mode — Ask Before Building
If the user invokes this skill without specifying all parameters, ASK:
-
Topic/Subject — "What topic should the course cover?" (e.g., "General English for beginners", "Business English", "English through IT skills")
-
Languages — "What is the learner's first language (L1) and target language (L2)?" (default: Thai → English)
-
Number of weeks/units — "How many units?" (default: 12)
-
Age group — "Who is this course for?"
- Children (4-7) — Audio-first, big visuals, tap/drag interactions, no reading required
- Older children (8-12) — Can read simple text, handle basic game rules
- Adults — Full text-based lessons with grammar explanations
-
Page types — "Which page types per unit?" Show options based on age group:
Adult page types:
- Lesson, Activities, Exam, Flashcards, Conversation, Pronunciation, Worksheet, Syllabus
Children's page types: See templates/children_pages/README.md for full list and specifications (Story, Game, Song, Coloring, Stickers, Flashcards, Reward, Avatar Video for ages 4-7; Comic, Quiz Show, Word Puzzle, Adventure, Journal, Video Lesson, Board Game, Reading for ages 8-12)
-
Output location — "Where should files go?" (default:
HTML/courses/{topic_slug}/) -
Color theme — "Any color preference?" (or auto-pick)
Backend choices group (Q8–Q10) — ask contiguously after Q7. Before showing each prompt, call the matching probe from scripts/backend_probes.py and display the availability result inline. If the operator picks a backend that the probe reports as unavailable, show a visible warning and record off — never silently accept an unavailable backend. The config file always reflects what was actually confirmed available.
-
Content-truth validation backend — probe availability first, then ask:
Content-truth validation backend? [tavily / notebooklm / off](default: off — no quota spend)tavily: cross-checks vocab cards, grammar boxes, and exam answers via Tavily Research CLI.notebooklm: queries the Phase-2 research notebook (ID stored in research.md).off: skip content-truth validation entirely — no quota consumed.- Records to
content_truth.backendin the course config.
-
Image generation backend — probe availability first, then ask:
Image generation backend? [flux / off](default: off)flux: generate images via Replicate (FLUX Dev model). RequiresREPLICATE_API_TOKENin env or.env. (Note: "FLUX" is the correct name — an earlier iteration of the spec used "FLEX" which was a typo.)off: no image generation.- Records to
images.backendin the course config.
-
Video generation backend — probe availability first, then ask:
Video generation backend? [heygen / remotion / capcut / webm / off](default: off)heygen: AI avatar video via HeyGen API. Costs approximately $2 per lesson video on the current plan — choose knowingly. Requires theheygenSSM parameter (AWS deploy profile).remotion: React-based video generation via thewatdonchan/ai-english-videoRemotion project. Free, requires Node + the project directory.capcut: prompt-to-human workflow — operator assembles video manually in CapCut. Always available; no runtime requirement.webm: NOT YET IMPLEMENTED — probe returns unavailable; will fall back tooffautomatically.off: no video generation.- Records to
video.backendin the course config. - Follow-up if not
off:Video max count (integer cap to prevent surprise spend)?→ records tovideo.max_count.
Course-creation backend choices — availability-check contract
Before each Q8/9/10 prompt, run the corresponding probe and show the result inline:
| Backend | Probe | On unavailable |
|---|---|---|
content_truth: tavily | probe_tavily() — tvly auth --json | warn, fall back to off |
content_truth: notebooklm | probe_notebooklm() — python -m notebooklm auth check --test --json | warn, fall back to off |
images: flux | probe_flux() — check REPLICATE_API_TOKEN in env / .env | warn, fall back to off |
video: heygen | probe_heygen() — aws ssm get-parameter --name heygen --profile deploy | warn, fall back to off |
video: remotion | probe_remotion() — check watdonchan/ai-english-video/node_modules/remotion/ | warn, fall back to off |
video: capcut | probe_capcut() — always available | n/a |
video: webm | probe_webm() — NOT YET IMPLEMENTED | warn "not yet implemented", fall back to off |
Config never records an unavailable backend. If the operator insists on a backend the probe says is unavailable, the config records off and the warning is shown again. The operator must fix the environment (install the tool, add the credential) and re-run the skill to unlock that backend.
video.max_count enforcement. When video.backend != off, the generator MUST check the count of videos generated so far against video.max_count before each video call. If the count would be exceeded, halt immediately with a readable error such as:
ERROR: video.max_count=3 reached — refusing to generate video for unit 4.
To generate more videos, increase max_count in config/<course_id>.json.
Silent truncation (skipping videos without error) is not acceptable. If video.max_count is 0 or absent and video.backend != off, treat it as "no limit" — but interactive mode MUST prompt the operator to set an explicit count when they choose a non-off backend (see Q10 follow-up above).
Research method is auto-selected based on Step 0 results:
- If Tavily Research + NotebookLM both ready → use Tavily → NotebookLM bridge (best quality)
- If only NotebookLM ready → use plain NotebookLM
source add-research - If only Tavily Research ready → use Tavily standalone (writes report directly to research.md)
- If neither → use Web Search
- User can override to "training data only" if they want speed over accuracy
If a config file exists in config/ matching the topic, load it instead of asking. Still respect the auth results from Step 0 and probe results for Q8–Q10 (e.g., if config has content_truth.backend: tavily but the probe fails, warn and proceed with off for this run).
How it works — Buddy Workflow Integration
The lesson builder uses a structured spec → plan → tasks → implement pipeline, delegated to the buddy:* skill suite when available. This ensures each course is well-designed before code generation begins.
Templates and worked example:
templates/buddy/{spec,plan,tasks,research}.md— placeholder skeletons to copy for a new coursespecs/EXAMPLE-tefl_children_10_12/— complete worked example (Thai children ages 10-12, 12 units, 84 files) showing what each artifact looks like filled in. Read this before starting a new course to understand the proven shape and detail level.
Buddy-availability detection (runs once at Phase 1 start)
Before delegating, check whether buddy:spec, buddy:plan, buddy:tasks, and buddy:implement appear in the current session's available skills list (the same list visible in the system-reminder). This is a self-awareness check — the assistant scans its own skill list. No shell which call; buddy:* are harness-registered skills, not PATH binaries. If all four are present, set BUDDY_AVAILABLE=1 and use the delegation path below. If any are absent, set BUDDY_AVAILABLE=0 and use the fallback at §Appendix: Minimal Inline Fallback.
Phase 1: Specification
Delegate to buddy:spec with TEFL-specific overlays.
Pass inputs from Step 0 + interactive Q&A (topic, L1/L2, weeks, age group, page types, research method, backend choices). TEFL overlays to include: vocabulary density target (10-12 words/unit adults; 6-8 children); bilingual markup rules (§Bilingual Content Rules); page-type catalog for chosen age group (§Adult Page Type Specifications or templates/children_pages/); theme palette. Spec lands at specs/{YYYYMMDD}-{course_id}/spec.md. Reference: specs/EXAMPLE-tefl_children_10_12/spec.md.
Fallback (buddy:spec not available): see §Appendix: Minimal Inline Fallback.
Phase 2: Research & Planning
Delegate to buddy:plan with TEFL-specific research step.
Run the TEFL-specific corpus loading first (buddy can't do this): use the research method from Step 0 (Tavily→NotebookLM bridge, NotebookLM only, Tavily standalone, or web search). Save findings to specs/{YYYYMMDD}-{course_id}/research.md. Then invoke buddy:plan with the research.md content as context. Windows note: always prefix notebooklm/tvly commands with PYTHONIOENCODING=utf-8. Plan lands at specs/{YYYYMMDD}-{course_id}/plan.md.
Querying the corpus
Phrase NotebookLM queries as "What does [source] say about X?" — the source-attributed form forces the model to draw from uploaded material rather than general knowledge. Example: "What does the Tavily TEFL report say about vocabulary density for A1 learners?" Always prefix with PYTHONIOENCODING=utf-8 on Windows to avoid cp1252 crashes. For more query patterns and auth troubleshooting, see templates/notebooklm/query_patterns.md.
Fallback (buddy:plan not available): see §Appendix: Minimal Inline Fallback.
Phase 3: Task Breakdown
Delegate to buddy:tasks. Pass spec + plan as context. TEFL constraints: include a Unit 1 gate task; include check_links.py, check_exams.py, check_pages.py invocations in Phase 5 tasks; keep tasks terse. Tasks land at specs/{YYYYMMDD}-{course_id}/tasks.md.
Fallback (buddy:tasks not available): see §Appendix: Minimal Inline Fallback.
Phase 4: Implementation
Delegate to buddy:implement. Pass the full specs/{YYYYMMDD}-{course_id}/ directory as context.
TEFL-specific constraint: pause at Unit 1 gate. buddy:implement must stop after generating Unit 1, report that Unit 1 is ready for browser verification, and wait for operator confirmation before proceeding to bulk generation. This is the single most important structural rule — do not let buddy:implement auto-proceed past Unit 1.
Fallback (buddy:implement not available): see §Appendix: Minimal Inline Fallback.
Phase 5: Post-Generation Verification (REQUIRED)
Before declaring the course complete, run two automated checks and one semantic review. These catch the two classes of bugs that have shipped to users and been reported back as complaints: broken links and unanswerable exam questions.
5a. Link check (always run)
python scripts/check_links.py {output_dir} --html-root HTML
The script walks every .html file in the course directory and reports:
- BROKEN — a
hreforsrcpoints at a local file that doesn't exist on disk - DEPTH — a media ref like
../imgs/foo.pnguses the wrong number of..for the page's depth (so it deploys correctly locally but 404s on S3 — this is the bug we hit on the English Quest teens_13_14 course) - YouTube embeds — list of video IDs for the user to spot-check
- External URLs — list of external refs for the user to review (optional
--check-externalflag HEADs each one)
Fix every BROKEN and DEPTH finding by editing the generator (not the HTML — the HTML is a build output), then regenerate the affected pages. Don't proceed until the script exits 0.
5b. Exam answerability check (if course has exam pages)
Exam pages ship with embedded question banks. The common complaints from learners are:
- "The question is incomplete" — e.g. the vocabulary word got stripped and the question reads
What is the meaning of ""? - "None of the answers are right" — the distractors are fine but the correct option was swapped with a near-miss
- "Two answers look correct" — the distractors aren't actually distinct from the correct answer
Step 1 — run the structural check:
python scripts/check_exams.py {output_dir} --review-out {output_dir}/_exam_review.json
This catches: empty/placeholder question text, fewer than 2 real options, duplicate options, correct index out of range, explanation that quotes a string no option matches. Fix any structural issues in the generator and regenerate.
Step 2 — semantic review (mandatory). The script writes _exam_review.json containing every question with its options, correct index, and explanation. You (the skill) MUST read this file and for each question verify:
- Is the question complete? A question like
What is the meaning of ""?orChoose the correct form of tois incomplete — a word was stripped during generation. - Does
options[correct_index]actually answer the question? E.g. if the question is "What is the past tense of 'go'?" andoptions[correct]is"going", that is wrong. - Are any other options also correct? A question like "Which is a greeting?" with options
["hello", "hi", "bye", "goodbye"]has two right answers — unanswerable as written. - Are the distractors plausible but clearly wrong? If the distractors are nonsense strings or identical in meaning to the correct one, the question is either trivial or broken.
Report findings back in this format:
Exam review: N questions across M exam files
Issues:
begin_greetings_exam.html / vocabulary/easy #3
Q: "What is the meaning of \"\"?"
→ question is truncated (empty quoted word)
→ fix generator: vocab loop lost the word variable
begin_food_exam.html / grammar/medium #7
Q: "Which is the correct plural of 'fish'?"
Options: ["fish", "fishes", "fishies", "none of these"]
correct=0 ("fish")
→ "fishes" is also accepted as plural in several contexts (different species);
rewrite the distractor or add specificity to the question
Then fix the generator and regenerate the affected exam pages. Re-run both checks until clean.
5c. Content-truth validation (opt-in, controlled by content_truth.backend in config)
python scripts/check_content.py {output_dir} config/{course_id}.json
The first line of output always declares the active backend so the operator is never unaware of quota spend:
content-truth backend: off→ exits 0 immediately; printsPhase 5c skipped by config — no quota consumed.content-truth backend: tavily→ runs sampling + Tavily Research queries.content-truth backend: notebooklm→ runs sampling + NotebookLM notebook queries.
Sampling rates (seeded per course_id for reproducibility):
- 20% of vocab cards per unit
- 10% of grammar boxes per unit
- 100% of exam correct-answer entries
Exit codes and gate behavior:
backend: off→ exit 0; never blocks shipment.backend: tavily|notebooklm+ exit 0 → proceed to ship.backend: tavily|notebooklm+ exit 1 → SHIPPING BLOCKED. Operator must resolve each flag (fix content / mark false-positive with justification / explicit override) before shipping. Grep_content_truth_report.mdforUNRESOLVEDto enumerate blockers.
Reports written to {output_dir}/_content_truth_report.json and {output_dir}/_content_truth_report.md.
When to skip: 5c is always opt-in. If content_truth.backend: off (the default), the check never runs and never blocks. No course is required to run content-truth validation — but if it is enabled, unresolved flags block shipment.
5d. When to skip 5b
- Skip 5b if
page_structure.examis"none"for this course. - Never skip 5a. Every course has links; every course needs the check.
Operational: Patching a Single Page or Question
For a single learner-reported bug, use scripts/regenerate.py instead of re-running the full generator. This avoids touching the other 80+ files in the course.
# Page-level: regenerate unit 7's quiz page only
python scripts/regenerate.py --course-config config/children_10_12.json --unit 7 --page-type quiz
# Page-level: regenerate unit 3's story page
python scripts/regenerate.py --course-config config/children_10_12.json --unit 3 --page-type story
# Question-level: replace just question 4 (0-based) in unit 7's quiz
python scripts/regenerate.py --course-config config/children_10_12.json --unit 7 --exam-question vocabulary:medium:4
# Determinism check: verify regenerated output matches full-generator for that page
python scripts/regenerate.py --course-config config/children_10_12.json --unit 7 --page-type quiz --determinism-check
Supported courses: children_10_12, teens_13_14, tefl_beginners, tefl_intermediate. Each run appends a JSON event to {output_dir}/_regenerate_log.jsonl for audit.
Stepping Through vs. Auto-Run
By default, pause after Phase 1 (spec) to confirm with the user before proceeding. The user can say:
- "Looks good, continue" → proceed through phases 2-4 automatically
- "Change X" → update spec and re-confirm
- "Just generate" → skip remaining phases, go straight to generation (legacy mode)
Legacy Mode (Direct Generation)
If the user says "just generate" or "skip planning", bypass the buddy workflow and generate directly:
- Gather requirements (interactive or config)
- Research (NotebookLM/web search/training data)
- Fact-check
- Write Python generator script (
generate_{course_id}.py) and run it - Images (if Replicate available)
- Hero video (optional — ask user)
This preserves backward compatibility for quick one-off generation.
Configuration System
Courses are defined by JSON config files in config/. See config/schema.md for the full specification.
Key config fields
Top-level keys (per config/schema.md — canonical):
course_id,l1,l2,output_dir,theme— course identitypage_types— which page types each unit gets (see assignment modes below)page_structure— sections/options per page typeresearch—tavily,notebooklm,web_search,fact_checkflagscontent_truth.backend—tavily | notebooklm | off(Phase 5c validator)images.backend—flux | offvideo.backend—heygen | remotion | capcut | webm | offplusvideo.max_countcapunits— the actual course content
For full field definitions, defaults, and examples, see config/schema.md and the worked examples under config/.
Page type assignment modes
| Mode | Example | Behavior |
|---|---|---|
"every" | "lesson": "every" | Generate for all units |
"none" | "exam": "none" | Never generate |
"random:N" | "flashcards": "random:3" | Randomly assign to N units |
"units:1,5,9" | "pronunciation": "units:6,9,12" | Only specific units |
| Override per unit | "page_types_override": {"conversation": true} | Force on/off for one unit |
Adult Page Type Specifications
All pages use inline CSS (no external dependencies except Google Fonts). All content is bilingual (L1 first, L2 second).
Lesson Page
7 sections: Introduction (bilingual box + fun facts), Vocabulary (10-12 flip cards with audio), Grammar Focus (rules + examples in both languages), Tutorial Steps (guided practice), Hands-On Activity, Reference Table, Summary. Includes section-check quizzes (configurable count), homework section, and references.
Activities Page
5-6 activity cards with difficulty badges (Easy/Medium/Hard). Types: fill-in-the-blank, matching, labeling, role-play, writing, troubleshooting. Mark-as-complete buttons.
Exam Page
Interactive exam with difficulty selector (Easy/Medium/Hard controlling question count). Question bank with configurable categories. Full exam engine JS with score calculation, answer review, and optional gradebook integration (localStorage).
Flashcard Page
Interactive flip cards. Front: L2 term + pronunciation button. Back: L1 translation + definition + example sentence. Controls: shuffle, progress counter, prev/next, keyboard navigation (Space/arrows).
Conversation Page
Situation setup (bilingual), dialogue with speech bubbles and speaker labels, audio buttons per line, key phrases box, comprehension questions, role-play prompts.
Pronunciation Page
Target sound description (bilingual), L1 interference callout, minimal pairs with audio, listen-and-repeat with slow mode (0.6x), tongue twisters, practice sentences.
Worksheet Page
Print-optimized (@media print). Fill-in-the-blank, matching, unscramble, writing prompts with dotted lines. Hidden answer key with toggle button.
Syllabus Page
Course overview with sticky header, week navigation, hero section, scope table, week cards (objectives + vocab + grammar pattern), assessment rubric, outcomes, and References & Research Basis section. Uses different font stack (DM Sans/Noto Sans Thai/DM Serif Display).
References & Research Basis section (REQUIRED):
Every syllabus MUST include a "References & Research Basis" section that documents the evidence base behind the course design. This serves three purposes:
- Transparency for learners and parents — they can see this isn't a random course, it's grounded in peer-reviewed research
- Provenance for teachers and reviewers — they can trace pedagogical decisions back to their sources
- Defensibility if challenged — academic-grade citation chain protects the course content
The section must include:
- Research files used — list every Tavily Research file in
specs/{date}-{course_id}/tavily/(filename + brief topic + source count) - Total cited sources — aggregate count across all research files (e.g., "94 peer-reviewed sources")
- Pedagogical principles list — bullet list of every evidence-based principle baked into the course design, each citing the underlying research file
- External corpus references if used (TLE, ICNALE, CEFR Companion Volume, etc.)
- Citation footer note pointing to where the original research artifacts live (e.g., "Full research reports available in
specs/{date}-{course_id}/tavily/") - NotebookLM notebook ID if a per-unit Q&A grounding pass was run in Phase 2 (allows future re-querying against the same corpus)
- Optional: links to external research databases (Cambridge ReCALL, MDPI, NCBI/PMC, ScienceDirect, Springer, ERIC, Council of Europe CEFR portal)
The section should be visually distinct (use a .references-section class) and placed near the bottom of the syllabus, after assessment rubric and outcomes but before the footer. Use a slightly muted background (e.g., --primary-pale from the course theme) so it reads as supplementary metadata rather than primary content.
When a syllabus includes both a hero video and hero image:
- Video goes first (inside
.hero-videodiv, noposterattribute) - Image goes below as a separate element (inside
.hero-imgdiv) - They must NOT overlap — use
margin-top: 20pxon the image container for spacing
Nav Footer (All Pages)
Every page in a unit MUST include a .nav-footer with links to ALL page types generated for that unit, not just a subset. The generator script must track which page types each unit has and inject the complete nav footer into every page for that unit.
Quality Checklist — Run Before Delivery
1. Test one unit end-to-end first
Generate only unit 1 first. Open every page in a browser and verify:
- All interactive features work (games load, songs play, stickers place)
- No JS console errors
- Nav footer links to all pages in the unit
- Story emojis vary per page
- TTS speaks correctly on all buttons
Only after unit 1 passes, generate remaining units using the same templates.
2. Cross-file consistency
- Every function called in JS is defined in the same
<script>block — pages are standalone HTML with no shared JS files - Nav footer on every page lists ALL page types for that unit
- No hardcoded page-type subsets — always derive from the unit's actual page list
3. Content-specific visuals
- Story page emojis: each of the 6 pages shows a DIFFERENT emoji matching its vocabulary word
- Sticker page: uses themed concrete objects (animals, food, etc.), not abstract shapes
- Emoji colors match the text: if the label says "blue bird", the emoji must naturally render as blue. See
templates/images/README.mdfor emoji color reference tables (circles, squares, hearts, color-accurate animals, common pitfalls) - Song page: embeds a real YouTube children's song (verify video ID is valid)
Image prompt conventions
When generating course images (coloring pages, story illustrations, game assets):
- Style prefix for coloring pages: copy from
templates/images/prompt_style_prefix.txt— enforces thick B&W outlines, no shading, white background. Edit in isolation without touching generator code. - Ethnicity in prompt: for Thai courses, include "Southeast Asian Thai ethnicity with warm brown skin, straight black hair" in the style prefix when generating images of people.
- Emoji color rule: CSS
colordoes NOT recolor emojis — they have built-in colors. Always choose a Unicode codepoint whose native rendering matches the intended color. Verify in a browser before bulk-generating. - Path convention: images go in
imgs/tefl/{course_slug}/(sibling toHTML/, never inside it). HTMLsrcuses relative depth-correct paths. Theimgs/dir is gitignored — upload to S3 separately. - Full generate_images.py usage, aspect-ratio table, and Unicode color tables:
templates/images/README.md.
4. JS quality checks
Rules 2.1–2.5 and 2.7 are mechanically enforced by scripts/check_pages.py (run in Phase 5). One operational concern that remains:
localStorage quota
Each page-type's localStorage keys should have a per-key size budget. For a 12-unit course with chat history + quiz scores + project drafts + vocab tracking, total budget should stay under 1 MB. If quota is hit, fall back to clearing oldest entries first.
Bilingual Content Rules
- L1 text appears first (left column in bilingual boxes)
- L2 text appears second (right column)
- L1 orange background (
#fff3e0with#FF9800border) - L2 blue background (
#e3f2fdwith#2196F3border) - All grammar explanations in both languages
- All quiz questions have L1 translation
- All vocab cards have L1 translation and bilingual definition
- Homework tasks must be bilingual
CSS Theme Variables
Each course defines a color theme in its config. Lesson pages use:
:root {
--primary: {from config};
--primary-dark: {from config};
--primary-light: {from config};
--primary-pale: {from config};
--accent: {from config};
}
Fonts: 'Sarabun','Nunito',sans-serif for lesson/activity/exam pages.
Fonts: 'DM Sans','Noto Sans Thai',sans-serif for syllabus pages.
JS Components
speak() — Text-to-Speech
function speak(text) {
window.speechSynthesis.cancel();
const u = new SpeechSynthesisUtterance(text);
u.rate = 0.9; u.lang = '{l2 locale}';
window.speechSynthesis.speak(u);
}
scAnswer() — Section Check Engine
Interactive quiz engine with score tracking. See config/schema.md for ID conventions.
Exam Engine
Difficulty selector → question rendering → answer checking → score display → gradebook save.
Gradebook: wdc_saveScore(examKey, percentage, grade) saves to localStorage.
Generator Script Pattern
For bulk generation, write a Python script rather than individual files:
import os, json
# Load config
with open('config/my_course.json', 'r', encoding='utf-8') as f:
config = json.load(f)
OUT_DIR = config['output_dir']
os.makedirs(OUT_DIR, exist_ok=True)
def generate_lesson(unit, config): ...
def generate_activities(unit, config): ...
def generate_exam(unit, config): ...
def generate_flashcards(unit, config): ...
# ... one function per page type
for unit in config['units']:
for page_type, gen_fn in page_generators.items():
if should_generate(page_type, unit, config):
path = os.path.join(OUT_DIR, f"{unit['prefix']}_{page_type}.html")
with open(path, 'w', encoding='utf-8') as f:
f.write(gen_fn(unit, config))
Critical implementation notes
- Always
encoding='utf-8'for all file operations (non-ASCII text breaks on Windows default cp1252) - Keep scripts under ~3000 lines — split into parts if larger
- Sequential generation only — never launch multiple heavy agents in parallel (they exhaust token budget and all fail)
- One course at a time — verify output before starting the next
Relative path depth
The number of .. segments in a relative path must equal the page's depth from HTML/ root — enforced by scripts/check_links.py (depth check) and scripts/check_pages.py (rule 2.6 src existence). Make html_relative_path configurable in the course config, not hardcoded in the generator.
Unit 1 verification gate — what to actually verify
The Unit 1 gate is load-bearing. Run scripts/check_pages.py, scripts/check_links.py, and scripts/check_exams.py first; they catch mechanical issues automatically. Then do the human-only checks:
- Open DevTools Console. ANY red error or warning is a fail. Most subtle bugs surface here.
- Click every interactive element on every page — every vocab card flip, every quiz answer button, every chat scenario chip, every "Next" button, every "Save to Portfolio" button. Watch the console while doing it.
- Refresh the page after entering quiz/project/chat data. Verify
localStoragepersistence — data should still be there. - Test mobile breakpoints — DevTools responsive mode at 768px and 480px. Check for layout breakage, button overlap, text overflow.
- For chatbot pages: send a real test message and verify the response renders + speaks correctly. This proves the Lambda endpoint is reachable.
If ANY of these checks fail, fix the generator (not the HTML — the HTML is a build output) and re-run generation before re-checking.
FLUX safety filter false positives
This came up on English Quest where quest_health_fitness_vocab_sleep.png was rejected as NSFW. FLUX Dev's safety filter can flag prompts that combine common words in unexpected ways, especially:
- "teen" or "child" + bedroom/sleep/bed words
- "teen" + bath/shower/changing words
- "teen" + medical/body words
Mitigation: when a vocab item triggers a content filter false positive, retry with a more constrained prompt that explicitly anchors the context. Example for "sleep":
- ❌ Triggers: "A Thai teenager sleeping"
- ✓ Works: "A Thai teenager studying late at a desk, then yawning, with a clock showing it's bedtime"
- ✓ Works: "A clean educational illustration showing the abstract concept of rest — a closed book, a moon icon, an alarm clock"
The image generator script should support per-vocab prompt overrides via the config so the rare problematic items can be re-prompted without changing the global style prefix.
Example Configs
config/tefl_beginners.json— 12-week Thai→English beginner course (everyday + work), web search researchconfig/tefl_intermediate.json— 12-week Thai→English intermediate course (career growth), NotebookLM researchconfig/schema.md— Full configuration schema documentation
Extending
To add a new page type:
- Add it to
page_typesin the config schema - Add its structure options to
page_structure - Write a
generate_{type}(unit, config)function in the generator script - Document it in this skill file
Appendix: Minimal Inline Fallback (no buddy:* installed)
WARNING — This is a safety net, not a second canonical path. Do not grow it. If buddy:* becomes unavailable for a real user, fix the harness installation, not this fallback. Never add workflow logic here that duplicates or competes with what buddy:spec / buddy:plan / buddy:tasks / buddy:implement do.
Use this fallback only when BUDDY_AVAILABLE=0 (buddy:* skills not in the available-skills list).
4-step barest pipeline:
-
Create the course folder:
mkdir -p specs/{YYYYMMDD}-{course_id}/ -
Copy templates in:
cp templates/buddy/spec.md specs/{YYYYMMDD}-{course_id}/spec.md cp templates/buddy/plan.md specs/{YYYYMMDD}-{course_id}/plan.md cp templates/buddy/tasks.md specs/{YYYYMMDD}-{course_id}/tasks.md cp templates/buddy/research.md specs/{YYYYMMDD}-{course_id}/research.md -
Fill in the obvious fields in each template (course_id, topic, L1/L2, weeks, age group, page types, output_dir, theme). Use
specs/EXAMPLE-tefl_children_10_12/as the reference shape for content and detail level. -
Stop and hand off to the operator — present the filled-in spec.md for review before proceeding to any generation. The operator must confirm the spec before the course is built. This fallback does not implement the full buddy workflow; it only produces the file scaffold.
What ships with it: 55 files
473.7 KB alongside SKILL.md, 9 of them executable
HTML/
- privacy.html5.4 KB
config/
- children_10_12.json1.0 KB
- coloring_prompts_example.json7.9 KB
- schema.md10.4 KB
- tefl_beginners.json17.3 KB
- tefl_intermediate.json21.7 KB
scripts/
- backend_probes.pyruns9.7 KB
- check_content.pyruns17.4 KB
- check_exams.pyruns10.3 KB
- check_links.pyruns8.1 KB
- check_pages.pyruns22.9 KB
- fixtures/check_pages/broken/rule_2_1_broken.html265 B
- fixtures/check_pages/broken/rule_2_2_broken.html394 B
- fixtures/check_pages/broken/rule_2_3_broken.html344 B
- fixtures/check_pages/broken/rule_2_4_broken.html358 B
- fixtures/check_pages/broken/rule_2_5_broken.html289 B
- fixtures/check_pages/broken/rule_2_6_broken.html309 B
- fixtures/check_pages/broken/rule_2_7_broken.html354 B
- fixtures/check_pages/pass/rule_2_1_pass.html276 B
- fixtures/check_pages/pass/rule_2_2_pass.html398 B
- fixtures/check_pages/pass/rule_2_3_pass.html311 B
- fixtures/check_pages/pass/rule_2_4_pass.html393 B
- fixtures/check_pages/pass/rule_2_5_pass.html362 B
- fixtures/check_pages/pass/rule_2_6_pass.html393 B
- fixtures/check_pages/pass/rule_2_7_pass.html390 B
- regenerate.pyruns27.8 KB
- test_check_pages.pyruns4.1 KB
spec/
specs/
- generate_images.pyruns18.2 KB
- .gitignore43 B
- LICENSE1.0 KB
- notebooklm_login.pyruns7.3 KB
- README.md19.0 KB
15 more files not listed here. See all 55 in the repository.
Gives 0 of the 12 instructions most learn study skills give in ~8.9k tokens
Counted across 546 of the 573 authors here whose files we hold, read 2026-08-07
- Calculate the zone of proximal development before teachingin 25 of 546, across 8 files
- Produce self-contained HTML lessonsin 24 of 546, across 8 files
- Record user preferences in a notes filein 23 of 546, across 5 files
- Maintain a teaching workspace in the current directoryin 21 of 546, across 4 files
- Find high-quality resources before writing lessonsin 19 of 546, across 5 files
- Make lessons beautiful, short, and quickly completablein 19 of 546, across 3 files
- Create reusable components for lessonsin 19 of 546, across 5 files
- Create compressed reference documents for quick lookupin 19 of 546, across 3 files
- Update the mission file and records upon mission changesin 16 of 546, across 2 files
- Set min_dist to 0.0 for clustering preprocessingin 16 of 546, across 6 files
- Populate the mission file before teachingin 15 of 546, across 1 file
- Include interactive feedback loops in lessonsin 15 of 546, across 1 file
Said here and by no other author read
- run backend probes on every invocation
- display backend probe output to the user
- probe each backend before prompting its choice
- warn and fall back if a backend is unavailable
- load existing config file if it matches the topic
- delegate phases to the buddy suite when available
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.