| name | exam-quiz |
| description | 从标准题库 references/quiz_bank.json 抽取本章题目对学生测验并判分,支持 6 大题型(选择、主观、 画图、填空、判断、代码)。主观题用「要点检索制」对照 keywords 判分,连续答错两次给提示/跳过/归档。 禁止现场编题。当某一阶段学完需要刷题检验、或用户要求测验/模考时使用。
|
| license | MIT |
exam-quiz — question drilling & grading
Quiz the student from the question bank and grade against stored answers. Never invent questions or answers on the fly.
Purpose
Pull chapter/phase-scoped items from references/quiz_bank.json, present one item at a time across the six quiz types, grade each answer, run the escape hatch on repeated failures, and archive skipped/wrong items through update_progress.py add-mistake (initialize state first when Python works; direct study_progress.md writes are true no-Python fallback only). Hand control back to exam-cram after the checkpoint.
Activation
- Trigger after a phase is studied and needs a checkpoint quiz, or when the user asks 「测一下 / 来几道题 / 模考」.
Inputs
references/quiz_bank.json — the question bank. Each item carries type, answer, explanation, source, and a chapter OR phase tag; subjective items carry keywords. Filter selection by chapter or phase. An item with neither tag cannot be selected for a chapter quiz. Items MAY also carry difficulty (1-5) + difficulty_reason written by scripts/score_difficulty.py — an honest heuristic lower bound from structural signals (跨知识点数/结构/需读图/多页解答/章节位置/题型), never a semantic judgement and never per-student; treat the number as an ordering floor, not truth.
- Current chapter number — select only items whose
chapter (or matching phase) equals it.
If exam-ingest produced the bank, require every item to carry chapter/phase. Without it, the chapter quiz reports "no items found" even when the bank holds matching items.
Workflow
- Select & gate items: filter by matching
chapter OR phase (the bank uses both fields; filtering on chapter alone drops items tagged only with phase). Questions ALWAYS come from the bank. A missing references/quiz_bank.json is an incomplete workspace and returns to exam-ingest; this branch handles only an existing bank whose filtered pool has no usable item. In that empty-pool case, report that no verifiable checkpoint is available, emit no substitute question, and cap the phase at covered_unverified.
- Scope filter (source taxonomy) — the default pool is mixed (all
source_types). When the student restricts the range (e.g. 「只做作业题」 = homework-only), record it as a SCOPE FILTER in the progress state and select via it (official tool: python scripts/select_questions.py --workspace <ws> --source-type homework). Items with no source_type tag are EXCLUDED from a restricted scope and their count reported — never silently serve untagged items as if they matched.
- Explicit scope override — if a later request needs items OUTSIDE the active scope (e.g. homework-only is active but the student asks for lecture figure questions), announce the override BEFORE serving them in the active reply language (
中文 「⚠️ 临时覆盖你的 范围偏好」 / English ⚠️ Temporarily overriding your <scope> scope preference, substituting the active scope name). A one-turn override never silently changes the recorded scope; ask whether to switch permanently.
- Difficulty × mastery ordering — when the student wants targeted practice (「挑难题」 / 「先补弱点」 / a mode-driven session), order items with
python "${CLAUDE_SKILL_DIR}/scripts/select_hard_questions.py" --workspace <ws> --chapter <当前章> -n <k> (the script resolves from the skill package root — the student workspace has no scripts/; never resolve from cwd) instead of ad-hoc picking. For a checkpoint quiz pass --chapter <当前章> (exact-chapter filter) — select_hard_questions defaults to the whole bank, and --chapter is the ONLY exact-current-chapter scope. Do not use --from-chapter N for a checkpoint: it means every numeric chapter number ≥ N (「所有数值章号 ≥ N」), which pulls in later unstudied chapters; --from-chapter is only for 某章起步补弱 (start from chapter N onward). It combines the bank's difficulty (scored by ${CLAUDE_SKILL_DIR}/scripts/score_difficulty.py; if unscored it computes on the fly, no write) with the student's study_state.json mastery (错题/疑难 → weak, 窗口外 → weak, 在窗口/已实测 → mastered) and honors the learning mode: 查缺补漏 serves weak-first 先易后难 then mastered 先难挑战; 零基础从头讲 is globally 先易后难. It honors the recorded scope (study_state.scope, untagged excluded per the scope-filter contract; --source-type all overrides to the mixed pool for one turn AFTER you announce the override); 某章起步补弱 requires an explicit --chapter or --from-chapter (it never guesses a chapter from current_phase). Deterministic heuristic ordering — no LLM ranking; the visual-first asset gate and scope filter below still apply to every item it returns.
- Visual-first asset gate (fail-closed) — before asking an item, apply the single runtime contract in
docs/file-format.md §4:
- For
requires_assets=true or maybe_requires_assets=true: before asking, explaining, hinting, or solving, actually render/show every question-side asset (question_context / figure / diagram / table) inline and label it per §4 in the active reply language (中文/双语 题面图, English Question-side asset). Merely printing the file path is not enough; the student must see the prompt image.
- Use only question-side assets at first. Do not show answer-side assets (
answer_context / worked_solution) before the question-side assets; show them only during solution/review and label them per §4 in the active reply language (中文/双语 答案图, English Answer-side asset).
- Do not ask the item if any required question-side asset is missing, unreadable, unrenderable in the current UI, or only available as a non-rendering path — say the item is blocked because the prompt asset cannot be shown, then pick another safe
full item if one is available.
- Prefer workspace-relative Markdown paths such as
references/assets/...; never emit malformed slash-prefixed Windows drive-letter links, and never claim an image was displayed if the link did not render.
- If
question_text_status is stub or page_reference: do not treat the text as a complete standalone question — surface the prompt asset or original page first. If neither can be displayed (no asset, original not in the workspace, or web/no-image), skip it rather than asking a question the student cannot see.
- Grade by the six quiz types:
choice — compare against the answer option.
subjective — keyword-coverage grading: pass if the answer covers the item's keywords and key steps; accept equivalent wording; report coverage feedback.
fill_blank — compare against the standard fill (accept synonyms).
true_false — compare the verdict and require a one-line reason.
code — check the key edits/output against answer.
diagram — do not judge the figure from memory: follow render_hint to run the standard algorithm first, derive the structure, then compare against the student's drawing; state that the instructor's drawing method takes precedence.
- Escape hatch: on a wrong answer, give the logic gap + the item's
explanation + a hint. On the 2nd consecutive wrong answer, offer three choices — view hint / skip and archive the wrong item / continue — and proceed per the choice.
- Archive + checkpoint evidence: before the first write, if
study_state.json is absent and Python works, run python "${CLAUDE_SKILL_DIR}/scripts/update_progress.py" --workspace <ws> init; only when Python truly cannot run may the md-only fallback below be used. After every handled bank item, persist its actual outcome with python "${CLAUDE_SKILL_DIR}/scripts/update_progress.py" --workspace <ws> record-phase-evidence --kind checkpoint --ref <qid> --outcome passed|wrong|skipped; an ID alone is not proof of mastery. Also record skipped or wrong items with python "${CLAUDE_SKILL_DIR}/scripts/update_progress.py" --workspace <ws> add-mistake --id <qid> --chapter <ch> --note <错误原因> (the script resolves from the skill package root — the student workspace has no scripts/; never resolve from the current directory). Hand-editing the generated md loses the row on the next render. In a true no-Python fallback, write into the study_progress.md wrong-item archive and mark phase completeness unverified. A nonzero state command while Python runs is a fail-loud write failure, not permission to hand-edit.
- Persist-first (notebook CLI) — each graded item's full feedback (verdict, logic gap, explanation, source block) is written to the notebook BEFORE the chat reply:
echo <feedback body> | python "${CLAUDE_SKILL_DIR}/scripts/notebook.py" --workspace <ws> add-entry --chapter <ch> --type feedback --id <qid> --title <question gist> (body via STDIN; same --id replaces in place; notebook/index.md rebuilds). For a wrong or skipped item, ALSO pass --mistake — it mirrors the entry into mistakes/chNN.md and rebuilds mistakes/index.md; this mirror is IN ADDITION to the update_progress.py add-mistake state row above (the state row and the notebook entry index each other), never a replacement for it. The chat reply is a short digest ending with the pack-provided link line (zh 「完整反馈:notebook/chNN.md#<anchor>|目录:notebook/index.md」, en Full feedback: notebook/chNN.md#<anchor> | Index: notebook/index.md). On a failed notebook write, TELL the student and give the full feedback in chat; file-less clients keep chat-only output per exam-cram's capability dispatch.
- Source honesty + per-item source block: when an item's or answer's
source is ai_generated, flag it at grading time with the full AI-generated provenance sentence in the active reply language (中文 「⚠️ AI生成答案,非老师/教材提供」 / English ⚠️ AI-generated answer — not from your teacher or textbook; reference only, verify against the instructor/textbook). Additionally, after grading EVERY item, emit the fixed one-line source block in the active reply language — 中文 题目来源:<文件名> 第<N>页(<source_type>)|答案来源:<文件名/老师·教材提供/AI 推导(无教材答案)>|<canonical 溯源标签>, English Question source: <file> p.<N> (<source_type>) | Answer source: <file p.N / teacher-provided / AI-derived (no textbook answer)> | <label> — the trailing label is exactly one of the three canonical provenance sentences in the active reply language (中文 🟢 来自资料 / 🟡 AI补充,可能与你老师讲的不完全一致 / ⚠️ AI生成答案,非老师/教材提供 — English the three EN sentences in docs/language-policy.md). When the answer is AI-supplied (no stored / textbook answer), the label MUST be the full ⚠️ sentence AND the 解析/参考答案 block title carries it. Missing source metadata → write 中文 「来源未知」 / English Source unknown (Source page unknown when only the page is missing), never fabricate a filename or page. Same contract as exam-tutor's teaching template.
Output Contract
- Present one item at a time; grade as pass/not-pass plus key-point feedback; refresh the progress panel at the end.
- Persist-first default: grading feedback is written to
notebook/chNN.md (--type feedback) before the chat reply, and wrong/skipped items carry --mistake so the entry ALSO lands in mistakes/chNN.md alongside the add-mistake state row (Workflow step 4); the chat reply is a digest plus the pack-provided link line. On a failed write, say so and deliver full feedback in chat; file-less clients keep chat-only output.
- Each graded item's feedback ends with the one-line source block, rendered per the active reply language (
中文 题目来源:…|答案来源:…|<🟢/🟡/⚠️ 标签> / English Question source: … | Answer source: … | <label>) (Workflow step 5); an AI-supplied answer carries ⚠️ in both the 解析/参考答案 block title and the source label.
- Update checkpoint evidence and the wrong-item archive through
update_progress.py; let exam-cram/exam-tutor call the evidence-gated complete-phase only after all chapter evidence is present. In a true no-Python fallback, update study_progress.md and explicitly mark completeness unverified. Then hand control back to exam-cram.
- Student-facing output defaults to English (Simplified Chinese if the student opened in Chinese); a persisted
study_state.json language (中文/English/双语) switches it per exam-cram's dispatch rule with single-language purity. (See docs/language-policy.md.)
- Provenance labels in feedback follow the active reply language (
中文 verbatim markers 🟢 来自资料 / 🟡 AI补充,可能与你老师讲的不完全一致 / ⚠️ AI生成答案,非老师/教材提供 — English the three EN canonical sentences in docs/language-policy.md).
Language packs
Student-visible wording for this skill lives in per-language packs — load the one matching study_state.json.language BEFORE emitting any student-visible output:
Boundaries
-
Structured progress state: when study_state.json exists it is the SINGLE SOURCE OF TRUTH — update it via python "${CLAUDE_SKILL_DIR}/scripts/update_progress.py" --workspace <ws> record-phase-evidence/add-mistake/add-confusion/render (script path resolves from the skill package root); phase completion itself uses complete-phase. study_progress.md is a GENERATED view (hand edits are lost on the next render — never hand-patch it). If a state write fails, TELL the user; never continue as if it saved. Without study_state.json but WITH Python (a fresh, uninitialized workspace), run update_progress.py --workspace <ws> init to create the source of truth FIRST — do not stop at hand-editing study_progress.md; only when Python truly cannot run does a hand-maintained md stay valid.
-
Never write your own quiz item, including when the bank has no relevant item. With no stored answer, do not force a verdict — mark ⚠️ or state the limitation plainly.
-
Do not judge diagram items from memory — the algorithm-derived standard structure is the reference.
-
Fail-closed on assets: never ask an item whose requires_assets=true or maybe_requires_assets=true when a required question-side asset is missing, unreadable, or cannot be displayed (e.g. web-only). A structured workspace whose asset file is truly missing is blocked and returns to exam-ingest/validation; a file that exists but cannot render in the current UI is an item-level skip. In either case the item is never improvised—choose a full-text item only when the workspace itself remains ready. The validator (scripts/validate_workspace.py) rejects a workspace whose visual-required item lacks valid question-side asset files, so a clean workspace normally reaches only the UI-render branch here.
-
Use the official visual tools instead of ad-hoc parsing: to emit a visual item's prompt-side image Markdown deterministically, run python <package-root>/scripts/show_question_assets.py --workspace <ws> --id <qid> --lang <zh|en> (pass the active reply language; it fail-closes with exit 1 when the contract can't be met — then skip the item). When the student asks visual statistics (e.g. "which chapter has the most figures"), answer on both metrics — quiz-bank visual items (scripts/list_image_questions.py, per-chapter total × requires × maybe × suspects) AND material figure pages (scripts/list_figure_pages.py) — and say which metric is which; if image_question_index.json is missing, build it first via scripts/build_visual_index.py rather than counting by hand.