fullmark 0.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. fullmark-0.10.0/.gitignore +7 -0
  2. fullmark-0.10.0/.markdownlint-cli2.jsonc +18 -0
  3. fullmark-0.10.0/AGENTS.md +117 -0
  4. fullmark-0.10.0/LICENSE +21 -0
  5. fullmark-0.10.0/PKG-INFO +181 -0
  6. fullmark-0.10.0/README.md +151 -0
  7. fullmark-0.10.0/docs/GUIDE.html +1137 -0
  8. fullmark-0.10.0/docs/GUIDE.md +401 -0
  9. fullmark-0.10.0/docs/PLANNED.md +104 -0
  10. fullmark-0.10.0/docs/RELEASING.md +22 -0
  11. fullmark-0.10.0/docs/SPEC.md +793 -0
  12. fullmark-0.10.0/docs/build_docs.py +82 -0
  13. fullmark-0.10.0/docs/fullmark-project.html +4543 -0
  14. fullmark-0.10.0/docs/fullmark-project.md +194 -0
  15. fullmark-0.10.0/examples/calculus/assets/cubic.png +0 -0
  16. fullmark-0.10.0/examples/calculus/quiz.html +4547 -0
  17. fullmark-0.10.0/examples/calculus/quiz.md +157 -0
  18. fullmark-0.10.0/examples/exam/quiz.html +4547 -0
  19. fullmark-0.10.0/examples/exam/quiz.md +100 -0
  20. fullmark-0.10.0/examples/ielts/assets/ielts-chart.png +0 -0
  21. fullmark-0.10.0/examples/ielts/quiz.html +4543 -0
  22. fullmark-0.10.0/examples/ielts/quiz.md +50 -0
  23. fullmark-0.10.0/examples/medical/quiz.html +4547 -0
  24. fullmark-0.10.0/examples/medical/quiz.md +132 -0
  25. fullmark-0.10.0/examples/rust/quiz.html +4543 -0
  26. fullmark-0.10.0/examples/rust/quiz.md +217 -0
  27. fullmark-0.10.0/examples/survey/quiz.html +4543 -0
  28. fullmark-0.10.0/examples/survey/quiz.md +79 -0
  29. fullmark-0.10.0/pyproject.toml +72 -0
  30. fullmark-0.10.0/skill/SKILL.md +93 -0
  31. fullmark-0.10.0/src/fullmark/__init__.py +1 -0
  32. fullmark-0.10.0/src/fullmark/agents.md +117 -0
  33. fullmark-0.10.0/src/fullmark/blanks.py +71 -0
  34. fullmark-0.10.0/src/fullmark/brief.py +571 -0
  35. fullmark-0.10.0/src/fullmark/brief_questions.yaml +239 -0
  36. fullmark-0.10.0/src/fullmark/build.py +233 -0
  37. fullmark-0.10.0/src/fullmark/checking.py +48 -0
  38. fullmark-0.10.0/src/fullmark/cli.py +1123 -0
  39. fullmark-0.10.0/src/fullmark/examples/assets/cubic.png +0 -0
  40. fullmark-0.10.0/src/fullmark/examples/assets/ielts-chart.png +0 -0
  41. fullmark-0.10.0/src/fullmark/examples/blank.md +131 -0
  42. fullmark-0.10.0/src/fullmark/examples/calculus-1.md +157 -0
  43. fullmark-0.10.0/src/fullmark/examples/exam-mode.md +100 -0
  44. fullmark-0.10.0/src/fullmark/examples/ielts-writing.md +50 -0
  45. fullmark-0.10.0/src/fullmark/examples/make_assets.py +87 -0
  46. fullmark-0.10.0/src/fullmark/examples/medical-physiology.md +132 -0
  47. fullmark-0.10.0/src/fullmark/examples/rust-basics.md +217 -0
  48. fullmark-0.10.0/src/fullmark/examples/survey.md +79 -0
  49. fullmark-0.10.0/src/fullmark/grade_run.py +109 -0
  50. fullmark-0.10.0/src/fullmark/grades.py +112 -0
  51. fullmark-0.10.0/src/fullmark/grading.py +314 -0
  52. fullmark-0.10.0/src/fullmark/guide.md +367 -0
  53. fullmark-0.10.0/src/fullmark/inspect_quiz.py +97 -0
  54. fullmark-0.10.0/src/fullmark/library/__init__.py +5 -0
  55. fullmark-0.10.0/src/fullmark/library/commands.py +1316 -0
  56. fullmark-0.10.0/src/fullmark/library/config.py +83 -0
  57. fullmark-0.10.0/src/fullmark/library/fsutil.py +60 -0
  58. fullmark-0.10.0/src/fullmark/library/git.py +125 -0
  59. fullmark-0.10.0/src/fullmark/library/pretty.py +285 -0
  60. fullmark-0.10.0/src/fullmark/library/quizzes.py +379 -0
  61. fullmark-0.10.0/src/fullmark/library/store.py +69 -0
  62. fullmark-0.10.0/src/fullmark/lint.py +98 -0
  63. fullmark-0.10.0/src/fullmark/mdutil.py +41 -0
  64. fullmark-0.10.0/src/fullmark/models.py +501 -0
  65. fullmark-0.10.0/src/fullmark/numparse.py +28 -0
  66. fullmark-0.10.0/src/fullmark/parser.py +510 -0
  67. fullmark-0.10.0/src/fullmark/render.py +129 -0
  68. fullmark-0.10.0/src/fullmark/report.py +913 -0
  69. fullmark-0.10.0/src/fullmark/review.py +506 -0
  70. fullmark-0.10.0/src/fullmark/schemas/grades-v1.schema.json +27 -0
  71. fullmark-0.10.0/src/fullmark/schemas/report-v1.schema.json +114 -0
  72. fullmark-0.10.0/src/fullmark/schemas/results-v1.schema.json +185 -0
  73. fullmark-0.10.0/src/fullmark/schemas/review-v1.schema.json +39 -0
  74. fullmark-0.10.0/src/fullmark/serve.py +533 -0
  75. fullmark-0.10.0/src/fullmark/static/quiz.css +1574 -0
  76. fullmark-0.10.0/src/fullmark/static/quiz.js +2738 -0
  77. fullmark-0.10.0/src/fullmark/stats.py +148 -0
  78. fullmark-0.10.0/src/fullmark/templates/quiz.html.j2 +48 -0
  79. fullmark-0.10.0/src/fullmark/templates_cmd.py +101 -0
  80. fullmark-0.10.0/src/fullmark/theme.py +135 -0
  81. fullmark-0.10.0/src/fullmark/validate.py +495 -0
  82. fullmark-0.10.0/src/fullmark/vendor/katex/LICENSE +21 -0
  83. fullmark-0.10.0/src/fullmark/vendor/katex/VERSION +1 -0
  84. fullmark-0.10.0/src/fullmark/vendor/katex/contrib/auto-render.min.js +1 -0
  85. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  86. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  87. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  88. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  89. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  90. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  91. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  92. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  93. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  94. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  95. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  96. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  97. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  98. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  99. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  100. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  101. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  102. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  103. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  104. fullmark-0.10.0/src/fullmark/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  105. fullmark-0.10.0/src/fullmark/vendor/katex/katex.min.css +1 -0
  106. fullmark-0.10.0/src/fullmark/vendor/katex/katex.min.js +1 -0
  107. fullmark-0.10.0/tests/conftest.py +17 -0
  108. fullmark-0.10.0/tests/e2e/conftest.py +43 -0
  109. fullmark-0.10.0/tests/e2e/test_accessibility.py +165 -0
  110. fullmark-0.10.0/tests/e2e/test_browser.py +1870 -0
  111. fullmark-0.10.0/tests/e2e/test_followup_page.py +221 -0
  112. fullmark-0.10.0/tests/e2e/test_grader_parity.py +66 -0
  113. fullmark-0.10.0/tests/e2e/test_panel.py +254 -0
  114. fullmark-0.10.0/tests/e2e/test_review_page.py +164 -0
  115. fullmark-0.10.0/tests/fixtures/all-types.md +163 -0
  116. fullmark-0.10.0/tests/fixtures/assets/beep.wav +0 -0
  117. fullmark-0.10.0/tests/fixtures/assets/gradient.png +0 -0
  118. fullmark-0.10.0/tests/fixtures/followup.md +32 -0
  119. fullmark-0.10.0/tests/fixtures/invalid/answer-missing.md +7 -0
  120. fullmark-0.10.0/tests/fixtures/invalid/answer-on-choice.md +12 -0
  121. fullmark-0.10.0/tests/fixtures/invalid/answer-yaml-error.md +10 -0
  122. fullmark-0.10.0/tests/fixtures/invalid/blanks-in-math.md +7 -0
  123. fullmark-0.10.0/tests/fixtures/invalid/blanks-in-wrong-type.md +11 -0
  124. fullmark-0.10.0/tests/fixtures/invalid/blanks-none.md +7 -0
  125. fullmark-0.10.0/tests/fixtures/invalid/blanks-with-answer-block.md +11 -0
  126. fullmark-0.10.0/tests/fixtures/invalid/code-no-language.md +9 -0
  127. fullmark-0.10.0/tests/fixtures/invalid/duplicate-id.md +13 -0
  128. fullmark-0.10.0/tests/fixtures/invalid/expected.json +282 -0
  129. fullmark-0.10.0/tests/fixtures/invalid/fields-bad-regex.md +11 -0
  130. fullmark-0.10.0/tests/fixtures/invalid/fields-empty.md +9 -0
  131. fullmark-0.10.0/tests/fixtures/invalid/fm-bad-enum.md +9 -0
  132. fullmark-0.10.0/tests/fixtures/invalid/fm-bad-mode.md +9 -0
  133. fullmark-0.10.0/tests/fixtures/invalid/fm-bad-time-limit.md +9 -0
  134. fullmark-0.10.0/tests/fixtures/invalid/fm-missing-title.md +8 -0
  135. fullmark-0.10.0/tests/fixtures/invalid/fm-no-front-matter.md +4 -0
  136. fullmark-0.10.0/tests/fixtures/invalid/fm-unknown-key.md +9 -0
  137. fullmark-0.10.0/tests/fixtures/invalid/graded-not-bool.md +8 -0
  138. fullmark-0.10.0/tests/fixtures/invalid/heading-h3.md +10 -0
  139. fullmark-0.10.0/tests/fixtures/invalid/input-empty-prompt.md +8 -0
  140. fullmark-0.10.0/tests/fixtures/invalid/input-on-heading.md +8 -0
  141. fullmark-0.10.0/tests/fixtures/invalid/input-on-ordering.md +8 -0
  142. fullmark-0.10.0/tests/fixtures/invalid/input-on-truefalse-choice-list.md +12 -0
  143. fullmark-0.10.0/tests/fixtures/invalid/input-unclosed-quote.md +8 -0
  144. fullmark-0.10.0/tests/fixtures/invalid/input-unquoted-prompt.md +8 -0
  145. fullmark-0.10.0/tests/fixtures/invalid/matching-duplicate-right.md +8 -0
  146. fullmark-0.10.0/tests/fixtures/invalid/matching-empty-right.md +8 -0
  147. fullmark-0.10.0/tests/fixtures/invalid/matching-missing-separator.md +8 -0
  148. fullmark-0.10.0/tests/fixtures/invalid/matching-one-pair.md +8 -0
  149. fullmark-0.10.0/tests/fixtures/invalid/media-bad-ext.md +10 -0
  150. fullmark-0.10.0/tests/fixtures/invalid/media-missing.md +10 -0
  151. fullmark-0.10.0/tests/fixtures/invalid/media-remote.md +10 -0
  152. fullmark-0.10.0/tests/fixtures/invalid/media-video.md +10 -0
  153. fullmark-0.10.0/tests/fixtures/invalid/multi-none-checked.md +8 -0
  154. fullmark-0.10.0/tests/fixtures/invalid/multi-one-item.md +7 -0
  155. fullmark-0.10.0/tests/fixtures/invalid/no-questions.md +5 -0
  156. fullmark-0.10.0/tests/fixtures/invalid/no-type-no-choices.md +7 -0
  157. fullmark-0.10.0/tests/fixtures/invalid/numeric-both-tolerances.md +11 -0
  158. fullmark-0.10.0/tests/fixtures/invalid/numeric-negative-tolerance.md +10 -0
  159. fullmark-0.10.0/tests/fixtures/invalid/numeric-not-number.md +9 -0
  160. fullmark-0.10.0/tests/fixtures/invalid/ordering-answer-block.md +12 -0
  161. fullmark-0.10.0/tests/fixtures/invalid/ordering-duplicate-item.md +9 -0
  162. fullmark-0.10.0/tests/fixtures/invalid/ordering-no-list.md +7 -0
  163. fullmark-0.10.0/tests/fixtures/invalid/ordering-one-item.md +7 -0
  164. fullmark-0.10.0/tests/fixtures/invalid/scale-graded-true.md +5 -0
  165. fullmark-0.10.0/tests/fixtures/invalid/scale-label-outside.md +10 -0
  166. fullmark-0.10.0/tests/fixtures/invalid/scale-range-too-wide.md +10 -0
  167. fullmark-0.10.0/tests/fixtures/invalid/short-bad-regex.md +9 -0
  168. fullmark-0.10.0/tests/fixtures/invalid/short-empty-accept.md +9 -0
  169. fullmark-0.10.0/tests/fixtures/invalid/short-lookbehind.md +9 -0
  170. fullmark-0.10.0/tests/fixtures/invalid/short-named-group.md +9 -0
  171. fullmark-0.10.0/tests/fixtures/invalid/single-none-checked.md +8 -0
  172. fullmark-0.10.0/tests/fixtures/invalid/single-one-item.md +7 -0
  173. fullmark-0.10.0/tests/fixtures/invalid/single-two-checked-explicit.md +8 -0
  174. fullmark-0.10.0/tests/fixtures/invalid/survey-correct-choice.md +9 -0
  175. fullmark-0.10.0/tests/fixtures/invalid/survey-graded-attr.md +9 -0
  176. fullmark-0.10.0/tests/fixtures/invalid/survey-instant-feedback.md +10 -0
  177. fullmark-0.10.0/tests/fixtures/invalid/survey-multi-scoring.md +10 -0
  178. fullmark-0.10.0/tests/fixtures/invalid/survey-pass-mark.md +10 -0
  179. fullmark-0.10.0/tests/fixtures/invalid/survey-pick.md +10 -0
  180. fullmark-0.10.0/tests/fixtures/invalid/survey-section-pick.md +11 -0
  181. fullmark-0.10.0/tests/fixtures/invalid/survey-truefalse.md +10 -0
  182. fullmark-0.10.0/tests/fixtures/invalid/truefalse-not-bool.md +9 -0
  183. fullmark-0.10.0/tests/fixtures/invalid/ungraded-correct-choice.md +8 -0
  184. fullmark-0.10.0/tests/fixtures/invalid/ungraded-essay-rubric.md +10 -0
  185. fullmark-0.10.0/tests/fixtures/invalid/ungraded-fields-with-value.md +11 -0
  186. fullmark-0.10.0/tests/fixtures/invalid/ungraded-numeric.md +9 -0
  187. fullmark-0.10.0/tests/fixtures/invalid/ungraded-short-answer-block.md +9 -0
  188. fullmark-0.10.0/tests/fixtures/invalid/unknown-attribute.md +8 -0
  189. fullmark-0.10.0/tests/fixtures/invalid/unknown-type.md +7 -0
  190. fullmark-0.10.0/tests/fixtures/make_fixtures.py +435 -0
  191. fullmark-0.10.0/tests/fixtures/mixed.md +37 -0
  192. fullmark-0.10.0/tests/fixtures/survey.md +62 -0
  193. fullmark-0.10.0/tests/grading_cases.json +3657 -0
  194. fullmark-0.10.0/tests/helpers.py +169 -0
  195. fullmark-0.10.0/tests/make_grading_cases.py +977 -0
  196. fullmark-0.10.0/tests/test_bank.py +247 -0
  197. fullmark-0.10.0/tests/test_blank_width.py +45 -0
  198. fullmark-0.10.0/tests/test_brief.py +800 -0
  199. fullmark-0.10.0/tests/test_build.py +243 -0
  200. fullmark-0.10.0/tests/test_docs.py +90 -0
  201. fullmark-0.10.0/tests/test_examples.py +266 -0
  202. fullmark-0.10.0/tests/test_followup.py +129 -0
  203. fullmark-0.10.0/tests/test_grades.py +185 -0
  204. fullmark-0.10.0/tests/test_grading.py +130 -0
  205. fullmark-0.10.0/tests/test_layers.py +112 -0
  206. fullmark-0.10.0/tests/test_library.py +1871 -0
  207. fullmark-0.10.0/tests/test_lint.py +82 -0
  208. fullmark-0.10.0/tests/test_public.py +44 -0
  209. fullmark-0.10.0/tests/test_report.py +506 -0
  210. fullmark-0.10.0/tests/test_review.py +588 -0
  211. fullmark-0.10.0/tests/test_serve.py +465 -0
  212. fullmark-0.10.0/tests/test_skill.py +55 -0
  213. fullmark-0.10.0/tests/test_stats.py +150 -0
  214. fullmark-0.10.0/tests/test_validate.py +291 -0
  215. fullmark-0.10.0/tests/test_wheel.py +86 -0
  216. fullmark-0.10.0/tests/vendor/axe.min.js +12 -0
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ dist/
7
+ *.egg-info/
@@ -0,0 +1,18 @@
1
+ // Same rules as the user's default config, except two that the fullmark format needs:
2
+ // one H1 per section (MD025) and adjacent hint/explanation blockquotes (MD028).
3
+ {
4
+ "config": {
5
+ "default": true,
6
+ "MD003": { "style": "atx" },
7
+ "MD004": { "style": "dash" },
8
+ "MD007": { "indent": 2 },
9
+ "MD013": false,
10
+ "MD024": { "siblings_only": true },
11
+ "MD025": false,
12
+ "MD028": false,
13
+ "MD033": false,
14
+ "MD041": false,
15
+ "MD046": { "style": "fenced" },
16
+ "MD060": false
17
+ }
18
+ }
@@ -0,0 +1,117 @@
1
+ # AGENTS.md: working with fullmark
2
+
3
+ fullmark turns one Markdown file into one offline, self-grading HTML quiz. This file is for AI agents. It covers **using** fullmark (Jobs 1, 2 and 4: write, grade and file quizzes) and **changing** it (Job 3: develop the tool).
4
+
5
+ ## Quick facts
6
+
7
+ - CLI: `quiz` (installed with `uv tool install --editable <path to this repo>`). In the repo, use `uv run quiz ...`.
8
+ - Full authoring reference: run `quiz guide`. This file: run `quiz agent-guide` (it works without the repo). It is the source of truth for syntax. Read it before writing a quiz; do not guess.
9
+ - Examples to copy from: `examples/calculus/`, `examples/rust/`, `examples/ielts/`, `examples/medical/`, `examples/exam/` (each has `quiz.md`, a built `quiz.html`, and `assets/` where needed). `quiz new calculus|rust|ielts|medical|exam|blank [PATH]` copies a template and never overwrites.
10
+ - Exit codes for `check` and `build`: 0 = ok (warnings allowed), 1 = quiz errors, 2 = usage or I/O problem. With `--strict`, any warning is also 1 (use it in CI; `build --strict` writes nothing).
11
+ - Nothing needs the network. Do not add network requests to the page; tests assert there are none.
12
+
13
+ ## Job 1: author a quiz
14
+
15
+ 1. Read the source material, then run `quiz guide`.
16
+ 2. Write `quiz.md`: YAML front matter (`title` required; quote it if it contains a colon), optional `#` sections, `##` questions.
17
+ 3. Run `quiz check quiz.md` (add `--json` to parse findings). Output is `file:line: error|warning: qN (type): message`. Fix every error; fix warnings too, usually a missing `> [!explanation]`. `quiz check` also lints for weak questions (short explanation, "all of the above", a correct option much longer than the rest, answer in the question text, skewed answer positions). Fix those too; they are the usual agent mistakes. `quiz inspect quiz.md` shows types, points, sections, tags and answer positions, so you can check coverage against the source material.
18
+ 4. While iterating, `quiz watch quiz.md` rebuilds on every save (reload the page after each rebuild, or use `--serve` to have the browser reload by itself).
19
+ 5. Run `quiz build quiz.md` to write `quiz.html` beside it (`-o OUT` to choose, `--open` to launch it). Give the learner that one file.
20
+
21
+ Rules that avoid most errors:
22
+
23
+ - Choice questions: every item needs `[ ]` or `[x]`. One `[x]` is single, two or more is multi.
24
+ - `truefalse`, `numeric`, `short`, `fields`, `essay` and `code` need a fenced block whose info string is `answer`, containing YAML (`value`, `tolerance`, `accept`, `patterns`, `min_words`, `rubric`, `language`, `starter`).
25
+ - `ordering`: write an ordered list in the **correct** order (the page shuffles it). `matching`: bullets `left :: right`; a bullet `:: right` is a distractor. Neither takes an `answer` block. Both follow `multi_scoring` (`partial` or `all_or_nothing`).
26
+ - `blanks` use inline `[[answer|variant]]`, also inside code blocks.
27
+ - Heading attributes go at the end of the `##` line: `{type=numeric points=2 id=q-area tags=limits}`. Tags have no spaces; they drive the per-topic report.
28
+ - Numeric answers are compared as numbers, not symbolically. For `short` answers list every accepted spelling.
29
+ - Question banks: write extra questions and set `pick: N` (front matter) or `{pick=N}` on sections so each attempt draws a random subset. The export records the draw (`attempt.bank`) and `quiz grade` checks it. Use it for practice drills, not when everyone must see the same questions.
30
+ - Questionnaires: `mode: survey` (front matter) makes every question ungraded and the page shows no score; `graded=false` on a `##` heading does that for one question; `{type=scale}` is a rating row. Ungraded questions are `single`, `multi`, `short`, `fields`, `essay`, `ordering` (a ranking) and `scale`; write no `[x]`, no `answer` block on `short`, no rubric on `essay`. Use a survey instead of asking more than about five questions in chat, or when the user wants to keep the answers. Start from `quiz new survey`. When you are waiting for the answers, collect them directly: run `quiz serve quiz.md --once --open --json` in the background (it exits 0 when the respondent presses Finish, 2 on `--timeout`) and read the results file named in its JSON output; never start it in a way that blocks you.
31
+ - Follow-up text on a choice: end a `single` or `multi` choice with `{input}` or `{input="What is it?"}` (default prompt "Please specify") to ask for a few words when it is picked, for example "Something else". It works in quizzes and surveys, graded or not, and never changes the score. The export adds `response.inputs` (`{"2": "text"}`, original choice indices) and `quiz grade` shows `Something else: text` (`answer_text`, plus the raw `inputs`, in the JSON report). The marker must end the line and the prompt needs double quotes; on any other question type it is an error.
32
+ - Brief: for a new quiz, look for `brief.yaml` in the quiz folder first and read it. It holds the purpose, scope, material (`content/`), count, mix, difficulty, mode, weak topics (`focus_tags`) and language. If `request` is filled in, do not ask the brief questions again. The human-only values are **purpose and scope**: if `brief.yaml` is missing and the user has not said them, ask, or run `quiz brief DIR --web` for them; `quiz brief DIR ...` exits 3 until both are given, and `--accept-defaults` never gives you the scope. Use `--accept-defaults` only when the user handed those choices over, and list what you chose. When `past-exams` is ticked, mirror their structure (types, split of marks, length) and cite them in explanations as `Source: file, question N`. `quiz check` warns when the quiz drifts from the brief (`brief.count`, `brief.mix`, `brief.past-exams`, `brief.content-kinds`).
33
+ - Review what you wrote: an agent-written quiz can have a wrong key or a question that is not in the material, and only the learner can judge that. After writing a quiz from material, offer `quiz review quiz.md`; when the user agrees, run `quiz review quiz.md --once --open --json` in the background and wait for it (exit 0 when the learner presses Finish, 2 on `--timeout`). Its JSON lists `flagged` questions (verdict, text, problems, note) and counts. Fix what is flagged **by changing the source**, then run `quiz check quiz.md --review review.json`: `review.drop`, `review.fix` and `review.key-wrong` are errors until the questions change. Offer `quiz review quiz.md --since review.json` for the changed ones. `review: true` in front matter is set only by the generated review form; never write it in a quiz. Never write or edit `review.json` yourself: it is the learner's verdict (it says `"by": "quiz review"`), and the check cannot tell. Give questions `{id=...}` so a review survives reordering; matching otherwise uses a hash of the question text.
34
+ - Put images in `assets/` and reference them relatively (`![alt](assets/x.png)`); they are embedded as data URIs. No video, no web URLs.
35
+ - Give every auto-graded question an explanation. Write essay and code rubrics concretely, because they are what you will grade against later.
36
+ - The answer key is in the HTML, only lightly obfuscated. Never describe the quiz as secure.
37
+
38
+ ## Job 2: grade results
39
+
40
+ The learner presses Submit, then Export answers, which produces `<id>-results-<date>.json`. Its JSON Schema is `src/fullmark/schemas/results-v1.schema.json`; the `--json` report has `report-v1.schema.json` beside it. Both carry a `version`; new optional fields keep it, so ignore keys you do not know.
41
+
42
+ ```powershell
43
+ quiz grade results.json --quiz quiz.md # writes results-report.md next to the results
44
+ quiz grade results.json --quiz quiz.md --json # writes results-report.json instead (preferred for agents)
45
+ ```
46
+
47
+ - A learner can press **Retry missed** after Submit. That export has `attempt.retry_of` (the earlier attempt's `started_at`) and `attempt.bank.drawn` lists only the missed questions; `quiz grade` accepts it without a draw warning.
48
+ - Python re-grades from the quiz source and is the authority. If it disagrees with the browser score it warns and uses its own number. It also warns if `quiz.md` changed after the attempt.
49
+ - Prefer `--json`. It has `summary`, `by_topic` and a `questions` list; each question has `status`, `earned`, `points`, `your_answer`, `correct_answer`, `explanation`, `needs_review` and `needs_ai_grading`. Essay, code and ungraded-field questions also carry an `ai_grading` object: `rubric`, `criteria`, `scoring`, and the learner's `response` (plus `word_count`, `language` and so on).
50
+ - Ungraded questions (`graded=false`, `scale`, every question in a survey) have `status: "ungraded"` (or `unanswered`), `earned: null`, `ungraded: true`, the raw `response` and a readable `answer_text`; they are never in `needs_review` or `needs_ai_grading`, and the grades template never lists them. To read a survey: `quiz grade results.json --quiz quiz.md --json` and read `answer_text` per question (`attempt.mode` and the top-level `mode` say `survey`); the Markdown report of a survey is titled "Answers" and has no score.
51
+ - The Markdown report has a summary, a per-tag table, a per-question table, an **Answers** section (only when the quiz has ungraded questions), **Review these** (wrong or partial answers with correct answer and explanation), and **Pending AI review**.
52
+ - Your job is the **Pending AI review** section: for each essay and code answer, read the learner's response against the rubric and fill in the empty **Grading** block (score out of the question's points, short justification, one concrete improvement). Code answers are never executed; judge by reading.
53
+ - Preferred way to record grades: `quiz grade results.json --quiz quiz.md --json` also writes `<results>-grades.json`, an empty template (schema `grades-v1.schema.json`). Fill `score` (0 to the question's points), `band` (0-9, half steps), `criteria`, `justification` and `improvement` for each id, then run the same command with `--grades <file>`. Errors (unknown id, score out of range, bad band) exit 1. The report then carries each grade under `ai_grading.result` and `summary.final_earned`, `final_possible` and `final_percent`. Band questions are reported but never add to the points. An existing template is never overwritten.
54
+ - To see progress over several attempts at one quiz: `quiz stats a.json b.json c.json [--quiz quiz.md] [--json]` prints scores by date, per-topic percent per attempt with the change from first to last, and the three weakest topics of the latest attempt, plus an "Answers over time" table for `scale` and ungraded `single` questions (ungraded questions never enter the scores; surveys work too). Different quizzes exit 2; a changed quiz only warns.
55
+ - Then summarize for the learner: weak tags first, what to revisit, and corrections for each wrong answer. Do not edit the learner's answers or the JSON.
56
+ - Essays with `scoring: band` (set in the `answer` block, IELTS style) ask for a 0-9 band; give one per rubric criterion if the rubric lists criteria.
57
+
58
+ In Claude Code the `/quiz` skill wraps both jobs. Its source is `skill/SKILL.md` in this repo (linked into `~/.claude/skills/quiz`); `tests/test_skill.py` fails if it names a command, flag or template the CLI does not have.
59
+
60
+ ## Job 3: develop fullmark
61
+
62
+ Layout (`src/fullmark/`):
63
+
64
+ | Path | Role |
65
+ |---|---|
66
+ | `parser.py`, `models.py`, `validate.py` | Markdown to pydantic models, with line numbers; all error messages |
67
+ | `grading.py` and `static/quiz.js` | The same grading rules twice (Python authority, JS in the page). Change both together |
68
+ | `render.py`, `build.py`, `theme.py`, `templates/`, `static/quiz.css` | The HTML page. Colors live only in `theme.py`; the CSS uses `var(--name)` |
69
+ | `report.py` | Markdown report |
70
+ | `checking.py`, `serve.py` | The findings behind `quiz check`; the local servers (`watch --serve`, `serve`, `review`, `take`) and `serve_loop` |
71
+ | `library/` | `quiz library`: config, registry, git, terminal tables. It imports core; core never imports it (`tests/test_layers.py`); `cli.py` imports it only inside the `library` handler |
72
+ | `cli.py`, `templates_cmd.py`, `guide.md`, `agents.md` | CLI, `quiz new`, the text printed by `quiz guide` and `quiz agent-guide` |
73
+ | `examples/` | Packaged templates used by `quiz new` and the tests |
74
+ | `vendor/katex/` | Vendored KaTeX 0.16.47. Do not edit |
75
+
76
+ Top-level `examples/` holds copies of the three example quizzes with built HTML. They are not read by the package. The packaged files are the source. After editing a packaged example, `quiz.css` or `quiz.js`, run `uv run python scripts/sync_examples.py` to copy them over and rebuild the HTML; a test fails when they drift. The same script copies `src/fullmark/agents.md` (the source of this file) to the root `AGENTS.md`; edit the packaged one.
77
+
78
+ Workflow:
79
+
80
+ ```powershell
81
+ uv sync
82
+ uv run pytest -q # full suite, about 5 min, includes real-browser tests
83
+ uv run pytest -q --ignore=tests/e2e # skip the browser tests
84
+ ```
85
+
86
+ - Grader changes: add a golden case (`uv run python tests/make_grading_cases.py`); cases run through both Python and JS and must agree.
87
+ - New validation error: add one fixture (`tests/fixtures/make_fixtures.py`).
88
+ - Docs: `docs/GUIDE.md` (user guide), `docs/SPEC.md` (design decisions), `src/fullmark/guide.md` (authoring reference). Keep the syntax tables in `guide.md` and `GUIDE.md` consistent, then run `uv run python docs/build_docs.py` to rebuild the HTML.
89
+ - Example images: `uv run python src/fullmark/examples/make_assets.py`.
90
+ - UI rules the tests enforce: no network requests, contrast limits in light and dark, keyboard access. Do not use color alone to signal right or wrong; badges carry a symbol.
91
+ - Style: Python is linted by ruff, JS/JSON by Biome, Markdown by markdownlint. A hook formats edited files; fix what it reports.
92
+
93
+ Working rules (every change):
94
+
95
+ - Read this file before changing anything.
96
+ - One task, one commit. Run `uv run pytest -q` before every commit; it must pass. Known flakes under load, rerun alone before treating them as failures: `tests/e2e/test_panel.py::test_narrow_screen_keeps_progress_and_pills_in_the_header` and Windows socket aborts (WinError 10053).
97
+ - A change to syntax updates `src/fullmark/guide.md` **and** `docs/GUIDE.md`. A change to the page (CSS, JS, template) or a packaged example ends with `uv run python scripts/sync_examples.py`.
98
+ - Put all localStorage access through the `store()` wrapper in `quiz.js`; it copes with blocked storage.
99
+ - Keep `ruff check`, `ruff format --check`, `biome lint src/fullmark/static/quiz.js` and `ty check src/fullmark/library src/fullmark/serve.py` clean.
100
+ - A new dependency changes the installed tool: say in the README that `uv tool install --editable <path to this repo> --reinstall` is needed.
101
+ - Never touch the user's real library or config (`quiz library path` shows it) except to read it; tests and manual checks use a scratch `FULLMARK_CONFIG`, `GIT_CONFIG_GLOBAL` and `GIT_CONFIG_NOSYSTEM` (the `lib` fixture in `tests/conftest.py`).
102
+ - Stop and ask the user before anything that needs an account, a remote or a publish. Never push; never publish to a package index.
103
+ - Plans: the roadmap is `docs/PLANNED.md` (strike items through when done: `~~item~~: done`). A work order for a larger change goes in `docs/plans/NAME_PLAN.md` and gets a line in `docs/plans/README.md`. The root holds only `README.md` and `AGENTS.md`.
104
+
105
+ ## Job 4: the library
106
+
107
+ `quiz library` keeps the user's own quizzes and results in one folder under git. Commands: `init`, `status`, `register`, `list`, `take`, `share`, `path`, `grade`, `stats`, `rename`, `rm`. Exit codes: 0 done, 1 problems or a refused quiz, 2 usage error or no library, 3 needs input. The tool commits after each write and **never pushes**. Everything below is the contract; the user's library may or may not exist.
108
+
109
+ - **Check first.** `quiz library status --json`: `status` is `no_library` when nothing is set up (exit 2), `ok` or `problems` otherwise. Read it; never change the library or the config by hand.
110
+ - **The loop** for a quiz you write: `quiz check quiz.md` (fix errors and warnings), optionally `quiz review`, then `quiz library register quiz.md --json`. No `quiz build` is needed for the user's own quizzes; the `.md` and its `assets/` can sit in your scratch folder. Images must be inside the quiz's folder and referenced relatively.
111
+ - **Exit 3 means ask.** With no library, `register` exits 3 with `{"status": "needs_input", "missing": [...], "fix": "quiz library init PATH"}`. Ask the user where the library goes. **Never run `init` yourself**: without PATH it makes the current folder (often the project) the library, and the location is theirs to choose. `init` refuses a folder that has other files (exit 2); give it a new or empty one, for example `D:\quizzes`. Pass `--force` only after the user said yes to switching libraries.
112
+ - **A name clash** exits 1 and suggests a free name: register again with `-n` and that name, and tell the user which name you used. The default name is the slugged title, and the JSON says when it was defaulted (`name_defaulted`).
113
+ - **Changing a quiz** is explicit. Export the source with `quiz library share NAME --source -o DIR`, edit the file it prints, run `quiz check`, then `quiz library register DIR/NAME/FILE.md --update NAME`. Never guess which quiz a file belongs to, and never edit files inside the library. Registering the same file again is safe: `unchanged`, or `replaced` while nobody has taken that version.
114
+ - **`register --json`** prints `{"status": "registered"|"replaced"|"new-version"|"updated-assets"|"unchanged", "id", "name", "name_defaulted", "hash", "version_count", "committed", "take": "quiz library take <name>"}`. **End your message with the `take` line** so the user can start the quiz.
115
+ - **Grading.** Results are in `quiz library path NAME`/results, one JSON file per attempt, named by date and time. Grade with `quiz library grade NAME --json` (the newest attempt, or `--attempt STAMP`): it finds the version the attempt was taken on and writes `reports/<stamp>-report.json`, plus an empty `reports/<stamp>-grades.json` when essays or code need grading. Fill that template, then run `quiz library grade NAME --grades reports/<stamp>-grades.json --json`. Reports go in `reports/`, never `results/`. `quiz library stats NAME [--json]` shows progress over all attempts. The results files are the learner's: read them, do not edit them.
116
+ - **`rm` and `rename` are the user's call.** Never run `quiz library rm` or `rename` unless the user asked for that exact quiz. `rm` deletes the quiz with its versions and results (git can revert it); never pass `--yes` or `--force` on your own.
117
+ - **Never push**, and never run git commands in the library. Results hold the learner's answers and the snapshots hold the answer keys, so any remote the user adds must be private; that is their step.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hung-nd233960
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.5
2
+ Name: fullmark
3
+ Version: 0.10.0
4
+ Summary: Turn a Markdown file into a self-contained, self-grading HTML quiz.
5
+ Project-URL: Homepage, https://github.com/Hung-nd233960/fullmark
6
+ Project-URL: Repository, https://github.com/Hung-nd233960/fullmark
7
+ Project-URL: Issues, https://github.com/Hung-nd233960/fullmark/issues
8
+ Author: Hung-nd233960
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: education,html,markdown,offline,quiz,self-grading
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Education
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Education
20
+ Requires-Python: >=3.13
21
+ Requires-Dist: jinja2>=3
22
+ Requires-Dist: markdown-it-py>=3
23
+ Requires-Dist: mdit-py-plugins>=0.4
24
+ Requires-Dist: pydantic>=2
25
+ Requires-Dist: pygments>=2.17
26
+ Requires-Dist: pyyaml>=6
27
+ Requires-Dist: rich>=13
28
+ Requires-Dist: tomli-w>=1.0
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Fullmark Alchemist
32
+
33
+ `fullmark`: Markdown in, a self-grading offline quiz out.
34
+
35
+ *To obtain full marks, something of equal value must be studied. That is the law of Equivalent Exchange.*
36
+
37
+ ## How this was made
38
+
39
+ Fullmark was vibe-coded: written almost entirely by AI coding agents (Claude) from my prompts, plans and reviews, with me steering and testing it on my own coursework. The test suite (about 700 tests, including real-browser and accessibility checks) is what keeps it honest. Read the code before trusting it with anything important.
40
+
41
+ ## Install
42
+
43
+ ```sh
44
+ uv tool install fullmark # or: pipx install fullmark
45
+ quiz --help
46
+ ```
47
+
48
+ The command is `quiz`. Where another program already owns `quiz` (Debian and Ubuntu's `bsdgames` ships an old trivia game by that name), use `fullmark`, which runs the same program.
49
+
50
+ From source, in a clone of this repository:
51
+
52
+ ```sh
53
+ uv tool install --editable .
54
+ ```
55
+
56
+ This project was called quizmd before 0.10.0. Version 0.10.0 does not read quizmd results or libraries (the data formats, the config folder and the environment variables were all renamed); rebuild them from your Markdown sources.
57
+
58
+ After pulling a version that adds a dependency (0.9.0 added `tomli-w` and `rich`), reinstall the tool, or the installed `quiz` fails with `ModuleNotFoundError`:
59
+
60
+ ```sh
61
+ uv tool install --editable . --reinstall
62
+ ```
63
+
64
+ For development: `uv sync`, then `uv run pytest` (the browser tests need Chromium: `uv run playwright install chromium`).
65
+ How a release is made: [docs/RELEASING.md](docs/RELEASING.md).
66
+
67
+ ## What it is
68
+
69
+ Turn a Markdown file into **one self-contained HTML quiz** that opens by double-click, grades itself
70
+ offline, and exports the learner's answers as JSON so an AI can review them. The page follows the
71
+ muted pastel palette: soft paper in light mode, soft slate in dark mode. A quiz can lock its theme (`theme: light`), as the IELTS example does to resemble the real exam screen.
72
+
73
+ Docs: [agent guide](AGENTS.md), [user guide](docs/GUIDE.md) ([HTML](docs/GUIDE.html)), [design spec](docs/SPEC.md), and a [quiz about this project](docs/fullmark-project.html).
74
+
75
+ ## Fullmark and Anki
76
+
77
+ They do different jobs.
78
+
79
+ | | Anki | Fullmark |
80
+ |---|---|---|
81
+ | Best for | Remembering facts: definitions, formulas, anatomy | Exam practice: worked problems, mixed question types, full mock tests |
82
+ | Unit | One card | One quiz, with sections and points |
83
+ | Scheduling | Spaced repetition | None. You decide when to retake; the library keeps your scores and weak topics |
84
+ | Questions | Front and back, cloze | Choice, numeric with tolerance, blanks, matching, ordering, essays and code graded by an AI |
85
+ | Where | Apps on every device, with sync | One HTML file that opens anywhere, no account |
86
+
87
+ Use both: Anki for what you must recall, Fullmark to check you can use it under exam conditions.
88
+
89
+ ## The loop
90
+
91
+ 1. An AI reads study material and writes a quiz in fullmark Markdown (`quiz guide` prints the format).
92
+ 2. `quiz check quiz.md` validates it with line-numbered errors; fix until it passes.
93
+ 3. `quiz build quiz.md --open` produces `quiz.html`.
94
+ 4. The learner answers, presses **Submit**, then **Export answers** (`results.json`).
95
+ 5. `quiz grade results.json --quiz quiz.md` writes a Markdown report; an AI grades essays and code from it.
96
+
97
+ ## Two layers: core and library
98
+
99
+ - **Core** (`quiz check`, `build`, `serve`, `grade`, ...) works on files: Markdown in, one HTML page or a JSON file out. It needs no setup and no config, and it is all you need to make a quiz and share it.
100
+ - **Library** (`quiz library ...`, new in 0.9.0, design in [docs/plans/LIBRARY_PLAN.md](docs/plans/LIBRARY_PLAN.md)) owns your own quizzes in one folder under git: register a quiz once (the source can be a throwaway file), take it with `quiz library take NAME`, and every result is filed next to the version you answered. Pages are built on the spot, so they always use the installed fullmark. It calls core and adds nothing to the pages, so a shared page stays the normal offline file. Core never depends on it.
101
+
102
+ ## Core commands
103
+
104
+ | Command | What it does |
105
+ |---|---|
106
+ | `quiz check FILE [--json] [--strict] [--review REVIEW]` | Validate. Exit 0 = no errors (warnings allowed), 1 = errors, 2 = usage or I/O problem |
107
+ | `quiz build FILE [-o OUT] [--open] [--strict]` | Check, then write the single-file HTML; `--strict` fails on warnings and writes nothing |
108
+ | `quiz watch FILE [-o OUT] [--open] [--serve]` | Rebuild whenever the quiz or its `assets/` change; keeps the last good build when a save has errors. Reload the page after each rebuild, or add `--serve` to serve it on 127.0.0.1 and reload the browser automatically (only the served copy gets the reload code; the file you hand out is unchanged) |
109
+ | `quiz serve FILE [--out RESULTS] [--once] [--timeout SECONDS] [--open] [--port N] [--autosave PROGRESS] [--json]` | Serve the page on 127.0.0.1; **Finish** or **Submit** sends the results back and the server writes the JSON file, then exits (`--once` is the default when stdout is not a terminal; exit 2 on timeout). For questionnaires that a tool or an agent is waiting on. `--autosave` keeps an attempt in progress so it resumes after the server or browser closes |
110
+ | `quiz review FILE [--out REVIEW] [--since REVIEW] [--once] [--open] [--timeout SECONDS] [--port N] [--json]` | The learner checks each question (keep, fix or drop) in a form; **Finish** writes `review.json`. Then `quiz check FILE --review review.json` fails until the flagged questions are fixed |
111
+ | `quiz brief DIR [--purpose P] [--scope S] [--count N] [--web] [--accept-defaults] [--json] ...` | Write `brief.yaml` and `content/` for a quiz an agent will write. A missing purpose or scope exits 3; `--web` asks in a form. `quiz check` then warns when the quiz does not match the brief |
112
+ | `quiz grade RESULTS [--quiz FILE] [-o OUT] [--grades GRADES] [--json]` | Re-grade in Python (the authority) and write the report; `--json` writes `*-report.json` instead and an empty `*-grades.json` template; `--grades` folds a filled-in file into the report |
113
+ | `quiz stats RESULTS... [--quiz FILE] [--json]` | Progress across several attempts at one quiz: scores by date, percent per topic, the weakest topics |
114
+ | `quiz inspect FILE [--json] [--review REVIEW]` | Summarize a quiz: types, points, sections, tags, answer positions; with `--review`, counts of kept, to fix and to drop |
115
+ | `quiz new TEMPLATE [PATH]` | Copy `calculus`, `rust`, `ielts`, `medical`, `exam`, `survey` or `blank` plus its assets; never overwrites |
116
+ | `quiz templates` | List the templates |
117
+ | `quiz guide` | Print the authoring reference |
118
+ | `quiz agent-guide` | Print the instructions for AI agents (`AGENTS.md`) |
119
+
120
+ ## Library commands
121
+
122
+ Eleven commands, all under `quiz library`. Exit codes: 0 done, 1 problems found (or a refused quiz), 2 usage error or nothing to work on, 3 needs input from the user (`register` with no library). The design is in [docs/plans/LIBRARY_PLAN.md](docs/plans/LIBRARY_PLAN.md).
123
+
124
+ | Command | What it does |
125
+ |---|---|
126
+ | `quiz library init [PATH] [--force] [--no-git] [--json]` | Make PATH (default: the current folder) this machine's library, `git init` it, and record it in the config (`%APPDATA%\fullmark\config.toml` on Windows, `~/.config/fullmark/config.toml` on Linux). A new, missing or empty folder is set up; an existing library is adopted; a folder with other files is **refused** (exit 2, nothing written; use a new folder such as `PATH/fullmark-library`). Switching from another library needs `--force` |
127
+ | `quiz library status [--json] [--plain]` | Diagnostics: config, library path, git (repo, uncommitted changes, remote), counts, snapshots whose content no longer matches their hash, orphan files. Colored in a terminal; `--plain` or `NO_COLOR` gives plain text. Exit 0 healthy, 1 problems, 2 no library |
128
+ | `quiz library register FILE.md [-n NAME \| --update NAME] [--strict] [--review REVIEW] [--json]` | Check the quiz, then copy its source, assets and brief into the library (no HTML is stored) and commit. The source can then be deleted. `--update` adds a version to an existing quiz. The last step when an agent writes a quiz; exits 3 if no library is set up |
129
+ | `quiz library list [--json] [--plain]` | Every quiz with versions, attempts, last score, and a note when its original source still exists and has changed |
130
+ | `quiz library take NAME [--no-open] [--timeout SECONDS] [--port N] [--json]` | Build the page on the spot with the installed fullmark, serve it, save the result into the library and commit; resume survives closing the tab |
131
+ | `quiz library share NAME [-o DIR] [--version H] [--source]` | Build the clean offline page to share (with `quiz build`, the only way to get a standalone HTML file); `--source` writes the editable source folder instead, with the original file name, its assets and the brief |
132
+ | `quiz library grade NAME [--attempt STAMP] [--grades FILE] [--json]` | Grade an attempt (the newest by default) against the version it was taken on and write the report into `quizzes/<id>/reports/` (never `results/`); `--json` also writes the grades template there. Commits the report |
133
+ | `quiz library stats NAME [--json]` | Progress over every attempt: scores by date, percent per topic, weakest topics. Uses the grades each attempt was made with when the attempts span several versions. Writes nothing |
134
+ | `quiz library rename OLD NEW [--json]` | Change a quiz's name (its id and folder stay) and commit |
135
+ | `quiz library rm NAME [--yes] [--force] [--json]` | **Delete** a quiz with its versions and results, and commit, so `git -C LIBRARY revert HEAD` brings it back. Refused when the library is not its own git repository or the quiz folder has uncommitted changes (`--force` skips both); asks for confirmation unless `--yes`. Images stay in `assets/` |
136
+ | `quiz library path [NAME]` | Print the library folder or a quiz's folder; results and snapshots are plain files there |
137
+
138
+ Later, when needed: `sync`, `replay`, `--web` (see [docs/PLANNED.md](docs/PLANNED.md)). The work order for `grade`, `stats`, `rename` and `rm` is [docs/plans/LIBRARY_HOUSEKEEPING_PLAN.md](docs/plans/LIBRARY_HOUSEKEEPING_PLAN.md).
139
+
140
+ ### Backups
141
+
142
+ The library is a plain folder under local git history, which protects against mistakes (a hand-edited snapshot, a broken file) but not against losing the drive. fullmark commits after each `init`, `register` and `take`, and it **never pushes**. Results hold your answers and the snapshots hold the answer keys, so push only to a **private** remote. To back up automatically, add a `post-commit` hook in the library's `.git/hooks` that runs `git push`; that is your hook, not fullmark's.
143
+
144
+ ## Question types
145
+
146
+ Single choice, multiple answer, true/false, numeric (tolerances, fractions), short answer (accepted
147
+ strings and regexes), fill-in-the-blank (also inside code blocks), labeled fields, essay and code
148
+ (both exported for AI grading). LaTeX math (KaTeX, embedded only when used), highlighted code,
149
+ images and audio are embedded as data URIs. Timers, instant or end-of-quiz feedback, resume after
150
+ reload, and a versioned JSON export are built in. A choice can ask for a few words when picked
151
+ (`- [ ] Something else {input="What is it?"}`); the text is exported and reported but never graded.
152
+
153
+ A minimal quiz:
154
+
155
+ ````markdown
156
+ ---
157
+ title: Capitals
158
+ ---
159
+
160
+ ## What is the capital of France?
161
+
162
+ - [ ] Lyon
163
+ - [x] Paris
164
+
165
+ > [!explanation]
166
+ > Paris has been the capital since 987.
167
+ ````
168
+
169
+ ## Notes
170
+
171
+ - The answer key ships inside the HTML, lightly obfuscated (XOR with a key derived from the quiz hash).
172
+ This stops casual peeking only. It is not an exam-security feature.
173
+ - The page makes no network requests; a Content Security Policy blocks them, and the browser tests assert it.
174
+ - Project config: `.markdownlint-cli2.jsonc` (the user's rules, minus MD025 and MD028, which the format needs),
175
+ `biome.json` for the runtime JavaScript.
176
+ - Regenerate fixtures and example images:
177
+ `uv run python tests/fixtures/make_fixtures.py`,
178
+ `uv run python tests/make_grading_cases.py`,
179
+ `uv run python src/fullmark/examples/make_assets.py`,
180
+ `uv run python docs/build_docs.py` (rebuilds `docs/GUIDE.html` and `docs/fullmark-project.html`).
181
+ - KaTeX 0.16.47 is vendored in `src/fullmark/vendor/katex/` (MIT, see its `LICENSE`).
@@ -0,0 +1,151 @@
1
+ # Fullmark Alchemist
2
+
3
+ `fullmark`: Markdown in, a self-grading offline quiz out.
4
+
5
+ *To obtain full marks, something of equal value must be studied. That is the law of Equivalent Exchange.*
6
+
7
+ ## How this was made
8
+
9
+ Fullmark was vibe-coded: written almost entirely by AI coding agents (Claude) from my prompts, plans and reviews, with me steering and testing it on my own coursework. The test suite (about 700 tests, including real-browser and accessibility checks) is what keeps it honest. Read the code before trusting it with anything important.
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ uv tool install fullmark # or: pipx install fullmark
15
+ quiz --help
16
+ ```
17
+
18
+ The command is `quiz`. Where another program already owns `quiz` (Debian and Ubuntu's `bsdgames` ships an old trivia game by that name), use `fullmark`, which runs the same program.
19
+
20
+ From source, in a clone of this repository:
21
+
22
+ ```sh
23
+ uv tool install --editable .
24
+ ```
25
+
26
+ This project was called quizmd before 0.10.0. Version 0.10.0 does not read quizmd results or libraries (the data formats, the config folder and the environment variables were all renamed); rebuild them from your Markdown sources.
27
+
28
+ After pulling a version that adds a dependency (0.9.0 added `tomli-w` and `rich`), reinstall the tool, or the installed `quiz` fails with `ModuleNotFoundError`:
29
+
30
+ ```sh
31
+ uv tool install --editable . --reinstall
32
+ ```
33
+
34
+ For development: `uv sync`, then `uv run pytest` (the browser tests need Chromium: `uv run playwright install chromium`).
35
+ How a release is made: [docs/RELEASING.md](docs/RELEASING.md).
36
+
37
+ ## What it is
38
+
39
+ Turn a Markdown file into **one self-contained HTML quiz** that opens by double-click, grades itself
40
+ offline, and exports the learner's answers as JSON so an AI can review them. The page follows the
41
+ muted pastel palette: soft paper in light mode, soft slate in dark mode. A quiz can lock its theme (`theme: light`), as the IELTS example does to resemble the real exam screen.
42
+
43
+ Docs: [agent guide](AGENTS.md), [user guide](docs/GUIDE.md) ([HTML](docs/GUIDE.html)), [design spec](docs/SPEC.md), and a [quiz about this project](docs/fullmark-project.html).
44
+
45
+ ## Fullmark and Anki
46
+
47
+ They do different jobs.
48
+
49
+ | | Anki | Fullmark |
50
+ |---|---|---|
51
+ | Best for | Remembering facts: definitions, formulas, anatomy | Exam practice: worked problems, mixed question types, full mock tests |
52
+ | Unit | One card | One quiz, with sections and points |
53
+ | Scheduling | Spaced repetition | None. You decide when to retake; the library keeps your scores and weak topics |
54
+ | Questions | Front and back, cloze | Choice, numeric with tolerance, blanks, matching, ordering, essays and code graded by an AI |
55
+ | Where | Apps on every device, with sync | One HTML file that opens anywhere, no account |
56
+
57
+ Use both: Anki for what you must recall, Fullmark to check you can use it under exam conditions.
58
+
59
+ ## The loop
60
+
61
+ 1. An AI reads study material and writes a quiz in fullmark Markdown (`quiz guide` prints the format).
62
+ 2. `quiz check quiz.md` validates it with line-numbered errors; fix until it passes.
63
+ 3. `quiz build quiz.md --open` produces `quiz.html`.
64
+ 4. The learner answers, presses **Submit**, then **Export answers** (`results.json`).
65
+ 5. `quiz grade results.json --quiz quiz.md` writes a Markdown report; an AI grades essays and code from it.
66
+
67
+ ## Two layers: core and library
68
+
69
+ - **Core** (`quiz check`, `build`, `serve`, `grade`, ...) works on files: Markdown in, one HTML page or a JSON file out. It needs no setup and no config, and it is all you need to make a quiz and share it.
70
+ - **Library** (`quiz library ...`, new in 0.9.0, design in [docs/plans/LIBRARY_PLAN.md](docs/plans/LIBRARY_PLAN.md)) owns your own quizzes in one folder under git: register a quiz once (the source can be a throwaway file), take it with `quiz library take NAME`, and every result is filed next to the version you answered. Pages are built on the spot, so they always use the installed fullmark. It calls core and adds nothing to the pages, so a shared page stays the normal offline file. Core never depends on it.
71
+
72
+ ## Core commands
73
+
74
+ | Command | What it does |
75
+ |---|---|
76
+ | `quiz check FILE [--json] [--strict] [--review REVIEW]` | Validate. Exit 0 = no errors (warnings allowed), 1 = errors, 2 = usage or I/O problem |
77
+ | `quiz build FILE [-o OUT] [--open] [--strict]` | Check, then write the single-file HTML; `--strict` fails on warnings and writes nothing |
78
+ | `quiz watch FILE [-o OUT] [--open] [--serve]` | Rebuild whenever the quiz or its `assets/` change; keeps the last good build when a save has errors. Reload the page after each rebuild, or add `--serve` to serve it on 127.0.0.1 and reload the browser automatically (only the served copy gets the reload code; the file you hand out is unchanged) |
79
+ | `quiz serve FILE [--out RESULTS] [--once] [--timeout SECONDS] [--open] [--port N] [--autosave PROGRESS] [--json]` | Serve the page on 127.0.0.1; **Finish** or **Submit** sends the results back and the server writes the JSON file, then exits (`--once` is the default when stdout is not a terminal; exit 2 on timeout). For questionnaires that a tool or an agent is waiting on. `--autosave` keeps an attempt in progress so it resumes after the server or browser closes |
80
+ | `quiz review FILE [--out REVIEW] [--since REVIEW] [--once] [--open] [--timeout SECONDS] [--port N] [--json]` | The learner checks each question (keep, fix or drop) in a form; **Finish** writes `review.json`. Then `quiz check FILE --review review.json` fails until the flagged questions are fixed |
81
+ | `quiz brief DIR [--purpose P] [--scope S] [--count N] [--web] [--accept-defaults] [--json] ...` | Write `brief.yaml` and `content/` for a quiz an agent will write. A missing purpose or scope exits 3; `--web` asks in a form. `quiz check` then warns when the quiz does not match the brief |
82
+ | `quiz grade RESULTS [--quiz FILE] [-o OUT] [--grades GRADES] [--json]` | Re-grade in Python (the authority) and write the report; `--json` writes `*-report.json` instead and an empty `*-grades.json` template; `--grades` folds a filled-in file into the report |
83
+ | `quiz stats RESULTS... [--quiz FILE] [--json]` | Progress across several attempts at one quiz: scores by date, percent per topic, the weakest topics |
84
+ | `quiz inspect FILE [--json] [--review REVIEW]` | Summarize a quiz: types, points, sections, tags, answer positions; with `--review`, counts of kept, to fix and to drop |
85
+ | `quiz new TEMPLATE [PATH]` | Copy `calculus`, `rust`, `ielts`, `medical`, `exam`, `survey` or `blank` plus its assets; never overwrites |
86
+ | `quiz templates` | List the templates |
87
+ | `quiz guide` | Print the authoring reference |
88
+ | `quiz agent-guide` | Print the instructions for AI agents (`AGENTS.md`) |
89
+
90
+ ## Library commands
91
+
92
+ Eleven commands, all under `quiz library`. Exit codes: 0 done, 1 problems found (or a refused quiz), 2 usage error or nothing to work on, 3 needs input from the user (`register` with no library). The design is in [docs/plans/LIBRARY_PLAN.md](docs/plans/LIBRARY_PLAN.md).
93
+
94
+ | Command | What it does |
95
+ |---|---|
96
+ | `quiz library init [PATH] [--force] [--no-git] [--json]` | Make PATH (default: the current folder) this machine's library, `git init` it, and record it in the config (`%APPDATA%\fullmark\config.toml` on Windows, `~/.config/fullmark/config.toml` on Linux). A new, missing or empty folder is set up; an existing library is adopted; a folder with other files is **refused** (exit 2, nothing written; use a new folder such as `PATH/fullmark-library`). Switching from another library needs `--force` |
97
+ | `quiz library status [--json] [--plain]` | Diagnostics: config, library path, git (repo, uncommitted changes, remote), counts, snapshots whose content no longer matches their hash, orphan files. Colored in a terminal; `--plain` or `NO_COLOR` gives plain text. Exit 0 healthy, 1 problems, 2 no library |
98
+ | `quiz library register FILE.md [-n NAME \| --update NAME] [--strict] [--review REVIEW] [--json]` | Check the quiz, then copy its source, assets and brief into the library (no HTML is stored) and commit. The source can then be deleted. `--update` adds a version to an existing quiz. The last step when an agent writes a quiz; exits 3 if no library is set up |
99
+ | `quiz library list [--json] [--plain]` | Every quiz with versions, attempts, last score, and a note when its original source still exists and has changed |
100
+ | `quiz library take NAME [--no-open] [--timeout SECONDS] [--port N] [--json]` | Build the page on the spot with the installed fullmark, serve it, save the result into the library and commit; resume survives closing the tab |
101
+ | `quiz library share NAME [-o DIR] [--version H] [--source]` | Build the clean offline page to share (with `quiz build`, the only way to get a standalone HTML file); `--source` writes the editable source folder instead, with the original file name, its assets and the brief |
102
+ | `quiz library grade NAME [--attempt STAMP] [--grades FILE] [--json]` | Grade an attempt (the newest by default) against the version it was taken on and write the report into `quizzes/<id>/reports/` (never `results/`); `--json` also writes the grades template there. Commits the report |
103
+ | `quiz library stats NAME [--json]` | Progress over every attempt: scores by date, percent per topic, weakest topics. Uses the grades each attempt was made with when the attempts span several versions. Writes nothing |
104
+ | `quiz library rename OLD NEW [--json]` | Change a quiz's name (its id and folder stay) and commit |
105
+ | `quiz library rm NAME [--yes] [--force] [--json]` | **Delete** a quiz with its versions and results, and commit, so `git -C LIBRARY revert HEAD` brings it back. Refused when the library is not its own git repository or the quiz folder has uncommitted changes (`--force` skips both); asks for confirmation unless `--yes`. Images stay in `assets/` |
106
+ | `quiz library path [NAME]` | Print the library folder or a quiz's folder; results and snapshots are plain files there |
107
+
108
+ Later, when needed: `sync`, `replay`, `--web` (see [docs/PLANNED.md](docs/PLANNED.md)). The work order for `grade`, `stats`, `rename` and `rm` is [docs/plans/LIBRARY_HOUSEKEEPING_PLAN.md](docs/plans/LIBRARY_HOUSEKEEPING_PLAN.md).
109
+
110
+ ### Backups
111
+
112
+ The library is a plain folder under local git history, which protects against mistakes (a hand-edited snapshot, a broken file) but not against losing the drive. fullmark commits after each `init`, `register` and `take`, and it **never pushes**. Results hold your answers and the snapshots hold the answer keys, so push only to a **private** remote. To back up automatically, add a `post-commit` hook in the library's `.git/hooks` that runs `git push`; that is your hook, not fullmark's.
113
+
114
+ ## Question types
115
+
116
+ Single choice, multiple answer, true/false, numeric (tolerances, fractions), short answer (accepted
117
+ strings and regexes), fill-in-the-blank (also inside code blocks), labeled fields, essay and code
118
+ (both exported for AI grading). LaTeX math (KaTeX, embedded only when used), highlighted code,
119
+ images and audio are embedded as data URIs. Timers, instant or end-of-quiz feedback, resume after
120
+ reload, and a versioned JSON export are built in. A choice can ask for a few words when picked
121
+ (`- [ ] Something else {input="What is it?"}`); the text is exported and reported but never graded.
122
+
123
+ A minimal quiz:
124
+
125
+ ````markdown
126
+ ---
127
+ title: Capitals
128
+ ---
129
+
130
+ ## What is the capital of France?
131
+
132
+ - [ ] Lyon
133
+ - [x] Paris
134
+
135
+ > [!explanation]
136
+ > Paris has been the capital since 987.
137
+ ````
138
+
139
+ ## Notes
140
+
141
+ - The answer key ships inside the HTML, lightly obfuscated (XOR with a key derived from the quiz hash).
142
+ This stops casual peeking only. It is not an exam-security feature.
143
+ - The page makes no network requests; a Content Security Policy blocks them, and the browser tests assert it.
144
+ - Project config: `.markdownlint-cli2.jsonc` (the user's rules, minus MD025 and MD028, which the format needs),
145
+ `biome.json` for the runtime JavaScript.
146
+ - Regenerate fixtures and example images:
147
+ `uv run python tests/fixtures/make_fixtures.py`,
148
+ `uv run python tests/make_grading_cases.py`,
149
+ `uv run python src/fullmark/examples/make_assets.py`,
150
+ `uv run python docs/build_docs.py` (rebuilds `docs/GUIDE.html` and `docs/fullmark-project.html`).
151
+ - KaTeX 0.16.47 is vendored in `src/fullmark/vendor/katex/` (MIT, see its `LICENSE`).