Coursewright
Run a persistent study pipeline: ingest → cheatsheet → quiz → app → grade → track → schedule. All state lives in courses/<slug>/course.json — read it first, trust it, keep it current.
Where the scripts are ($CW)
The scripts live beside this file, so resolve them relative to this skill's own directory. Both installs are valid; use whichever exists:
CW=$(ls -d ~/.claude/skills/coursewright .claude/skills/coursewright 2>/dev/null | head -1)
Every $CW/scripts/... command below means that path. Courses live under courses/ in the user's
working directory, never inside the skill folder.
Course folder layout
courses/<slug>/
├── course.json # single source of truth: chapters, quizzes, results, mastery, schedule
├── quizzes/*.json # quiz data incl. answer keys (see references/quiz-format.md)
├── learn/*.json # Learn-tab + vocabulary data, derived from the cheatsheet by ingest_learn.py
├── prequiz/*.json # 6–8 open pre-reading questions per chapter (attempted before reading)
├── short/*.json # write-it-out questions with rubrics (production-format retrieval)
├── labs/*.json # one runnable exercise per chapter (the chapter's central mechanism)
├── results/*.json # sync/result files exported from the app (attempts, SRS, sessions, labs)
└── app/index.html # built learning center (generated — never hand-edit)
The cheatsheet may live anywhere; course.json.cheatsheet holds its path relative to the course folder.
Sources are anything: book PDFs, articles, video/movie transcripts, lecture notes, docs. "Chapter" means any content unit — an actual chapter, a transcript segment, an article. The pipeline is identical; only step 1 of ingest (locating/reading the source) differs.
Method grounding: schedules, drills, and feedback follow references/learning-methods.md (retrieval practice, spacing and successive relearning are the high-utility techniques; passive re-reading is never scheduled alone). Read it before designing a schedule or drill plan. The user-facing version is references/how-to-study.md — build_app.py bakes it into every app as the How to study tab plus the daily checklist card on the dashboard, so the method ships with the course. Edit that file when the method changes; a course may append its own sections in courses/<slug>/how-to-study.md (e.g. book-specific labs).
"New course" / studying something new (any content)
The skill is content-agnostic — a book, a wargame, a video series, lecture notes. Scaffold once:
python3 $CW/scripts/new_course.py courses <slug> \
--title "<Title>" --source "<path/URL>" --goals "<what the learner optimizes for>" --units Chapter
--units names the content unit (Chapter, Level, Episode, Module…). This creates the folder layout, a
course.json, and a cheatsheet skeleton. If a cheatsheet already exists elsewhere, pass --cheatsheet <path>
(relative to the course folder or absolute) and it's reused. Then follow the ingest workflow per unit. Units
are matched by any ## heading, so "Level 7"/"Episode 12" work exactly like "Chapter 3"; pass --id <slug>
to ingest_learn.py if the auto-slug (Chapter 6b→ch6b, Level 7→level7) isn't what you want.
State check — always first
ls courses/*/course.json 2>/dev/null # from the project root
- Found → read the relevant
course.jsonbefore doing anything else. It answers "where are we at". - Not found / new course → create the folder layout above and a fresh
course.json(schema inreferences/app-spec.md), then continue with the user's request.
Workflows
"Prequiz me on chapter N" (before reading — pretesting)
Skim the source's headings (or the book's TOC) and ask 5–8 closed questions on the unit's Tier-1 ideas, one at a time, in chat. Expect mostly misses — say so up front. Do not explain answers; just note which the user got and tell them to read for the rest. Pretesting only strengthens what was asked, so aim at what matters most. Record "prequiz": {date, hits, asked} on the chapter in course.json; use the misses as the first drill after ingest.
"I read chapter N" / "here's a transcript" (ingest)
- If the user pastes a brain dump (free recall written right after reading), keep it in
results/dump-ch<N>.mdand, after step 2, compare it with the cheatsheet: list what they recalled, what they missed, what they got wrong. Misses become the first drill; wrong items are confident errors — explain those immediately. - Locate the source (
course.json.source, or the file/text the user hands over). For PDFs: find the unit's pages (pdftotext -f/-l), extract to a scratch file, and read the actual text — never write a cheatsheet or quiz from memory of the material. - Append a chapter section to the cheatsheet, following
references/cheatsheet-format.md. Insert in chapter order, not at the file end. - Derive the Learn + Vocab data:
python3 $CW/scripts/ingest_learn.py courses/<slug> "Chapter <N>"(add--id <slug>for non-"Chapter N" units, or to disambiguate a second part that shares a number —## Chapter 6b …→ch6b). Check the printed counts (sections/cards/hooks/vocab) against the cheatsheet — if a count is 0 or obviously low, the cheatsheet section deviates from the house format; fix the markdown and re-run. - Generate the quiz per
references/quiz-format.md→quizzes/ch<N>.json. Every question gets anexplain. Use atype:"order"question (in a dedicated section) for any pipeline/sequence — they learn badly as MCQ. Validate withpython3 $CW/scripts/validate_quiz.py <file>(tiers, letter spread, option-length parity, contiguity, order items) until it prints OK — a longer key is an answer leak, so write distractors at the key's length and specificity from the start rather than fixing them after the gate fires. - Write the prequiz →
prequiz/ch<N>.json(6–8 open questions on the Tier-1 ideas, for use before reading). 5b. Write write-it-out items →short/ch<N>.json:{id, chapter, title, questions:[{n, tier, concept, q, rubric:[3–5 points the answer must cover], explain}]}. Six to ten per chapter, on the ideas the learner will have to use, not just recognise. MCQ and short answer are equivalent for retention but production is what transfers (Pan & Rickard 2018), so these get reserved slots in every daily session. - Update
course.json: chapter status, quiz entry (status: "ready"). - Propose the chapter's lab — one small runnable exercise for the unit's main mechanism (30 lines of code or a deliberate break-and-fix). Give the goal and the check, not the solution; scaffold worked → faded → solo across chapters. Store as
"lab": {title, goal, check, status}on the chapter. - Rebuild the app (below) and tell the user the quiz is ready and to take it within 48 h.
Build / rebuild the app
python3 $CW/scripts/build_app.py courses/<slug>
Validates every quiz file and injects all course data into assets/quiz-app-template.html. Fails loudly on schema problems — fix the quiz JSON, never the generated HTML. After building, verify it reports OK and the byte size is nonzero.
After changing the template's question-selection logic, rebuild and then run
node $CW/scripts/test_gate.js <course-dir> (12 assertions on the Today gate: new and due
items only come from unlocked quizzes — ones with a recorded result, an unsynced local submission, or
status: "taken" — locked chapters stay parked with their SRS state, the per-quiz dashboard override
works both ways, and a fresh course with nothing taken still serves its first quiz).
Record results (grade)
The app grades client-side and exports a results block (clipboard) or file (results/). When the user pastes a block, save it to results/ first. Then:
python3 $CW/scripts/record_results.py courses/<slug> results/<file>.json
The script re-scores from the quiz file (never trust client-side numbers), appends to course.json.results, updates quiz status and suggested per-section mastery. Then do the judgment half yourself:
- Read the wrong answers; group them into concept clusters (finer than sections).
- Report: overall grade, tier-weighted score, 🔴/🟡/🟢 cluster map, drill plan for weak spots.
- Mastery rules: 🟢 only for correct without a
guess/self-correctedflag; flagged-correct = 🟡; wrong = 🔴. Inversion questions ("which is NOT…") wrong alone may be careless reading — say so rather than marking a gap. - Update
course.json.masteryandcourse.json.scheduleaccordingly.
"Save my progress" / "sync" (back up everything the browser owns)
All day-to-day state — flashcard SRS, question SRS (Today + quizzes), session logs, prequiz results, lab
status, explain-back texts — lives in the browser's localStorage until synced, so it must be backed up or a
cleared browser erases it. The dashboard shows a Sync to Claude card whenever the browser is ahead of
disk. Save the pasted block ({"kind":"sync", …}) to results/ and then:
python3 $CW/scripts/save_progress.py courses/<slug> results/<file>.json
It merges every map (newest last/date wins), writes srsSnapshot / qsrsSnapshot / prequizResults /
labs / sessions / explainBack, and prints the repeatedly-missed items, confident-and-wrong questions,
and explain-back texts to check. Rebuild afterwards so the app restores from the snapshot. The legacy
flashcards-only block (kind:"srs") is still accepted.
After syncing: treat repeatedly-missed / confident-and-wrong as red-cluster drill candidates; check the written short answers against their rubrics; and read the explain-back texts.
Where corrections go: write them to course.json["explainFeedback"][<quiz>][<n>] = {verdict, note, date}
and course.json["shortAnswerFeedback"] respectively. Those fields are yours — a sync never overwrites them,
and the app shows them under the learner's own answer. Never edit explainBack or shortAnswers; the
browser owns those and your edit would be clobbered on the next sync (and vice versa).
A sync block may also contain finished quiz attempts (attempts) — save_progress.py writes each to
results/attempt-*.json but does not score them. Run record_results.py on each one.
"Set my cue" / the study ritual
Ask once, store in course.json.ritual as {cue, place, minutes}, where cue is one if-then sentence
in the learner's own words ("after my morning coffee, at the kitchen table, before I open email"). The app prints it
verbatim at the top of the dashboard. Context stability — a fixed trigger and a fixed, small session —
is what turns this into a habit; motivation is not the lever (Wood & Neal 2007).
Never scold a gap. If the learner comes back after a break, the app is already in comeback mode; your job is to name the next natural restart point (Monday, the start of the month) and move on.
"I read chapter N" — the closed-book map (chapter close)
When the learner offers a brain dump, ask for it as structure, not prose: "closed book — list this
chapter's 6–10 concepts and draw the arrows between them" (indented text or Mermaid, whichever is
faster to type). Then diff it against the cheatsheet's ### cards and report missing nodes and
missing/wrong links separately. Wrong links are the diagnostic signal multiple choice cannot produce;
feed them straight into the next drill. Building the map from memory is what makes it retrieval —
never let them do it with the chapter open.
"Teach me X" / "teach-back" (learning by teaching)
Role-flip: the user explains a topic; Claude plays a confused junior engineer — asks naive "why" and "what breaks if…" questions, never supplies the content. Grade the explanation (correct mechanism? right vocabulary? one concrete example? one failure case?) rather than recall, then report gaps as mastery updates. Zero app code; run once per chapter on the topic the user feels most sure about.
"Drill the red zone" / "quiz me" (Socratic drill)
Follow learning-methods.md § Drill design: ask cold → on a miss, reveal the card and have the user explain it back → re-test next session mixed with its confusable neighbours → if the cluster is a mechanism, assign the lab instead of more questions. One question at a time; smallest hint that unblocks.
"Where are we at" (status)
Read course.json and summarize: chapters ingested vs. book total, quizzes taken/pending with scores, current mastery map, next scheduled item. No searching around — the file is authoritative.
Schedule
course.json.schedule is a list of {date, task, status}. When creating or updating it: anchor to the user's real cadence (ask if unknown), put review-of-weak-clusters items after each graded quiz, and mark items done/skipped as they resolve. Surface the next pending item whenever the user checks status.
Rules
- Quizzes are closed book by design; the app embeds keys client-side (honor system) but never print answers into the human-readable cheatsheet or chat before the user submits.
- Never regenerate an existing quiz's questions in place after it's been taken — retakes reuse the same file; new material gets a new quiz id.
- One quiz's option order is fixed once written; shuffle options when authoring, not at runtime.
- After any change to quizzes, course.json, a saved snapshot, prequiz/labs, or
references/how-to-study.md, rebuild the app so it never displays stale state. - Scheduling is FSRS (default weights, 0.9 retention) for both flashcards and questions; don't fit personal parameters until thousands of reviews exist. Graduation = 3 correct answers on 3 distinct days; a miss resets.
- Both queues are capped (25 questions / 60 cards a sitting) and new material is gated on the backlog. Do not raise the caps to "get through the bank faster" — an ungated deck peaks near 233 reviews/day during ramp-up and the habit dies. A large bank is a reservoir, not a to-do list.
- Labs must not carry self-explanation prompts. Self-explanation is excellent on its own and negatively moderates the worked-example effect (β=−0.24). Keep the two apart: explain-back on quiz misses, clean worked→faded→solo on labs.
- Mastery semantics: 🟢 means produced correctly, unflagged, on ≥3 separate days (successive relearning). The app tracks that per question (
gradinqsrsSnapshot); the cluster mastery map incourse.jsonis yours to keep honest — a single clean quiz pass is 🟡 at most unless the cluster was also correct in a later drill or retake. - Failure modes: build fails → fix quiz JSON per the error; malformed pasted results → ask the user to re-export from the app rather than hand-repairing numbers; result for an unknown quiz id → check
course.json.quizzesfor the right id.