# Coursewright

> Personal study system: turn book chapters or any learning content into cheatsheets, tier-weighted diagnostic quizzes, and an interactive local quiz app, with graded results, mastery tracking, and a study schedule that persist across sessions. Use when the user says "I read another chapter", "quiz me", "grade my answers", "where are we at", "study schedule", "new course", or names a book/topic they are studying. Differentiator: full pipeline with persistent per-course state in courses/<slug>/course.json — not a one-off quiz generator.

- Skill: `l-kedzielawski/coursewright` (Agent Skill, multi-file: 14 files)
- Install (CLI): `npx skillmds@latest add l-kedzielawski/coursewright`
- Raw SKILL.md: https://api.skillmd.com/api/skills/l-kedzielawski/coursewright/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: l-kedzielawski (https://skillmd.com/u/l-kedzielawski)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/l-kedzielawski/coursewright

---


# 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:

```bash
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:

```bash
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

```bash
ls courses/*/course.json 2>/dev/null   # from the project root
```

- **Found** → read the relevant `course.json` before doing anything else. It answers "where are we at".
- **Not found / new course** → create the folder layout above and a fresh `course.json` (schema in `references/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)

0. If the user pastes a **brain dump** (free recall written right after reading), keep it in `results/dump-ch<N>.md` and, 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.
1. 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.
2. Append a chapter section to the cheatsheet, following `references/cheatsheet-format.md`. Insert in chapter order, not at the file end.
3. 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.
4. Generate the quiz per `references/quiz-format.md` → `quizzes/ch<N>.json`. **Every question gets an `explain`**. Use a `type:"order"` question (in a dedicated section) for any pipeline/sequence — they learn badly as MCQ. Validate with `python3 $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.
5. 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.
6. Update `course.json`: chapter status, quiz entry (`status: "ready"`).
7. 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.
8. Rebuild the app (below) and tell the user the quiz is ready and to take it within 48 h.

### Build / rebuild the app

```bash
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:

```bash
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:

1. Read the wrong answers; group them into concept clusters (finer than sections).
2. Report: overall grade, tier-weighted score, 🔴/🟡/🟢 cluster map, drill plan for weak spots.
3. Mastery rules: 🟢 only for correct **without** a `guess`/`self-corrected` flag; flagged-correct = 🟡; wrong = 🔴. Inversion questions ("which is NOT…") wrong alone may be careless reading — say so rather than marking a gap.
4. Update `course.json.mastery` and `course.json.schedule` accordingly.

### "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:

```bash
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 (`grad` in `qsrsSnapshot`); the *cluster* mastery map in `course.json` is 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.quizzes` for the right id.

