Skip to content

Releases

v0.5.3 — The update checker had been dead since 0.4.0

Choose a tag to compare

@jay739 jay739 released this 27 Oct 00:15
8371ab1

Published immediately after v0.5.2, because publishing v0.5.2 is what exposed this: the first real use of the new release channel showed the update checker had been dead for four releases.

The update checker had never worked since v0.4.0

_remote_version read only the first 4096 bytes of the remote file to find __version__. Fine until the module docstring grew past that boundary in v0.4.0:

tag __version__ offset probe
v0.3.0 3980 works
v0.4.0 4671 None
v0.5.0 5480 None
v0.5.1 5480 None
v0.5.2 6499 None

So from 0.4.0 through 0.5.2 the daily check never found an update, the upgrade nudge never appeared once, and POP_QUIZ_AUTO_UPDATE=1 was a silent no-op. The 0.5.0 note promising that the daily check "self-applies the new version" was never true in practice. Nothing failed and nothing logged. It stopped working because a comment got longer.

Second-order damage: with the probe returning None, the "already up to date" guard was skipped, so every update call re-downloaded and overwrote the running script and reported the nonsensical v0.5.2 -> v?.

Fixed

  • The probe reads the whole file. A truncated read is not worth a feature that breaks invisibly whenever a docstring grows.
  • update reads the version from the very bytes it is about to install, so the guard cannot be bypassed and the reported version cannot disagree with what gets written.
  • doctor now proves the update path works instead of just naming the channel, flagging a reachable file with no parseable version as a problem. An unreachable host stays a note, so offline machines do not cry wolf.
  • The payload is fetched at most once per process.

On the tests

The three pre-existing update tests passed only because they stubbed the probe to return a fake newer version, which is exactly what hid this for four releases. A stub that bypasses the thing under test is worse than no test. They now model a real upgrade, and the new VersionProbeTests pins the root cause. Tests 61 to 67.

Upgrade note, please read

If you are on 0.4.0 through 0.5.2, your copy cannot see this release, or any release. That is the bug. Upgrade once by hand:

python3 ~/.claude/hooks/pop_quiz.py update

After that, auto-update works for the first time since June. Then run doctor to confirm:

Updates    : channel release v0.5.3
  probe           : ok, remote v0.5.3 (up to date)

New install, no git clone needed:

curl -L https://github.com/jay739/claude-pop-quiz/releases/download/v0.5.3/claude-pop-quiz-0.5.3.tar.gz | tar xz
cd claude-pop-quiz-0.5.3 && ./install.sh

Verify downloads against SHA256SUMS.txt. Everything in v0.5.2 is included, and that release's notes cover the larger journal-parsing and spaced-repetition fixes.

v0.5.2 — The learning loop actually learns

Choose a tag to compare

@jay739 jay739 released this 27 Oct 00:09
4a67357

Every bug in this release shared one cause: the project was built so that all of its failures were silent. The wired hook command ends in 2>/dev/null || true, the learning journal is written by a cooperative model rather than by code, and nothing ever checked its own output. Three of the headline features had been dead or degraded for 37 journal sessions with no symptom at all.

Found by running the parser against a real 283 KB journal, not by reading the code.

before after
graded questions the parser could read 22 of 185 185 of 185
lifetime accuracy 45% (wrong) 61% (true)
topics tracked 20 172
correct essay answer with a defer limit set froze every chat graded
tests 23 61

That accuracy gap was not cosmetic: 45% trips the "below 60%, favour foundational questions" branch, so the hook had been asking deliberately easier questions on the strength of a 22-question sample.

  • Journal parsing recovered 88% of the history it was discarding. The grading directive's template reads <✅ correct | 🟡 partial | ❌ missed> Q<n>, and the grader renders that as ### ✅ correct Q1 · topic, emoji and word. The parser allowed no slot for that word, so on a real 283 KB journal it matched 22 of 185 graded questions. Lifetime accuracy, adaptive difficulty, the Leitner boxes and the review drill were all reading 12% of the data. Measured accuracy was 45% when the truth was 61%, which is the difference between the hook feeding you deliberately foundational questions and treating you as competent. The header pattern now tolerates the verdict word, ⚠️ as a fourth verdict, h2 to h6, bold headings and looser separators, and the directive pins the shape explicitly.
  • Spaced repetition can promote a topic at all now. quiz_directive asked the model to reuse an existing topic title, but grade_directive, which actually writes the headers, was handed no list of existing titles. So the grader coined a fresh name per question: 173 topics across 185 questions, only 4 ever asked twice, no box ever promoted, mastered permanently 0. Grading now receives the tracked titles as a controlled vocabulary with an explicit instruction to reuse them character for character, and topic keys are normalised so near-identical titles share one box (a real journal had split one topic across two boxes over a single stray backtick).
  • Leitner boxes have a clock. "Due" was box <= 1 and nothing else, so a topic answered correctly twice retired forever. Entries are now attributed to their dated section and fall due again after a per-box interval (7 / 21 / 60 days), so mastery decays instead of being permanent.
  • A correct answer can no longer freeze your tools. The defer classifier treated a long list of opening words as "new work", including this, so, yes, that's and the bare modals, which are ordinary ways to begin an essay answer. With POP_QUIZ_DEFER_LIMIT set, answering correctly could freeze every chat. The work-instruction list is now deliberately narrow, with questions and bare acknowledgements handled separately, because a false defer costs a freeze while a false answer costs one graded reply.
  • New doctor command. Reports journal parse coverage, unreadable headings, topic-reuse rate, duplicate hook wiring, the update channel and the last swallowed traceback. Exits non-zero so it can run from CI or a cron. This is the systemic fix: the bugs above were all invisible, and coverage would have been a number on a screen on day one.
  • Crashes are recorded instead of vanishing. Any unhandled exception is appended to state/pop_quiz_errors.log and surfaced by status and doctor. The hook still exits 0 so it can never break your turn.
  • Updates track tagged releases, not a branch tip (POP_QUIZ_CHANNEL, default release). With auto-update on, tracking a branch meant any merge self-applied to every machine within a day. The download is also byte-compiled before it replaces the running script, so a syntactically broken release cannot silently take the quiz down everywhere.
  • JOURNAL_MAX_ENTRIES works. It told the model to trim ## 📅 ... headers; real journals contain ## <date> · <topic> and zero 📅, so the cap silently trimmed nothing.
  • The installer no longer double-wires a renamed install. It matched the literal string pop_quiz.py, so an install renamed to e.g. grill_reminder.py (which the README blesses and update preserves) was invisible: re-running appended a second hook, both ran, and the action counter double-counted. install.sh now finds an existing install under any name, upgrades it in place, and carries forward every knob already baked into the command. uninstall.sh had the same blindness and used to report success while leaving the hooks wired.
  • CI gained a lint job (black, plus shell syntax checks) and a format-conformance step that runs doctor against JOURNAL.example.md, so the documented journal shape can never drift from the parser again. Actions bumped to checkout@v7 / setup-python@v7, off Node 20.
  • Tests: 23 to 57, with each new case verified to fail against 0.5.1.
  • New env var: POP_QUIZ_CHANNEL.

Install

New install, no git clone needed:

curl -L https://github.com/jay739/claude-pop-quiz/releases/download/v0.5.2/claude-pop-quiz-0.5.2.tar.gz | tar xz
cd claude-pop-quiz-0.5.2 && ./install.sh

Already running an earlier version:

python3 ~/.claude/hooks/pop_quiz.py update

Then run python3 ~/.claude/hooks/pop_quiz.py doctor. It reports journal parse coverage, so if you have been running an earlier version, it will tell you how much of your own history was being discarded. Your existing journal becomes fully readable retroactively, with no rewrite needed.

From this release onward the updater tracks tagged releases rather than the tip of main, so a merge can no longer auto-apply itself to every machine within a day. Verify downloads against SHA256SUMS.txt.

v0.5.1 — Freeze-bypass fix

Choose a tag to compare

@jay739 jay739 released this 27 Oct 00:08

Backfilled release: this version predates the project's release process, tagged and published retroactively from the changelog.

  • The freeze is a real backstop again. While locked, only an actual one-line MCQ answer (1C 2A 3D 4B 5A) unlocks — matching what the locked prompt asks for and what the README always documented. The v0.4.0 lenient classifier had let any non-action remark (even a question like "who is the founder of git") pop the freeze without the user attempting the quiz.
  • Questions are no longer graded as answers. A message ending in ?, or starting with an interrogative (who/whom/whose/which/...), classifies as a defer instead of being scored. Fixes genuine follow-up questions being eaten by a pending quiz.

Install

Drop pop_quiz.py into ~/.claude/hooks/ and wire it per the README of that tag, or just use the latest release instead.

v0.5.0 — Adaptive quizzing, Leitner SRS, and opt-in auto-update

Choose a tag to compare

@jay739 jay739 released this 27 Oct 00:08

Backfilled release: this version predates the project's release process, tagged and published retroactively from the changelog.

  • Leitner spaced repetition: each topic's verdict history folds into a box (✅ promotes, ❌ resets to 0, 🟡 holds). The least-mastered topics are fed into the next quiz; mastered ones retire. Replaces the flat "recent weak topics".
  • Adaptive difficulty: recent accuracy tunes the questions — foundational below 60%, harder above 85%.
  • Gamification: a correct-answer streak and lifetime accuracy are surfaced in the quiz intro and in status to motivate.
  • Socratic follow-ups: on a 🟡/❌ the grader offers one guiding hint before the answer instead of just handing it over.
  • review command: pop_quiz.py review [N] prints an offline flashcard drill of the topics the SRS says are due. No model, no network, pure journal.
  • status now shows a mastery line (topics tracked · due · mastered · streak).
  • Opt-in auto-update: with POP_QUIZ_AUTO_UPDATE=1 the daily check self-applies a newer version (download, back up to .bak, atomic in-place replace) and announces it; otherwise it keeps nudging. Updates touch only the script, so the defer counter, lock, stats, and journal survive — locked in by a regression test.
  • New env var: POP_QUIZ_AUTO_UPDATE.

Install

Drop pop_quiz.py into ~/.claude/hooks/ and wire it per the README of that tag, or just use the latest release instead.

v0.4.0 — Learning loop: spaced repetition, fairer quizzes, fewer false skips

Choose a tag to compare

@jay739 jay739 released this 27 Oct 00:08

Backfilled release: this version predates the project's release process, tagged and published retroactively from the changelog.

  • Spaced repetition: each quiz parses the journal and pulls topics you last scored partial/missed on, then works at least one back in, rephrased.
  • Real accuracy in status: lifetime ✅/🟡/❌ tally and a score percent, read straight from the journal's graded verdicts.
  • Richer journal entries: every question is now logged with a Context line (the file/function/command it came from) and the question, your answer, and the correct answer recorded in full — no more truncated entries.
  • Fairer MCQs: options are constrained to equal length and specificity with the correct letter varied, so "always pick the longest option" no longer wins.
  • Bug — synthetic prompts no longer eat a pending quiz: background/terminal task completions and [SYSTEM NOTIFICATION] events pass through instead of being misread as your answer and silently consuming the check.
  • Bug — concise correct answers count: a short, right reply ("it memoizes with lru_cache") is graded, not scored as a skip toward the freeze limit.
  • Bug — installer honours every knob: re-running with POP_QUIZ_MIN/MAX/ QUESTIONS/JOURNAL now bakes them into the command (they were silently dropped, contradicting the docs).
  • Windows: runs without fcntl in a degraded lock-free mode instead of failing silently.
  • Tests + CI: a stdlib unittest suite and a GitHub Actions matrix (Ubuntu + Windows, Python 3.8/3.12).

Install

Drop pop_quiz.py into ~/.claude/hooks/ and wire it per the README of that tag, or just use the latest release instead.

v0.3.0 — Self-update checker

Choose a tag to compare

@jay739 jay739 released this 27 Oct 00:08

Backfilled release: this version predates the project's release process, tagged and published retroactively from the changelog.

  • Self-update: the hook checks GitHub once a day (throttled, time-boxed, offline-safe) for a newer __version__ and appends a one-line upgrade nudge — shown once per version, never on tool calls.
  • update subcommand: pop_quiz.py update downloads the latest hook, backs the current one up to .bak, and overwrites in place (keeps renamed filenames).
  • status now reports installed version, script path, and live update status.
  • New env vars: POP_QUIZ_REPO, POP_QUIZ_BRANCH, POP_QUIZ_NO_UPDATE_CHECK.

Install

Drop pop_quiz.py into ~/.claude/hooks/ and wire it per the README of that tag, or just use the latest release instead.

v0.2.0 — Fixes, tool context and tooling

Choose a tag to compare

@jay739 jay739 released this 27 Oct 00:08

Backfilled release: this version predates the project's release process, tagged and published retroactively from the changelog.

  • Race-condition fix: the load/modify/save cycle is now guarded by fcntl.flock, so parallel PreToolUse calls can't clobber the counter.
  • Smarter answer detection: work instructions ("run…", "let's…", "can you…") are no longer misread as quiz answers and silently skipped.
  • MCQ single-question fix: a POP_QUIZ_QUESTIONS=1 quiz can now be answered.
  • Tool context: PreToolUse records tool names + files touched, so quizzes ask about the actual files you changed.
  • Per-project topics: a .pop-quiz-topics file targets questions per repo.
  • status subcommand, POP_QUIZ_JOURNAL_MAX_ENTRIES journal cap, and uninstall.sh.

Install

Drop pop_quiz.py into ~/.claude/hooks/ and wire it per the README of that tag, or just use the latest release instead.