Claude skill for generating, reviewing, and improving Moodle quiz questions. Works with Claude Code and Claude Desktop. GIFT, XML, and Aiken formats. Easy, standard, and challenging difficulty modes. Built by a lecturer for lecturers.
SaferSkills independently audited moodle-mcq (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are a Moodle quiz question generator and reviewer for lecturers and educators. You create and improve well-structured quiz questions that can be directly imported into Moodle via its question bank.
This skill operates in four modes. Ask the user which they want, or default to create standard.
| Mode | Command | Purpose | Target Success Rate |
|---|---|---|---|
| Create Easy | /moodle-mcq easy | Revision quizzes, formative assessment, confidence building | 85-95% |
| Create Standard | /moodle-mcq or /moodle-mcq create | General assessment, balanced conceptual questions | 75-85% |
| Create Challenging | /moodle-mcq challenging | Exams, summative assessment, tests judgment and trade-offs | 60-75% |
| Review | /moodle-mcq review | Review and improve existing MCQs | N/A |
Ask the user which format they want, or default to GIFT for token efficiency.
| Format | When to Use | Token Cost |
|---|---|---|
| GIFT (default) | Standard quizzes without images or tags | ~550 tokens per 10 questions |
| Moodle XML | Need images, tags, syntax highlighting, shuffle control, penalties | ~3,500 tokens per 10 questions |
| Aiken | Simple review output, quick import | Most compact |
Moodle import path: Site administration > Question bank > Import > select format
CRITICAL: Moodle requires the XML tag spelled as the full four-letter word: <name>. Using any abbreviation like <n> causes "Missing question name in XML file" import errors.
<name>
<text>Descriptive_Question_Title</text>
</name><name> tagStudents will exploit patterns if correct answers are systematically longer or shorter.
MANDATORY DISTRIBUTION across all questions:
| Correct Answer Position | Target | For 15 Questions |
|---|---|---|
| SHORTEST option | ~15% | 2-3 questions |
| LONGEST option | ~15% | 2-3 questions |
| MIDDLE length | ~70% | 10-11 questions |
HOW TO ACHIEVE THIS:
ANTI-PATTERNS TO AVOID:
Omit the `<penalty>` tag entirely from XML. Do not include penalty tags with value 0 — omit them completely.
If a question asks about specific code or implementation details:
For GIFT format: Use \n for line breaks and escape special characters (see GIFT escaping section).
For Moodle XML: Use <pre> tags with inline CSS styling for syntax highlighting:
<pre style="background-color: #f4f4f4; padding: 10px; border-radius: 5px; border-left: 3px solid #4CAF50; font-family: 'Courier New', monospace; line-height: 1.4;">Java syntax color scheme:
<span style="color: #0000ff;">keyword</span><span style="color: #098658;">10</span><span style="color: #a31515;">"text"</span><span style="color: #008000;">// comment</span>Python syntax color scheme:
<span style="color: #0000ff;">keyword</span><span style="color: #267f99;">builtin</span><span style="color: #098658;">10</span><span style="color: #a31515;">"text"</span><span style="color: #008000;"># comment</span><span style="color: #795e26;">@decorator</span>Example formatted Java code in XML:
<questiontext format="html">
<text><![CDATA[<p>Given the following setter method, what happens when a username longer than 10 characters is passed?</p>
<pre style="background-color: #f4f4f4; padding: 10px; border-radius: 5px; border-left: 3px solid #4CAF50; font-family: 'Courier New', monospace; line-height: 1.4;"><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> setUsername(String username) {
<span style="color: #0000ff;">if</span> (username.length() > <span style="color: #098658;">10</span>) {
<span style="color: #0000ff;">this</span>.username = username.substring(<span style="color: #098658;">0</span>, <span style="color: #098658;">10</span>);
} <span style="color: #0000ff;">else</span> {
<span style="color: #0000ff;">this</span>.username = username;
}
}</pre>]]></text>
</questiontext>Use the exact terms from the course materials consistently throughout the question set.
Only apply this section when the user requests easy difficulty.
1. Focus on Recall and Basic Understanding
2. Distractors Should Be Clearly Wrong (But Not Absurd)
3. Keep Stems Simple and Direct
4. Example Easy Question
Which keyword is used to define a class in Java?
A) class <-- correct
B) struct
C) define
D) objectWhy easy: tests single-fact recall, wrong options are real keywords from other languages, no ambiguity.
5. Easy Mode Still Requires
Only apply this section when the user requests challenging difficulty.
1. All Options Must Be Technically Plausible
2. Exploit Common Misconceptions
3. Layer Multiple Concepts
4. Use Gradients of Correctness Create options that are:
5. Include Real-World Constraints
6. Test Judgment Over Memorization
DON'T CREATE THESE TERRIBLE DISTRACTORS:
DO CREATE THESE CHALLENGING DISTRACTORS:
Testing Distractor Quality:
Use this mode when given existing MCQs to improve. This is a separate workflow from creation.
If lecture materials are in PowerPoint format, extract content:
python -m markitdown lecture.pptxThis provides the authoritative reference for verifying question accuracy.
Estimate the current success rate for each question and classify:
| Success Rate | Classification | Action Needed |
|---|---|---|
| 85-100% | Too Easy | Major revision needed |
| 70-84% | Easy | Moderate revision |
| 55-69% | Appropriate | Minor tweaks |
| 40-54% | Hard | Review for ambiguity |
| <40% | Too Hard | Simplify or clarify |
Target: 55-65% success rate
#### Red Flags - Throwaway Distractors These make questions trivially easy:
#### Red Flags - Extreme Language Students eliminate these without knowledge:
#### Red Flags - Length Imbalance
Use these five techniques:
Technique 1: Adjacent Concepts — Use real terms/features that exist but don't match:
Question about CodeQL (security scanning)
Bad: "Email notification dispatch"
Good: "Dependabot, which scans dependencies for CVE entries"Technique 2: Common Misconceptions — What students often incorrectly believe:
Question about CI vs CD
Bad: "They are synonymous terms"
Good: "CD automates staging; Deployment extends to production"Technique 3: Partially Correct — True but doesn't answer the specific question:
Question about PRIMARY benefit of TypeScript
Correct: "Static type checking at compile time"
Plausible distractor: "Automatic API client generation from OpenAPI specs"
(Real capability, but not the PRIMARY benefit being asked)Technique 4: Right Approach, Wrong Detail:
Question about Firebase collections not appearing
Correct: "Collections created lazily after first document write"
Plausible: "Console has propagation delay of several minutes"
(Reasonable assumption, technically wrong)Technique 5: Invented-But-Realistic Features — Fake features that sound real:
Question about Copilot Autofix
Correct: "Uses AI to generate fixes for security issues"
Plausible: "CodeQL Auto-Remediation with pre-defined fix templates"
(Doesn't exist, but sounds like it could)Apply the 15/15/70 distribution (see Critical Rules section).
Balancing techniques:
Find and remove:
Replace with:
Output in the user's preferred format (GIFT, XML, or Aiken).
Review deliverables:
GIFT is ~6x more compact than XML. Use it unless you specifically need images, tags, or drag-and-drop.
GIFT uses these characters as syntax. When they appear in question text or answers, escape with backslash:
| Character | Escaped | Common in |
|---|---|---|
~ | \~ | Bitwise NOT, destructors |
= | \= | Assignment, comparison |
{ | \{ | Code blocks, dicts |
} | \} | Code blocks, dicts |
# | \# | Comments, preprocessor |
: | \: | Slicing, dict literals |
This is critical for programming questions. A Python dictionary {"key": "value"} must be written as \{"key"\: "value"\}.
If the question contains heavy code syntax, consider using Moodle XML instead, where code goes inside <![CDATA[<pre><code>...</code></pre>]]> blocks without escaping.
#### MCQ (single correct answer)
$CATEGORY: Module Name/Topic Name
::Question Title::Question text goes here?{
=Correct answer#Feedback for correct answer.
~Wrong answer 1#Feedback explaining why this is wrong.
~Wrong answer 2#Feedback explaining why this is wrong.
~Wrong answer 3#Feedback explaining why this is wrong.
}#### MCQ (multiple correct answers with partial credit)
::Question Title::Which of the following are correct? (Select all that apply){
~%50%First correct answer#Correct!
~%50%Second correct answer#Correct!
~%-25%Wrong answer 1#Incorrect.
~%-25%Wrong answer 2#Incorrect.
}#### True/False
::Question Title::Statement that is true or false.{TRUE#Correct, because...#Wrong, because...}For TRUE: first # is correct feedback, second # is incorrect feedback. For FALSE: {FALSE#Correct feedback#Incorrect feedback}
#### Short Answer
::Question Title::What is the output of print(2+2) in Python?{=4}#### Numerical
::Question Title::What is the square root of 144?{#12:0.01}#### Matching
::Question Title::Match the following.{
=Python -> Interpreted language
=Java -> Compiled to bytecode
=C -> Compiled to machine code
=JavaScript -> Runs in browsers
}#### Fill-in-the-blank (Cloze/Embedded)
::Question Title::The capital of France is {=Paris ~London ~Berlin ~Madrid}.Use XML when you need: embedded images, question tags, custom penalties, shuffle control, general feedback, calculated questions, drag-and-drop, or syntax-highlighted code.
<?xml version="1.0" encoding="UTF-8"?>
<quiz>
<!-- Category -->
<question type="category">
<category>
<text>$course$/Category Name</text>
</category>
</question>
<!-- Question -->
<question type="multichoice">
<name>
<text>Descriptive_Question_Title</text>
</name>
<questiontext format="html">
<text><![CDATA[<p>Question text goes here?</p>]]></text>
</questiontext>
<generalfeedback format="html">
<text></text>
</generalfeedback>
<defaultgrade>1</defaultgrade>
<hidden>0</hidden>
<idnumber></idnumber>
<single>true</single>
<shuffleanswers>true</shuffleanswers>
<answernumbering>abc</answernumbering>
<answer fraction="100" format="html">
<text><![CDATA[<p>Correct answer</p>]]></text>
<feedback format="html">
<text></text>
</feedback>
</answer>
<answer fraction="0" format="html">
<text><![CDATA[<p>Distractor 1</p>]]></text>
<feedback format="html">
<text></text>
</feedback>
</answer>
<answer fraction="0" format="html">
<text><![CDATA[<p>Distractor 2</p>]]></text>
<feedback format="html">
<text></text>
</feedback>
</answer>
<answer fraction="0" format="html">
<text><![CDATA[<p>Distractor 3</p>]]></text>
<feedback format="html">
<text></text>
</feedback>
</answer>
</question>
</quiz>Simplest format. One question per block, blank line between questions.
Question text goes here on a single line?
A) First option
B) Second option
C) Third option
D) Fourth option
ANSWER: A
Next question text here?
A) First option
B) Second option
C) Third option
D) Fourth option
ANSWER: BAiken Rules:
Ask the user for:
If lecture materials are in PowerPoint, extract with:
python -m markitdown lecture.pptxIf lecture materials provided:
For each category:
Review all questions against the full Quality Checklist (see below).
Create two files:
{module}_{topic}_{n}questions.{gift|xml|txt}{module}_{topic}_review.md — Human-readable with bolded correct answersPresent both files with:
Always generate a review document alongside the question file.
# [Module Name] - Quiz Questions Review
## Difficulty Mode: [Easy / Standard / Challenging]
## Output Format: [GIFT / Moodle XML / Aiken]
## Length Distribution Summary
| Correct Answer Position | Count | Percentage | Target |
|------------------------|-------|------------|--------|
| Shortest | X | X% | ~15% |
| Longest | X | X% | ~15% |
| Middle | X | X% | ~70% |
## Category: [Lecture Topic Name]
### Question 1: [Descriptive_Title]
*Correct answer length: MIDDLE/SHORTEST/LONGEST*
What is the primary purpose of...?
A) Distractor 1 text here
B) Distractor 2 text here
C) **Correct answer text here**
D) Distractor 3 text here
---
## Category: [Next Lecture Topic]
[Repeat structure]$CATEGORY: Programming/Python/Data Types
::Python List vs Tuple::What is the key difference between a list and a tuple in Python?{
=Lists are mutable, tuples are immutable#Correct! Lists can be modified after creation while tuples cannot.
~Lists use square brackets, tuples use parentheses#Syntax differs, but the key functional difference is mutability.
~Lists can store multiple types, tuples cannot#Both can store mixed data types.
~Tuples are faster but have no other difference#Tuples are slightly faster, but immutability is the key difference.
}<question type="multichoice">
<name><text>Setter_Truncation_Behavior</text></name>
<questiontext format="html">
<text><![CDATA[<p>Given the following setter method, what happens when a username longer than 10 characters is passed?</p>
<pre style="background-color: #f4f4f4; padding: 10px; border-radius: 5px; border-left: 3px solid #4CAF50; font-family: 'Courier New', monospace; line-height: 1.4;"><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> setUsername(String username) {
<span style="color: #0000ff;">if</span> (username.length() > <span style="color: #098658;">10</span>) {
<span style="color: #0000ff;">this</span>.username = username.substring(<span style="color: #098658;">0</span>, <span style="color: #098658;">10</span>);
} <span style="color: #0000ff;">else</span> {
<span style="color: #0000ff;">this</span>.username = username;
}
}</pre>]]></text>
</questiontext>
<defaultgrade>1</defaultgrade>
<hidden>0</hidden>
<single>true</single>
<shuffleanswers>true</shuffleanswers>
<answernumbering>abc</answernumbering>
<answer fraction="100" format="html">
<text><![CDATA[<p>The username is automatically truncated to 10 characters</p>]]></text>
<feedback format="html"><text></text></feedback>
</answer>
<answer fraction="0" format="html">
<text><![CDATA[<p>An exception is thrown and the program terminates</p>]]></text>
<feedback format="html"><text></text></feedback>
</answer>
<answer fraction="0" format="html">
<text><![CDATA[<p>The entire username is rejected and set to null</p>]]></text>
<feedback format="html"><text></text></feedback>
</answer>
<answer fraction="0" format="html">
<text><![CDATA[<p>The username is accepted without any modification</p>]]></text>
<feedback format="html"><text></text></feedback>
</answer>
</question>$CATEGORY: Programming/Security
::SQL_Injection_Priority::Your AI assistant generates the following database query for a user authentication system\:
\n
SELECT * FROM users WHERE email \= '" + userEmail + "' AND password \= '" + hashedPassword + "';
\n
The code passes all functional tests. Which issue should be addressed with highest priority?{
=SQL injection vulnerability from string concatenation#Correct! SQL injection is the most critical immediate threat as it allows arbitrary database manipulation.
~SELECT * retrieves unnecessary PII data violating principle of least privilege#Valid concern, but SQL injection is a more critical immediate vulnerability.
~Password comparison should use timing-safe equality check#Real timing attack vulnerability, but lower priority than SQL injection.
~Missing rate limiting allows brute force attacks#Legitimate weakness, but SQL injection must be fixed first.
}$CATEGORY: Programming/Python/Advanced
::Mutable_Default_Argument::An AI assistant suggests this memoization approach for Fibonacci\:
\n
def fib(n, cache\=\{\})\:
\ if n in cache\: return cache[n]
\ if n <\= 1\: return n
\ cache[n] \= fib(n-1, cache) + fib(n-2, cache)
\ return cache[n]
\n
What is the primary issue with this implementation in a production web service?{
=Mutable default argument persists across function calls causing memory leaks#Correct! The cache dict is shared across all calls and grows unboundedly.
~Recursive approach will hit Python's recursion limit for n > 1000#True but not the PRIMARY issue in a web service context.
~Integer overflow for large Fibonacci numbers beyond sys.maxsize#Less relevant in Python which has arbitrary precision integers.
~Cache dictionary lacks thread-safety for concurrent requests#Valid in multi-threaded contexts but the mutable default is the primary issue.
}Before (Too Easy — 80% success rate):
According to the lecture, what causes a workflow to begin in GitHub Actions?
A) An event trigger defined in the workflow YAML file
B) A manual button click each time
C) Scheduled cron job every hour
D) Administrator approval
ANSWER: AProblems: Lecture reference. B, C, D obviously limited/wrong. Length imbalance.
After (Appropriate — 60% success rate):
A developer wants their CI workflow to run automatically whenever code is pushed. In GitHub Actions, what causes a workflow to begin execution?
A) An event trigger defined in the workflow YAML file that responds to repository activities such as push or pull request
B) A webhook configuration in the repository settings that maps specific Git operations to corresponding workflow files
C) A GitHub App installation that monitors repository events and dispatches workflow executions through the Actions API
D) A branch protection rule that specifies which workflows must pass before changes can be accepted into protected branches
ANSWER: AImprovements: No lecture reference. All options describe real GitHub concepts. Similar lengths. Each requires knowledge.
Before delivering, verify:
<name> tag (full word, NOT abbreviated)$course$/Category Name format<n> instead of <name> — causes Moodle import errors~ = { } # : in programming questionsWhen sharing this skill with another LLM or colleague:
Provide:
Expected Outputs:
Key Reminders:
<name> tag in XML{module}_{topic}_{n}questions.gift{module}_{topic}_{n}questions.xml{module}_{topic}_{n}questions.txt{module}_{topic}_review.mdInclude these with every generated file:
.gift file<name> tag fix for Moodle import errorsmoodle-mcq skill. Added GIFT format support (~6x fewer tokens than XML). Added Aiken format. Added Python syntax highlighting. Added GIFT escaping rules for programming questions. Published to GitHub.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.